feat(settings): make action_dir editable via Settings → Agent access (#3313)

Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
CodeGhost21
2026-06-04 10:34:46 -04:00
committed by GitHub
co-authored by Steven Enamakel
parent 0d9002fcfc
commit ce3ac82ecf
21 changed files with 191 additions and 21 deletions
@@ -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<AgentPaths | null>(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<string | null>(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.
@@ -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();
});
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+11
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
+10
View File
@@ -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.',
-9
View File
@@ -452,18 +452,9 @@ export async function openhumanGetAgentPaths(): Promise<CommandResponse<AgentPat
/** Partial update for the agent's editable filesystem roots (issue #3240). */
export interface AgentPathsUpdate {
/**
* New absolute action sandbox path. An empty string clears the override and
* reverts to the default; omit the field to leave it unchanged.
*/
action_dir?: string;
}
/**
* Update the agent's `action_dir` sandbox root. The backend validates the path
* (must be absolute; auto-creates a missing directory; rejects the internal
* workspace dir) and returns the refreshed {@link AgentPaths} payload.
*/
export async function openhumanUpdateAgentPaths(
update: AgentPathsUpdate
): Promise<CommandResponse<AgentPaths>> {
-5
View File
@@ -2167,8 +2167,6 @@ pub struct SandboxSettingsPatch {
pub env_passthrough: Option<Vec<String>>,
}
/// 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<RpcOutcome<serde_json::Value>, String> {
let config = load_config_with_timeout().await?;
let sandbox = &config.sandbox;
@@ -2201,7 +2199,6 @@ pub async fn get_sandbox_settings() -> Result<RpcOutcome<serde_json::Value>, 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")]
{
+1 -1
View File
@@ -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",
+47
View File
@@ -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<u64, String> {
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).
+1 -1
View File
@@ -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()`.