import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import axios from '../../utils/axiosConfig'; import type { AgentSettingsInstallation, AgentSettingsPatch } from './useAgentSettings'; import './v2-agent-policy.css'; type MemorySection = 'soul' | 'long_term' | 'shared'; type MemoryDocument = { content: string; updatedAt: string | null; visibility: string }; type MemoryDocuments = Record; const memorySections: MemorySection[] = ['long_term', 'soul', 'shared']; const trustAdapters = new Set(['claude', 'codex', 'opencode', 'qwen']); const message = (error: unknown, fallback: string) => ( (error as { response?: { data?: { error?: string } } })?.response?.data?.error || (error instanceof Error && error.message) || fallback ); interface Props { agentName: string; instanceId: string; installation: AgentSettingsInstallation | null; update: (patch: AgentSettingsPatch) => Promise; } const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, update }) => { const { t } = useTranslation(); const environment = (installation?.config?.environment || {}) as { adapter?: string; sandbox?: { mode?: string } }; const runtime = installation?.runtime; const adapter = String(environment.adapter || runtime?.adapter || runtime?.runtimeType || '') .trim().toLowerCase().replace('claude-code', 'claude'); const canSetTrust = (runtime?.host === 'byo' || runtime?.runtimeType === 'wrapper') && trustAdapters.has(adapter); const [trustMode, setTrustMode] = useState(environment.sandbox?.mode || ''); const [quota, setQuota] = useState<{ turnCap: number; userRunCeiling: number | null } | null>(null); const [documents, setDocuments] = useState(null); const [section, setSection] = useState('long_term'); const [content, setContent] = useState(''); const [busy, setBusy] = useState(false); const [feedback, setFeedback] = useState<{ group: 'trust' | 'memory'; kind: 'error' | 'saved'; text: string } | null>(null); const [memoryLoadError, setMemoryLoadError] = useState(null); const [quotaError, setQuotaError] = useState(null); useEffect(() => { setTrustMode(environment.sandbox?.mode || ''); }, [installation]); const memoryUrl = `/api/agent-memory/${encodeURIComponent(agentName)}/${encodeURIComponent(instanceId)}/editable`; const loadMemory = useCallback(async (preserveDraft = false) => { const response = await axios.get<{ sections: MemoryDocuments }>(memoryUrl); setDocuments(response.data.sections); setMemoryLoadError(null); if (!preserveDraft) setContent(response.data.sections[section]?.content || ''); }, [memoryUrl, section]); useEffect(() => { let current = true; setDocuments(null); setMemoryLoadError(null); setQuotaError(null); void axios.get<{ sections: MemoryDocuments }>(memoryUrl).then((response) => { if (current) { setDocuments(response.data.sections); setContent(response.data.sections.long_term?.content || ''); } }).catch((requestError) => { if (current) setMemoryLoadError(message(requestError, t('agentPolicy.loadError'))); }); void axios.get<{ turnCap: number; userRunCeiling: number | null }>( `/api/registry/agents/${encodeURIComponent(agentName)}/manage-settings`, { params: { instanceId } }, ).then((response) => { if (current) setQuota(response.data); }) .catch((requestError) => { if (current) setQuotaError(message(requestError, t('agentPolicy.loadError'))); }); return () => { current = false; }; }, [agentName, instanceId, memoryUrl, t]); // Trust mode autosaves the moment a valid choice is made; there is no Save button. const saveTrust = async (mode: string) => { if (!canSetTrust || !['read-only', 'workspace'].includes(mode)) return; setBusy(true); setFeedback(null); try { await update({ config: { environment: { sandbox: { mode } } } }); setFeedback({ group: 'trust', kind: 'saved', text: t('agentPolicy.saved') }); } catch (requestError) { setFeedback({ group: 'trust', kind: 'error', text: message(requestError, t('agentPolicy.saveError')) }); } finally { setBusy(false); } }; const saveMemory = async () => { if (!documents) return; setBusy(true); setFeedback(null); try { await axios.patch(`${memoryUrl}/${section}`, { content, expectedUpdatedAt: documents[section].updatedAt, }); await loadMemory(); setFeedback({ group: 'memory', kind: 'saved', text: t('agentPolicy.saved') }); } catch (requestError) { setFeedback({ group: 'memory', kind: 'error', text: message(requestError, t('agentPolicy.saveError')) }); if ((requestError as { response?: { status?: number } })?.response?.status === 409) { await loadMemory(true).catch(() => {}); } } finally { setBusy(false); } }; const changeSection = (next: MemorySection) => { setSection(next); setContent(documents?.[next]?.content || ''); setFeedback(null); }; const groupFeedback = (group: 'trust' | 'memory') => feedback?.group === group && (

{feedback.text}

); return

{t('agentPolicy.title')}

{canSetTrust &&

{t('agentPolicy.trust')}

{groupFeedback('trust')}
}

{t('agentPolicy.memory')}

{documents ? <>
{memorySections.map((entry) => )}