Refine terminal close confirmation (#5541)

* Refine terminal close confirmation

* Translate terminal close dialog strings
This commit is contained in:
Jinjing 2026-06-16 17:42:48 -07:00 committed by GitHub
parent 8da3f864b3
commit 62fbb51eb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 476 additions and 41 deletions

View File

@ -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.

View File

@ -111,6 +111,7 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
defaultTuiAgent: null,
disabledTuiAgents: [],
skipDeleteWorktreeConfirm: false,
skipCloseTerminalWithRunningProcessConfirm: false,
skipDeleteAutomationConfirm: false,
defaultTaskViewPreset: 'all',
defaultTaskSource: 'github',

View File

@ -115,6 +115,7 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
defaultTuiAgent: null,
disabledTuiAgents: [],
skipDeleteWorktreeConfirm: false,
skipCloseTerminalWithRunningProcessConfirm: false,
skipDeleteAutomationConfirm: false,
defaultTaskViewPreset: 'all',
defaultTaskSource: 'github',

View File

@ -424,6 +424,47 @@ export function TerminalPane({
</SearchableSetting>
)}
<SearchableSetting
title={translate(
'auto.components.settings.TerminalPane.ask_before_closing_running_terminals_title',
'Ask Before Closing Running Terminals'
)}
description={translate(
'auto.components.settings.TerminalPane.ask_before_closing_running_terminals_description',
'Show a confirmation before closing a terminal that has a running command or agent.'
)}
keywords={[
'confirm',
'confirmation',
'close',
'terminal',
'running',
'command',
'agent',
'process',
'prompt',
'stop'
]}
>
<SettingsSwitchRow
label={translate(
'auto.components.settings.TerminalPane.ask_before_closing_running_terminals_title',
'Ask Before Closing Running Terminals'
)}
description={translate(
'auto.components.settings.TerminalPane.ask_before_closing_running_terminals_description',
'Show a confirmation before closing a terminal that has a running command or agent.'
)}
checked={!settings.skipCloseTerminalWithRunningProcessConfirm}
onChange={() =>
updateSettings({
skipCloseTerminalWithRunningProcessConfirm:
!settings.skipCloseTerminalWithRunningProcessConfirm
})
}
/>
</SearchableSetting>
<SearchableSetting
title={translate(
'auto.components.settings.TerminalPane.8eefeaa3da',

View File

@ -34,6 +34,28 @@ export const getTerminalPaneAppearanceSearchEntries = createLocalizedCatalog(()
])
export const getTerminalPaneInteractionSearchEntries = createLocalizedCatalog(() => [
{
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(

View File

@ -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 })

View File

@ -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<void> {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
mountedRoots.push(root)
await act(async () => {
root.render(
<CloseTerminalDialog
open
copyKind={props.copyKind}
onCancel={props.onCancel ?? vi.fn()}
onConfirm={props.onConfirm}
/>
)
})
}
function clickButton(label: string): void {
const button = [...document.body.querySelectorAll<HTMLButtonElement>('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<HTMLButtonElement>('[role="checkbox"]')
expect(checkbox).not.toBeNull()
await act(async () => {
checkbox?.click()
})
await act(async () => {
clickButton('Stop Agent')
})
expect(onConfirm).toHaveBeenCalledWith(true)
})
})

View File

@ -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 (
<Dialog
open={open}
@ -30,24 +48,61 @@ export default function CloseTerminalDialog({
<DialogContent className="max-w-sm" showCloseButton={false}>
<DialogHeader>
<DialogTitle className="text-sm">
{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?'
)}
</DialogTitle>
<DialogDescription className="text-xs">
{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.'
)}
</DialogDescription>
</DialogHeader>
<div className="flex items-center gap-2">
<Checkbox
id={checkboxId}
checked={dontAskAgain}
onCheckedChange={(checked) => setDontAskAgain(checked === true)}
/>
<Label htmlFor={checkboxId} className="text-xs font-normal text-muted-foreground">
{translate(
'auto.components.terminal.pane.CloseTerminalDialog.dont_ask_again',
"Don't ask again for running terminals"
)}
</Label>
</div>
<DialogFooter className="gap-2">
<Button type="button" variant="outline" size="sm" onClick={onCancel}>
{translate('auto.components.terminal.pane.CloseTerminalDialog.1d1a7a9c1f', 'Cancel')}
</Button>
<Button type="button" variant="destructive" size="sm" autoFocus onClick={onConfirm}>
{translate('auto.components.terminal.pane.CloseTerminalDialog.ebd2fa844d', 'Close')}
<Button
type="button"
variant="destructive"
size="sm"
autoFocus
onClick={() => onConfirm(dontAskAgain)}
>
{isAgent
? translate(
'auto.components.terminal.pane.CloseTerminalDialog.stop_agent_confirm',
'Stop Agent'
)
: translate(
'auto.components.terminal.pane.CloseTerminalDialog.stop_command_confirm',
'Stop and Close'
)}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -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<SearchState>({ query: '', caseSensitive: false, regex: false })
const [closeConfirmPaneId, setCloseConfirmPaneId] = useState<number | null>(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({
)
})}
<CloseTerminalDialog
open={closeConfirmPaneId !== null}
onCancel={() => setCloseConfirmPaneId(null)}
open={pendingCloseConfirmation !== null}
copyKind={pendingCloseConfirmation?.copyKind}
onCancel={handleCancelClose}
onConfirm={handleConfirmClose}
/>
</>

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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')
})

View File

@ -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',

View File

@ -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. */