From 62fbb51eb2d2d363b67b64510d70d9c184ef7efb Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:42:48 -0700 Subject: [PATCH] Refine terminal close confirmation (#5541) * Refine terminal close confirmation * Translate terminal close dialog strings --- docs/terminal-close-confirmation.md | 114 ++++++++++++++++++ .../runtime-home-service.test.ts | 1 + src/main/codex-accounts/service.test.ts | 1 + .../src/components/settings/TerminalPane.tsx | 41 +++++++ .../terminal-pane-appearance-search.ts | 22 ++++ .../settings/terminal-search.test.ts | 18 +++ .../CloseTerminalDialog.test.tsx | 91 ++++++++++++++ .../terminal-pane/CloseTerminalDialog.tsx | 77 ++++++++++-- .../components/terminal-pane/TerminalPane.tsx | 58 ++++++--- src/renderer/src/i18n/locales/en.json | 17 ++- src/renderer/src/i18n/locales/es.json | 17 ++- src/renderer/src/i18n/locales/ja.json | 17 ++- src/renderer/src/i18n/locales/ko.json | 17 ++- src/renderer/src/i18n/locales/zh.json | 17 ++- src/shared/constants.test.ts | 4 + src/shared/constants.ts | 1 + src/shared/types.ts | 4 + 17 files changed, 476 insertions(+), 41 deletions(-) create mode 100644 docs/terminal-close-confirmation.md create mode 100644 src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx diff --git a/docs/terminal-close-confirmation.md b/docs/terminal-close-confirmation.md new file mode 100644 index 000000000..da6ce20f2 --- /dev/null +++ b/docs/terminal-close-confirmation.md @@ -0,0 +1,114 @@ +# Terminal Close Confirmation + +## Problem + +- `CloseTerminalDialog` repeats the action users already requested with "Close Terminal?" and a generic "process will be killed" warning (`src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx:30`). +- `Cmd/Ctrl+W` closes only the focused split pane, or the tab when it is the last pane; the guard exists so tab-level close does not kill every pane by accident (`src/renderer/src/components/terminal-pane/keyboard-handlers.ts:364`). +- The running-process guard probes the PTY over the active runtime/SSH path and shows the dialog only when child processes exist (`src/renderer/src/components/terminal-pane/TerminalPane.tsx:781`). +- There is no way for power users to say "I understand, close it next time" even though similar destructive workflows persist skip-confirm settings (`src/shared/types.ts:2522`, `src/renderer/src/components/settings/GeneralWorkspaceSettingsSection.tsx:58`). + +## Goal + +Make the terminal close confirmation communicate the consequence, respect focused-pane scope, and allow users to disable future running-process close confirmations from the dialog or Settings. + +## Non-goals + +- Do not change window-close behavior; whole-window shutdown intentionally bypasses the child-process dialog. +- Do not change idle-shell behavior; idle shells still close immediately. +- Do not add bulk-close UI for this change. +- Do not introduce provider-specific agent kill logic; closing still uses the existing terminal close path. +- Do not add telemetry. + +## Design + +1. Add a persisted `skipCloseTerminalWithRunningProcessConfirm` boolean to `GlobalSettings`, defaulting to `false`. +2. Keep the existing child-process probe. If the new setting is true, close immediately after the probe reports child processes instead of showing the dialog. +3. Track the pending close as `{ paneId, copyKind }`, where `copyKind` is `agent` only when `agentStatusByPaneKey[makePaneKey(tabId, leafId)]` has a live non-unknown `agentType`; otherwise it is `command`. +4. Update dialog copy: + - command: title `Stop running command?`, body `Closing this terminal will stop the command running inside it.`, destructive button `Stop and Close`. + - agent: title `Stop this agent?`, body `Closing this terminal will stop the agent's current work.`, destructive button `Stop Agent`. +5. Add a checkbox: `Don't ask again for running terminals`. When checked and confirmed, persist `skipCloseTerminalWithRunningProcessConfirm: true` before closing the pane. +6. Add a Terminal Interaction settings switch: `Ask Before Closing Running Terminals`, checked when the skip flag is false. +7. Add the new setting to terminal settings search so "confirm", "close", "running", "agent", and "command" find it. + +## Data flow + +- `Cmd/Ctrl+W` or pane close action +- `TerminalPane.handleRequestClosePane(paneId)` +- Get `ptyId`; no PTY closes immediately +- `inspectRuntimeTerminalProcess(settings, ptyId)` +- No child processes closes immediately +- Child processes + skip setting closes immediately +- Child processes + confirmation enabled opens `CloseTerminalDialog(copyKind)` +- Confirm optionally persists skip flag, then calls `executeClosePane(paneId)` + +## Edge cases + +- If process inspection rejects, preserve the existing fallback: close the pane instead of trapping the shortcut. +- If the pane is removed before the dialog confirms, `executeClosePane` already no-ops when the manager cannot close it. +- For split panes, only the active pane gets the prompt and closes. +- For last-pane tabs, confirming still delegates to `onCloseTab`. +- Agent copy appears only from live pane status. Freshly launched agents that have not emitted hooks yet may use command copy; that is acceptable because the consequence is still accurate. +- SSH/runtime-host terminals still use the existing runtime process inspection; the setting lives in global renderer settings and is passed through the same update path. +- The skip flag affects only terminal running-process close confirmations, not workspace deletion, automation deletion, window close, or future bulk-close prompts. + +## Test plan + +- Unit/component: + - `CloseTerminalDialog` renders command copy, agent copy, checkbox, and reports the checked state on confirm. + - Settings search includes the running-terminal confirmation entry. + - Default settings include `skipCloseTerminalWithRunningProcessConfirm: false`. +- Integration/lightweight: + - Verify `TerminalPane` opens agent copy when live pane status has `agentType` and command copy otherwise. + - Verify checked confirm persists the skip flag before closing. +- Electron: + - Running command + default setting shows command confirmation. + - Running command + checkbox checked confirms and future close skips the dialog. + - Agent pane with live status shows agent confirmation copy. + - Idle shell closes without confirmation. + +## UI quality bar + +- Dialog uses existing shadcn `Dialog` and `Button` primitives, token colors, and current compact modal sizing. +- Copy names the destructive consequence first and avoids implying every terminal/tab/window will close. +- Checkbox is visually subordinate to the message and aligned with existing dense dialog spacing. +- Settings row matches neighboring Terminal Interaction switch rows and is searchable. +- No layout shift, clipping, or button text overflow at the current modal width. + +## Review screenshots + +1. Running-command confirmation dialog. +2. Running-agent confirmation dialog. +3. Terminal Interaction settings row for `Ask Before Closing Running Terminals`. + +## Rollout + +1. Add shared setting type/default. +2. Add dialog copy modes and checkbox. +3. Wire `TerminalPane` to derive copy kind, honor skip flag, and persist "don't ask again" on confirm. +4. Add Terminal settings row and search entry. +5. Add targeted tests. +6. Force-add this design doc when staging because root `.gitignore` treats new `docs/**` files as local-only by default. + +## Lightweight Eng Review + +- Scope: kept focused on the existing running-process confirmation; no new close routing, bulk-close behavior, or native window-close changes. +- Architecture/data flow: renderer-only UI setting rides the existing settings persistence path; process detection remains owned by `inspectRuntimeTerminalProcess` so SSH/runtime compatibility does not fork. +- Failure modes covered: + - process-inspection rejection preserves current close fallback + - stale pane between prompt and confirm no-ops through existing manager guard + - split-pane close remains pane-scoped + - missing/stale agent status falls back to generic command copy + - skip flag is scoped to terminal running-process confirmations only +- Test coverage required: + - component test for `CloseTerminalDialog` copy/checkbox + - shared default/type coverage via existing typecheck plus default-setting assertion + - settings search test for new discoverable entry + - focused TerminalPane behavior test if practical; otherwise Electron validation covers prompt routing +- Performance/blast radius: no polling, IPC, startup, or renderer-jank impact; only an extra settings boolean read during an already user-triggered close path. +- UI quality bar: Electron validation should judge the modal and Terminal settings row against `docs/STYLEGUIDE.md`, existing `Dialog`/`Button`/`SettingsSwitchRow`, and adjacent Terminal Interaction density. +- Required review screenshots: + 1. Running-command confirmation dialog + 2. Running-agent confirmation dialog + 3. Terminal Interaction settings row +- Residual risks: agent-specific copy depends on live hook status, so newly launched or manually run agents can still receive generic command copy; the design doc is ignored by default and must be force-staged for the PR. diff --git a/src/main/codex-accounts/runtime-home-service.test.ts b/src/main/codex-accounts/runtime-home-service.test.ts index 9e3b4dfc6..3ebbf6713 100644 --- a/src/main/codex-accounts/runtime-home-service.test.ts +++ b/src/main/codex-accounts/runtime-home-service.test.ts @@ -111,6 +111,7 @@ function createSettings(overrides: Partial = {}): GlobalSettings defaultTuiAgent: null, disabledTuiAgents: [], skipDeleteWorktreeConfirm: false, + skipCloseTerminalWithRunningProcessConfirm: false, skipDeleteAutomationConfirm: false, defaultTaskViewPreset: 'all', defaultTaskSource: 'github', diff --git a/src/main/codex-accounts/service.test.ts b/src/main/codex-accounts/service.test.ts index 29da65fbf..1072b9999 100644 --- a/src/main/codex-accounts/service.test.ts +++ b/src/main/codex-accounts/service.test.ts @@ -115,6 +115,7 @@ function createSettings(overrides: Partial = {}): GlobalSettings defaultTuiAgent: null, disabledTuiAgents: [], skipDeleteWorktreeConfirm: false, + skipCloseTerminalWithRunningProcessConfirm: false, skipDeleteAutomationConfirm: false, defaultTaskViewPreset: 'all', defaultTaskSource: 'github', diff --git a/src/renderer/src/components/settings/TerminalPane.tsx b/src/renderer/src/components/settings/TerminalPane.tsx index f75539fd5..dd00d685b 100644 --- a/src/renderer/src/components/settings/TerminalPane.tsx +++ b/src/renderer/src/components/settings/TerminalPane.tsx @@ -424,6 +424,47 @@ export function TerminalPane({ )} + + + updateSettings({ + skipCloseTerminalWithRunningProcessConfirm: + !settings.skipCloseTerminalWithRunningProcessConfirm + }) + } + /> + + [ + { + title: translate( + 'auto.components.settings.terminal.search.ask_before_closing_running_terminals_title', + 'Ask Before Closing Running Terminals' + ), + description: translate( + 'auto.components.settings.terminal.search.ask_before_closing_running_terminals_description', + 'Show a confirmation before closing a terminal that has a running command or agent.' + ), + keywords: [ + ...translateSearchKeyword('auto.components.settings.terminal.search.10f9fb6fea', 'settings'), + ...translateSearchKeyword('auto.components.settings.terminal.search.a0c44061ee', 'confirm'), + ...translateSearchKeyword('auto.components.settings.terminal.search.close_terminal', 'close'), + ...translateSearchKeyword('auto.components.settings.terminal.search.39ea7c0d28', 'terminal'), + ...translateSearchKeyword('auto.components.settings.terminal.search.running', 'running'), + ...translateSearchKeyword('auto.components.settings.terminal.search.command', 'command'), + ...translateSearchKeyword('auto.components.settings.terminal.search.agent', 'agent'), + ...translateSearchKeyword('auto.components.settings.terminal.search.process', 'process'), + ...translateSearchKeyword('auto.components.settings.terminal.search.prompt', 'prompt'), + ...translateSearchKeyword('auto.components.settings.terminal.search.stop', 'stop') + ] + }, { title: translate('auto.components.settings.terminal.search.c6178a2b4d', 'Focus Follows Mouse'), description: translate( diff --git a/src/renderer/src/components/settings/terminal-search.test.ts b/src/renderer/src/components/settings/terminal-search.test.ts index f1071bfc1..44ead63bb 100644 --- a/src/renderer/src/components/settings/terminal-search.test.ts +++ b/src/renderer/src/components/settings/terminal-search.test.ts @@ -69,6 +69,24 @@ describe('getTerminalPaneSearchEntries', () => { ).toBe(true) }) + it('includes the running-terminal close confirmation setting on all platforms', () => { + const entriesWindows = getTerminalPaneSearchEntries({ isWindows: true, isMac: false }) + const entriesMac = getTerminalPaneSearchEntries({ isWindows: false, isMac: true }) + const entriesLinux = getTerminalPaneSearchEntries({ isWindows: false, isMac: false }) + const hasEntry = (entries: typeof entriesWindows): boolean => + entries.some( + (entry) => + entry.title === 'Ask Before Closing Running Terminals' && + matchesSettingsSearch('confirm', [entry]) && + matchesSettingsSearch('agent', [entry]) && + matchesSettingsSearch('close', [entry]) + ) + + expect(hasEntry(entriesWindows)).toBe(true) + expect(hasEntry(entriesMac)).toBe(true) + expect(hasEntry(entriesLinux)).toBe(true) + }) + it('keeps terminal appearance settings in the Appearance search index', () => { const entriesWindows = getTerminalPaneSearchEntries({ isWindows: true, isMac: false }) const entriesMac = getTerminalPaneSearchEntries({ isWindows: false, isMac: true }) diff --git a/src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx new file mode 100644 index 000000000..bd18107ac --- /dev/null +++ b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx @@ -0,0 +1,91 @@ +// @vitest-environment happy-dom + +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' +import CloseTerminalDialog from './CloseTerminalDialog' + +const mountedRoots: Root[] = [] + +async function renderDialog(props: { + copyKind?: 'command' | 'agent' + onConfirm: (dontAskAgain: boolean) => void + onCancel?: () => void +}): Promise { + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + mountedRoots.push(root) + + await act(async () => { + root.render( + + ) + }) +} + +function clickButton(label: string): void { + const button = [...document.body.querySelectorAll('button')].find( + (candidate) => candidate.textContent === label + ) + if (!button) { + throw new Error(`Button not found: ${label}`) + } + button.click() +} + +describe('CloseTerminalDialog', () => { + afterEach(async () => { + await act(async () => { + for (const root of mountedRoots.splice(0)) { + root.unmount() + } + }) + document.body.innerHTML = '' + }) + + it('renders running command copy and confirms without skipping by default', async () => { + const onConfirm = vi.fn() + + await renderDialog({ copyKind: 'command', onConfirm }) + + expect(document.body.textContent).toContain('Stop running command?') + expect(document.body.textContent).toContain( + 'Closing this terminal will stop the command running inside it.' + ) + + await act(async () => { + clickButton('Stop and Close') + }) + + expect(onConfirm).toHaveBeenCalledWith(false) + }) + + it('renders agent copy and passes the skip preference when checked', async () => { + const onConfirm = vi.fn() + + await renderDialog({ copyKind: 'agent', onConfirm }) + + expect(document.body.textContent).toContain('Stop this agent?') + expect(document.body.textContent).toContain( + "Closing this terminal will stop the agent's current work." + ) + + const checkbox = document.body.querySelector('[role="checkbox"]') + expect(checkbox).not.toBeNull() + + await act(async () => { + checkbox?.click() + }) + await act(async () => { + clickButton('Stop Agent') + }) + + expect(onConfirm).toHaveBeenCalledWith(true) + }) +}) diff --git a/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx index 9ca464e8b..a1f5c371e 100644 --- a/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx +++ b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx @@ -1,3 +1,4 @@ +import { useEffect, useId, useState } from 'react' import { Dialog, DialogContent, @@ -7,17 +8,34 @@ import { DialogTitle } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' +import { Label } from '@/components/ui/label' import { translate } from '@/i18n/i18n' +export type CloseTerminalDialogCopyKind = 'command' | 'agent' + export default function CloseTerminalDialog({ open, + copyKind = 'command', onCancel, onConfirm }: { open: boolean + copyKind?: CloseTerminalDialogCopyKind onCancel: () => void - onConfirm: () => void + onConfirm: (dontAskAgain: boolean) => void }): React.JSX.Element { + const checkboxId = useId() + const [dontAskAgain, setDontAskAgain] = useState(false) + + useEffect(() => { + if (open) { + setDontAskAgain(false) + } + }, [open]) + + const isAgent = copyKind === 'agent' + return ( - {translate( - 'auto.components.terminal.pane.CloseTerminalDialog.78b79d854d', - 'Close Terminal?' - )} + {isAgent + ? translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_agent_title', + 'Stop this agent?' + ) + : translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_command_title', + 'Stop running command?' + )} - {translate( - 'auto.components.terminal.pane.CloseTerminalDialog.6b9a6975f8', - 'The terminal still has a running process. If you close the terminal, the process will be killed.' - )} + {isAgent + ? translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_agent_description', + "Closing this terminal will stop the agent's current work." + ) + : translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_command_description', + 'Closing this terminal will stop the command running inside it.' + )} +
+ setDontAskAgain(checked === true)} + /> + +
- diff --git a/src/renderer/src/components/terminal-pane/TerminalPane.tsx b/src/renderer/src/components/terminal-pane/TerminalPane.tsx index a3a0a6e1c..8cbc578f3 100644 --- a/src/renderer/src/components/terminal-pane/TerminalPane.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalPane.tsx @@ -34,7 +34,7 @@ import { useTerminalKeyboardShortcuts, type SearchState } from './keyboard-handl import type { MacOptionAsAlt } from './terminal-shortcut-policy' import { useEffectiveMacOptionAsAlt } from '@/lib/keyboard-layout/use-effective-mac-option-as-alt' import { useTerminalFontZoom } from './useTerminalFontZoom' -import CloseTerminalDialog from './CloseTerminalDialog' +import CloseTerminalDialog, { type CloseTerminalDialogCopyKind } from './CloseTerminalDialog' import { MobileDriverOverlay } from './MobileDriverOverlay' import { TerminalErrorToast } from './TerminalErrorToast' import { TerminalSessionStateSaveFailureDialog } from './TerminalSessionStateSaveFailureDialog' @@ -216,7 +216,10 @@ export default function TerminalPane({ const searchOpenRef = useRef(false) searchOpenRef.current = searchOpen const searchStateRef = useRef({ query: '', caseSensitive: false, regex: false }) - const [closeConfirmPaneId, setCloseConfirmPaneId] = useState(null) + const [pendingCloseConfirmation, setPendingCloseConfirmation] = useState<{ + paneId: number + copyKind: CloseTerminalDialogCopyKind + } | null>(null) const [quickCommandEditorOpen, setQuickCommandEditorOpen] = useState(false) // Why: the terminal menu can be the first quick-command entry point, so each // Add action starts with a fresh draft instead of reusing cancelled text. @@ -782,6 +785,19 @@ export default function TerminalPane({ // a running child process (e.g. npm run dev), so the user doesn't // accidentally kill it. An idle shell prompt closes immediately. Ctrl+D // (explicit EOF) bypasses this by design. + const getCloseDialogCopyKind = useCallback( + (paneId: number): CloseTerminalDialogCopyKind => { + const leafId = managerRef.current?.getLeafId(paneId) + if (!leafId) { + return 'command' + } + const agentType = + useAppStore.getState().agentStatusByPaneKey[makePaneKey(tabId, leafId)]?.agentType + return agentType && agentType !== 'unknown' ? 'agent' : 'command' + }, + [tabId] + ) + const handleRequestClosePane = useCallback( (paneId: number) => { const transport = paneTransportsRef.current.get(paneId) @@ -793,10 +809,10 @@ export default function TerminalPane({ const settings = useAppStore.getState().settings void inspectRuntimeTerminalProcess(settings, ptyId) .then((process) => { - if (process.hasChildProcesses) { - setCloseConfirmPaneId(paneId) - } else { + if (!process.hasChildProcesses || settings?.skipCloseTerminalWithRunningProcessConfirm) { executeClosePane(paneId) + } else { + setPendingCloseConfirmation({ paneId, copyKind: getCloseDialogCopyKind(paneId) }) } }) // Why: if the child-process probe rejects (IPC wedged, handler @@ -805,7 +821,7 @@ export default function TerminalPane({ // had a child process. Matches the semantics of the !ptyId branch above. .catch(() => executeClosePane(paneId)) }, - [executeClosePane] + [executeClosePane, getCloseDialogCopyKind] ) const handleSearchSelectedText = useCallback((selectedText: string): void => { @@ -813,13 +829,24 @@ export default function TerminalPane({ state.showRightSidebarSearch({ query: selectedText }) }, []) - const handleConfirmClose = useCallback(() => { - if (closeConfirmPaneId === null) { - return - } - executeClosePane(closeConfirmPaneId) - setCloseConfirmPaneId(null) - }, [closeConfirmPaneId, executeClosePane]) + const handleConfirmClose = useCallback( + (dontAskAgain: boolean) => { + if (pendingCloseConfirmation === null) { + return + } + const paneId = pendingCloseConfirmation.paneId + setPendingCloseConfirmation(null) + if (dontAskAgain) { + void updateSettings({ skipCloseTerminalWithRunningProcessConfirm: true }) + } + executeClosePane(paneId) + }, + [executeClosePane, pendingCloseConfirmation, updateSettings] + ) + + const handleCancelClose = useCallback(() => { + setPendingCloseConfirmation(null) + }, []) useTerminalPaneLifecycle({ tabId, @@ -2165,8 +2192,9 @@ export default function TerminalPane({ ) })} setCloseConfirmPaneId(null)} + open={pendingCloseConfirmation !== null} + copyKind={pendingCloseConfirmation?.copyKind} + onCancel={handleCancelClose} onConfirm={handleConfirmClose} /> diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 234ca1a35..27ad8992e 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -2224,7 +2224,14 @@ "ebd2fa844d": "Close", "1d1a7a9c1f": "Cancel", "6b9a6975f8": "The terminal still has a running process. If you close the terminal, the process will be killed.", - "78b79d854d": "Close Terminal?" + "78b79d854d": "Close Terminal?", + "stop_agent_title": "Stop this agent?", + "stop_command_title": "Stop running command?", + "stop_agent_description": "Closing this terminal will stop the agent's current work.", + "stop_command_description": "Closing this terminal will stop the command running inside it.", + "dont_ask_again": "Don't ask again for running terminals", + "stop_agent_confirm": "Stop Agent", + "stop_command_confirm": "Stop and Close" }, "MobileDriverOverlay": { "c6460cf584": "Take back", @@ -6067,7 +6074,9 @@ "af0c3b6e39": "On Windows, right-click pastes the clipboard into the terminal. Use Ctrl+right-click to open the context menu.", "29154326bb": "on", "ab20575a8a": "off", - "ab3a1f9068": "wsl.exe" + "ab3a1f9068": "wsl.exe", + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "TerminalSettingsPreview": { "a63953a48a": "Preview {{value0}} theme", @@ -7675,7 +7684,9 @@ "e989914ad6": "Font Family", "33031c1465": "text size", "0fe0073f0c": "Default terminal font size for new panes and live updates.", - "5930244899": "Font Size" + "5930244899": "Font Size", + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index d60388e39..4d711ed3b 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -2224,7 +2224,14 @@ "ebd2fa844d": "Cerca", "1d1a7a9c1f": "Cancelar", "6b9a6975f8": "La terminal todavía tiene un proceso en ejecución. Si cierra la terminal, el proceso finalizará.", - "78b79d854d": "¿Cerrar terminal?" + "78b79d854d": "¿Cerrar terminal?", + "stop_agent_title": "¿Detener este agente?", + "stop_command_title": "¿Detener comando en ejecución?", + "stop_agent_description": "Al cerrar esta terminal, se detendrá el trabajo actual del agente.", + "stop_command_description": "Al cerrar esta terminal, se detendrá el comando que se está ejecutando en ella.", + "dont_ask_again": "No volver a preguntar para terminales en ejecución", + "stop_agent_confirm": "Detener agente", + "stop_command_confirm": "Detener y cerrar" }, "MobileDriverOverlay": { "c6460cf584": "Devolver", @@ -6030,7 +6037,9 @@ "af0c3b6e39": "En Windows, haga clic derecho y pegue el portapapeles en la terminal. Utilice Ctrl+clic derecho para abrir el menú contextual.", "29154326bb": "en", "ab20575a8a": "apagado", - "ab3a1f9068": "wsl.exe" + "ab3a1f9068": "wsl.exe", + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "TerminalSettingsPreview": { "a63953a48a": "Vista previa del tema {{value0}}", @@ -7638,7 +7647,9 @@ "description": "Import theme YAML files as Orca terminal themes.", "keyword_yaml": "yaml", "keyword_custom": "custom" - } + }, + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 47b72dd53..650c7081b 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -2224,7 +2224,14 @@ "ebd2fa844d": "閉じる", "1d1a7a9c1f": "キャンセル", "6b9a6975f8": "terminal にはまだ実行中のプロセスがあります。terminal を閉じるとプロセスが強制終了されます。", - "78b79d854d": "Terminal を閉じますか?" + "78b79d854d": "Terminal を閉じますか?", + "stop_agent_title": "このエージェントを停止しますか?", + "stop_command_title": "実行中のコマンドを停止しますか?", + "stop_agent_description": "この terminal を閉じると、エージェントの現在の作業が停止します。", + "stop_command_description": "この terminal を閉じると、その中で実行中のコマンドが停止します。", + "dont_ask_again": "実行中の terminal では今後確認しない", + "stop_agent_confirm": "エージェントを停止", + "stop_command_confirm": "停止して閉じる" }, "MobileDriverOverlay": { "c6460cf584": "操作を取り戻す", @@ -6052,7 +6059,9 @@ "af0c3b6e39": "Windows では、右クリックしてクリップボードを terminal に貼り付けます。 Ctrl キーを押しながら右クリックしてコンテキスト メニューを開きます。", "29154326bb": "の上", "ab20575a8a": "オフ", - "ab3a1f9068": "wsl.exe" + "ab3a1f9068": "wsl.exe", + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "TerminalSettingsPreview": { "a63953a48a": "{{value0}} テーマのプレビュー", @@ -7660,7 +7669,9 @@ "e989914ad6": "フォントファミリー", "33031c1465": "文字サイズ", "0fe0073f0c": "新規ペインとライブ アップデートのデフォルトの terminal フォント サイズ。", - "5930244899": "フォントサイズ" + "5930244899": "フォントサイズ", + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index c213bd8ce..a441eb228 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -2224,7 +2224,14 @@ "ebd2fa844d": "닫기", "1d1a7a9c1f": "취소", "6b9a6975f8": "terminal 에는 여전히 실행 중인 프로세스가 있습니다. terminal을 닫으면 프로세스가 종료됩니다.", - "78b79d854d": "Terminal을 닫으시겠습니까?" + "78b79d854d": "Terminal을 닫으시겠습니까?", + "stop_agent_title": "Stop this agent?", + "stop_command_title": "Stop running command?", + "stop_agent_description": "Closing this terminal will stop the agent's current work.", + "stop_command_description": "Closing this terminal will stop the command running inside it.", + "dont_ask_again": "Don't ask again for running terminals", + "stop_agent_confirm": "Stop Agent", + "stop_command_confirm": "Stop and Close" }, "MobileDriverOverlay": { "c6460cf584": "회수", @@ -6015,7 +6022,9 @@ "af0c3b6e39": "Windows에서는 마우스 오른쪽 버튼을 클릭하여 클립보드를 terminal 에 붙여넣습니다. 컨텍스트 메뉴를 열려면 Ctrl+오른쪽 클릭을 사용하세요.", "29154326bb": "~에", "ab20575a8a": "끄다", - "ab3a1f9068": "wsl.exe" + "ab3a1f9068": "wsl.exe", + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "TerminalSettingsPreview": { "a63953a48a": "{{value0}} 테마 미리보기", @@ -7623,7 +7632,9 @@ "description": "Import theme YAML files as Orca terminal themes.", "keyword_yaml": "yaml", "keyword_custom": "custom" - } + }, + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 38b07fd37..252912801 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -2224,7 +2224,14 @@ "ebd2fa844d": "关闭", "1d1a7a9c1f": "取消", "6b9a6975f8": "terminal 仍有正在运行的进程。如果关闭 terminal,该进程将被终止。", - "78b79d854d": "关闭 Terminal?" + "78b79d854d": "关闭 Terminal?", + "stop_agent_title": "Stop this agent?", + "stop_command_title": "Stop running command?", + "stop_agent_description": "Closing this terminal will stop the agent's current work.", + "stop_command_description": "Closing this terminal will stop the command running inside it.", + "dont_ask_again": "Don't ask again for running terminals", + "stop_agent_confirm": "Stop Agent", + "stop_command_confirm": "Stop and Close" }, "MobileDriverOverlay": { "c6460cf584": "收回", @@ -6015,7 +6022,9 @@ "af0c3b6e39": "在 Windows 上,右键单击将剪贴板粘贴到 terminal 中。使用 Ctrl+右键单击打开上下文菜单。", "29154326bb": "在", "ab20575a8a": "离开", - "ab3a1f9068": "执行程序" + "ab3a1f9068": "执行程序", + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "TerminalSettingsPreview": { "a63953a48a": "预览 {{value0}} 主题", @@ -7623,7 +7632,9 @@ "description": "Import theme YAML files as Orca terminal themes.", "keyword_yaml": "yaml", "keyword_custom": "自定义" - } + }, + "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." }, "windows": { "search": { diff --git a/src/shared/constants.test.ts b/src/shared/constants.test.ts index 098c4e165..fcf4ac33e 100644 --- a/src/shared/constants.test.ts +++ b/src/shared/constants.test.ts @@ -31,6 +31,10 @@ describe('getDefaultSettings', () => { expect(getDefaultSettings('/tmp').terminalUseSeparateLightTheme).toBe(true) }) + it('asks before closing terminals with running processes by default', () => { + expect(getDefaultSettings('/tmp').skipCloseTerminalWithRunningProcessConfirm).toBe(false) + }) + it('uses system language by default', () => { expect(getDefaultSettings('/tmp').uiLanguage).toBe('system') }) diff --git a/src/shared/constants.ts b/src/shared/constants.ts index be520356d..5f2a3e2bb 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -287,6 +287,7 @@ export function getDefaultSettings(homedir: string): GlobalSettings { disabledTuiAgents: [...DEFAULT_DISABLED_TUI_AGENTS], claudeAgentTeamsDefaultDisabledMigrated: true, skipDeleteWorktreeConfirm: false, + skipCloseTerminalWithRunningProcessConfirm: false, skipDeleteAutomationConfirm: false, defaultTaskViewPreset: 'all', defaultTaskSource: 'github', diff --git a/src/shared/types.ts b/src/shared/types.ts index dffb9de18..ed0a2384b 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -2531,6 +2531,10 @@ export type GlobalSettings = { * again" checkbox inside it or from the General settings pane. We keep this * defaulted to false so first-time behavior stays safe. */ skipDeleteWorktreeConfirm: boolean + /** Why: closing a terminal with child processes kills foreground work. Keep + * this separate from other destructive confirmations so power users can speed + * up terminal cleanup without weakening workspace or automation safeguards. */ + skipCloseTerminalWithRunningProcessConfirm: boolean /** Why: deleting an automation also deletes its run history. Keep this * separate from worktree deletion so skipping one destructive confirmation * does not silently skip the other. */