diff --git a/pr-body.md b/pr-body.md new file mode 100644 index 000000000..179ce7682 --- /dev/null +++ b/pr-body.md @@ -0,0 +1,25 @@ +## Summary + +Fixes #2035 + +Adds the ability to delete remote server entries from the ACP Agents configuration page. Previously, when a remote SSH server was used, an ACP Agent entry was auto-created but could not be removed. If the server was no longer in use, the entry cluttered the UI indefinitely. + +## Changes + +- **AcpAgentsConfig.tsx**: Added a `deleteRemoteConnection` handler that calls `sshApi.deleteConnection()`, removes the connection from `savedConnections` state, cleans up probe data, and shows success/error notifications. Added a Delete button (red/danger variant) next to the existing Refresh button for each remote server row. +- **Locale files** (`en-US`, `zh-CN`, `zh-TW`): Added `remote.deleteConnection`, `remote.deleteConfirm`, `notifications.deleteConnectionSuccess`, and `notifications.deleteConnectionFailed` keys. + +## Behavior + +1. User clicks the Delete button on a remote server row +2. A confirmation dialog asks: "Remove the remote server \"{{name}}\" from saved SSH connections? Its ACP agent entries will also be removed." +3. On confirm, `sshApi.deleteConnection(connectionId)` is called +4. The connection is removed from the saved connections list and probe data is cleaned up +5. A success notification is shown (or error notification on failure) + +## Validation + +- TypeScript: `tsc --noEmit` passes (no errors in modified files) +- Tests: All 7 existing `AcpAgentsConfig.test.tsx` tests pass +- i18n audit: `pnpm run i18n:audit` passes with 0 warnings +- Locale key parity verified across en-US, zh-CN, zh-TW diff --git a/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx b/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx index be35d42ca..ad21f4c3c 100644 --- a/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx @@ -13,6 +13,7 @@ import { Search, Server, Terminal, + Trash2, } from 'lucide-react'; import { Button, Input, Select, Textarea } from '@/component-library'; import { @@ -379,6 +380,7 @@ const AcpAgentsConfig: React.FC = () => { const [registryFilter, setRegistryFilter] = useState('all'); const [installingClientIds, setInstallingClientIds] = useState>(() => new Set()); const [installingRemoteClientIds, setInstallingRemoteClientIds] = useState>(() => new Set()); + const [deletingRemoteIds, setDeletingRemoteIds] = useState>(() => new Set()); const requirementProbeRequestIdRef = useRef(0); const savingConfigRef = useRef(false); const loadedRemoteProbeIdsRef = useRef>(new Set()); @@ -533,6 +535,35 @@ const AcpAgentsConfig: React.FC = () => { } }, [notifyError, t]); + const deleteRemoteConnection = useCallback(async (connectionId: string, connectionName: string) => { + const confirmed = await window.confirm(t('remote.deleteConfirm', { name: connectionName })); + if (!confirmed) return; + + setDeletingRemoteIds(prev => new Set(prev).add(connectionId)); + try { + await sshApi.deleteConnection(connectionId); + setSavedConnections(prev => prev.filter(conn => conn.id !== connectionId)); + loadedRemoteProbeIdsRef.current.delete(connectionId); + setRemoteRequirementProbes(prev => { + const next = { ...prev }; + delete next[connectionId]; + return next; + }); + notifySuccess(t('notifications.deleteConnectionSuccess')); + } catch (error) { + log.error('Failed to delete remote SSH connection', error); + notifyError(error instanceof Error ? error.message : String(error), { + title: t('notifications.deleteConnectionFailed'), + }); + } finally { + setDeletingRemoteIds(prev => { + const next = new Set(prev); + next.delete(connectionId); + return next; + }); + } + }, [notifyError, notifySuccess, t]); + const loadConfig = useCallback(async ( options: { showLoading?: boolean; refreshRequirements?: boolean } = {} ) => { @@ -1397,6 +1428,7 @@ const AcpAgentsConfig: React.FC = () => { connection.id ); const probingRemote = probingRemoteRequirements.has(connection.id); + const deletingRemote = deletingRemoteIds.has(connection.id); const remoteRows = remoteAgentIds.map(clientId => { const preset = PRESET_BY_ID.get(clientId); const clientConfig = config.acpClients[clientId]; @@ -1502,6 +1534,17 @@ const AcpAgentsConfig: React.FC = () => { {t('remote.refreshDetection')} +