mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Merge pull request #1148 from QwenLM/feat/no-quit-confirm
Remove `/quit-confirm` flow
This commit is contained in:
@@ -145,16 +145,6 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **`nodesc`** or **`nodescriptions`**:
|
||||
- **Description:** Hide tool descriptions, showing only the tool names.
|
||||
|
||||
- **`/quit-confirm`**
|
||||
- **Description:** Show a confirmation dialog before exiting Qwen Code, allowing you to choose how to handle your current session.
|
||||
- **Usage:** `/quit-confirm`
|
||||
- **Features:**
|
||||
- **Quit immediately:** Exit without saving anything (equivalent to `/quit`)
|
||||
- **Generate summary and quit:** Create a project summary using `/summary` before exiting
|
||||
- **Save conversation and quit:** Save the current conversation with an auto-generated tag before exiting
|
||||
- **Keyboard shortcut:** Press **Ctrl+C** twice to trigger the quit confirmation dialog
|
||||
- **Note:** This command is automatically triggered when you press Ctrl+C once, providing a safety mechanism to prevent accidental exits.
|
||||
|
||||
- **`/quit`** (or **`/exit`**)
|
||||
- **Description:** Exit Qwen Code immediately without any confirmation dialog.
|
||||
|
||||
|
||||
@@ -671,4 +671,4 @@ Note: When usage statistics are enabled, events are sent to an Alibaba Cloud RUM
|
||||
- **Category:** UI
|
||||
- **Requires Restart:** No
|
||||
- **Example:** `"enableWelcomeBack": false`
|
||||
- **Details:** When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. This feature integrates with the `/summary` command and quit confirmation dialog. See the [Welcome Back documentation](./welcome-back.md) for more details.
|
||||
- **Details:** When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. This feature integrates with the `/summary` command. See the [Welcome Back documentation](./welcome-back.md) for more details.
|
||||
|
||||
@@ -81,14 +81,6 @@ The Welcome Back feature works seamlessly with the `/summary` command:
|
||||
2. **Automatic Detection:** Next time you start Qwen Code in this project, Welcome Back will detect the summary
|
||||
3. **Resume Work:** Choose to continue and the summary will be loaded as context
|
||||
|
||||
### Quit Confirmation
|
||||
|
||||
When exiting with `/quit-confirm` and choosing "Generate summary and quit":
|
||||
|
||||
1. A project summary is automatically created
|
||||
2. Next session will trigger the Welcome Back dialog
|
||||
3. You can seamlessly continue your work
|
||||
|
||||
## File Structure
|
||||
|
||||
The Welcome Back feature creates and uses:
|
||||
|
||||
@@ -110,7 +110,6 @@ export default {
|
||||
'open full Qwen Code documentation in your browser',
|
||||
'Configuration not available.': 'Configuration not available.',
|
||||
'change the auth method': 'change the auth method',
|
||||
'Show quit confirmation dialog': 'Show quit confirmation dialog',
|
||||
'Copy the last result or code snippet to clipboard':
|
||||
'Copy the last result or code snippet to clipboard',
|
||||
|
||||
@@ -690,18 +689,6 @@ export default {
|
||||
'A custom command wants to run the following shell commands:':
|
||||
'A custom command wants to run the following shell commands:',
|
||||
|
||||
// ============================================================================
|
||||
// Dialogs - Quit Confirmation
|
||||
// ============================================================================
|
||||
'What would you like to do before exiting?':
|
||||
'What would you like to do before exiting?',
|
||||
'Quit immediately (/quit)': 'Quit immediately (/quit)',
|
||||
'Generate summary and quit (/summary)':
|
||||
'Generate summary and quit (/summary)',
|
||||
'Save conversation and quit (/chat save)':
|
||||
'Save conversation and quit (/chat save)',
|
||||
'Cancel (stay in application)': 'Cancel (stay in application)',
|
||||
|
||||
// ============================================================================
|
||||
// Dialogs - Pro Quota
|
||||
// ============================================================================
|
||||
|
||||
@@ -108,7 +108,6 @@ export default {
|
||||
'在浏览器中打开完整的 Qwen Code 文档',
|
||||
'Configuration not available.': '配置不可用',
|
||||
'change the auth method': '更改认证方法',
|
||||
'Show quit confirmation dialog': '显示退出确认对话框',
|
||||
'Copy the last result or code snippet to clipboard':
|
||||
'将最后的结果或代码片段复制到剪贴板',
|
||||
|
||||
@@ -655,15 +654,6 @@ export default {
|
||||
'A custom command wants to run the following shell commands:':
|
||||
'自定义命令想要运行以下 shell 命令:',
|
||||
|
||||
// ============================================================================
|
||||
// Dialogs - Quit Confirmation
|
||||
// ============================================================================
|
||||
'What would you like to do before exiting?': '退出前您想要做什么?',
|
||||
'Quit immediately (/quit)': '立即退出 (/quit)',
|
||||
'Generate summary and quit (/summary)': '生成摘要并退出 (/summary)',
|
||||
'Save conversation and quit (/chat save)': '保存对话并退出 (/chat save)',
|
||||
'Cancel (stay in application)': '取消(留在应用程序中)',
|
||||
|
||||
// ============================================================================
|
||||
// Dialogs - Pro Quota
|
||||
// ============================================================================
|
||||
|
||||
@@ -71,7 +71,6 @@ vi.mock('../ui/commands/modelCommand.js', () => ({
|
||||
}));
|
||||
vi.mock('../ui/commands/quitCommand.js', () => ({
|
||||
quitCommand: {},
|
||||
quitConfirmCommand: {},
|
||||
}));
|
||||
vi.mock('../ui/commands/statsCommand.js', () => ({ statsCommand: {} }));
|
||||
vi.mock('../ui/commands/themeCommand.js', () => ({ themeCommand: {} }));
|
||||
|
||||
@@ -28,7 +28,7 @@ import { mcpCommand } from '../ui/commands/mcpCommand.js';
|
||||
import { memoryCommand } from '../ui/commands/memoryCommand.js';
|
||||
import { modelCommand } from '../ui/commands/modelCommand.js';
|
||||
import { permissionsCommand } from '../ui/commands/permissionsCommand.js';
|
||||
import { quitCommand, quitConfirmCommand } from '../ui/commands/quitCommand.js';
|
||||
import { quitCommand } from '../ui/commands/quitCommand.js';
|
||||
import { restoreCommand } from '../ui/commands/restoreCommand.js';
|
||||
import { settingsCommand } from '../ui/commands/settingsCommand.js';
|
||||
import { statsCommand } from '../ui/commands/statsCommand.js';
|
||||
@@ -77,7 +77,6 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
modelCommand,
|
||||
...(this.config?.getFolderTrust() ? [permissionsCommand] : []),
|
||||
quitCommand,
|
||||
quitConfirmCommand,
|
||||
restoreCommand(this.config),
|
||||
statsCommand,
|
||||
summaryCommand,
|
||||
|
||||
@@ -89,7 +89,6 @@ import { useSessionStats } from './contexts/SessionContext.js';
|
||||
import { useGitBranchName } from './hooks/useGitBranchName.js';
|
||||
import { useExtensionUpdates } from './hooks/useExtensionUpdates.js';
|
||||
import { ShellFocusContext } from './contexts/ShellFocusContext.js';
|
||||
import { useQuitConfirmation } from './hooks/useQuitConfirmation.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { useWelcomeBack } from './hooks/useWelcomeBack.js';
|
||||
import { useDialogClose } from './hooks/useDialogClose.js';
|
||||
@@ -446,8 +445,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
|
||||
const { toggleVimEnabled } = useVimMode();
|
||||
|
||||
const { showQuitConfirmation } = useQuitConfirmation();
|
||||
|
||||
const {
|
||||
isSubagentCreateDialogOpen,
|
||||
openSubagentCreateDialog,
|
||||
@@ -493,7 +490,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
addConfirmUpdateExtensionRequest,
|
||||
openSubagentCreateDialog,
|
||||
openAgentsManagerDialog,
|
||||
_showQuitConfirmation: showQuitConfirmation,
|
||||
}),
|
||||
[
|
||||
openAuthDialog,
|
||||
@@ -507,7 +503,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
openPermissionsDialog,
|
||||
openApprovalModeDialog,
|
||||
addConfirmUpdateExtensionRequest,
|
||||
showQuitConfirmation,
|
||||
openSubagentCreateDialog,
|
||||
openAgentsManagerDialog,
|
||||
],
|
||||
@@ -520,7 +515,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
commandContext,
|
||||
shellConfirmationRequest,
|
||||
confirmationRequest,
|
||||
quitConfirmationRequest,
|
||||
} = useSlashCommandProcessor(
|
||||
config,
|
||||
settings,
|
||||
@@ -969,7 +963,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
isFolderTrustDialogOpen,
|
||||
showWelcomeBackDialog,
|
||||
handleWelcomeBackClose,
|
||||
quitConfirmationRequest,
|
||||
});
|
||||
|
||||
const handleExit = useCallback(
|
||||
@@ -983,25 +976,18 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
}
|
||||
// Exit directly without showing confirmation dialog
|
||||
// Exit directly
|
||||
handleSlashCommand('/quit');
|
||||
return;
|
||||
}
|
||||
|
||||
// First press: Prioritize cleanup tasks
|
||||
|
||||
// Special case: If quit-confirm dialog is open, Ctrl+C means "quit immediately"
|
||||
if (quitConfirmationRequest) {
|
||||
handleSlashCommand('/quit');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Close other dialogs (highest priority)
|
||||
/**
|
||||
* For AuthDialog it is required to complete the authentication process,
|
||||
* otherwise user cannot proceed to the next step.
|
||||
* So a quit on AuthDialog should go with normal two press quit
|
||||
* and without quit-confirm dialog.
|
||||
* So a quit on AuthDialog should go with normal two press quit.
|
||||
*/
|
||||
if (isAuthDialogOpen) {
|
||||
setPressedOnce(true);
|
||||
@@ -1022,14 +1008,17 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
return; // Request cancelled, end processing
|
||||
}
|
||||
|
||||
// 3. Clear input buffer (if has content)
|
||||
// 4. Clear input buffer (if has content)
|
||||
if (buffer.text.length > 0) {
|
||||
buffer.setText('');
|
||||
return; // Input cleared, end processing
|
||||
}
|
||||
|
||||
// All cleanup tasks completed, show quit confirmation dialog
|
||||
handleSlashCommand('/quit-confirm');
|
||||
// All cleanup tasks completed, set flag for double-press to quit
|
||||
setPressedOnce(true);
|
||||
timerRef.current = setTimeout(() => {
|
||||
setPressedOnce(false);
|
||||
}, CTRL_EXIT_PROMPT_DURATION_MS);
|
||||
},
|
||||
[
|
||||
isAuthDialogOpen,
|
||||
@@ -1037,7 +1026,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
closeAnyOpenDialog,
|
||||
streamingState,
|
||||
cancelOngoingRequest,
|
||||
quitConfirmationRequest,
|
||||
buffer,
|
||||
],
|
||||
);
|
||||
@@ -1054,8 +1042,8 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// On first press: set flag, start timer, and call handleExit for cleanup/quit-confirm
|
||||
// On second press (within 500ms): handleExit sees flag and does fast quit
|
||||
// On first press: set flag, start timer, and call handleExit for cleanup
|
||||
// On second press (within timeout): handleExit sees flag and does fast quit
|
||||
if (!ctrlCPressedOnce) {
|
||||
setCtrlCPressedOnce(true);
|
||||
ctrlCTimerRef.current = setTimeout(() => {
|
||||
@@ -1196,7 +1184,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
!!confirmationRequest ||
|
||||
confirmUpdateExtensionRequests.length > 0 ||
|
||||
!!loopDetectionConfirmationRequest ||
|
||||
!!quitConfirmationRequest ||
|
||||
isThemeDialogOpen ||
|
||||
isSettingsDialogOpen ||
|
||||
isModelDialogOpen ||
|
||||
@@ -1245,7 +1232,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
confirmationRequest,
|
||||
confirmUpdateExtensionRequests,
|
||||
loopDetectionConfirmationRequest,
|
||||
quitConfirmationRequest,
|
||||
geminiMdFileCount,
|
||||
streamingState,
|
||||
initError,
|
||||
@@ -1337,7 +1323,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
confirmationRequest,
|
||||
confirmUpdateExtensionRequests,
|
||||
loopDetectionConfirmationRequest,
|
||||
quitConfirmationRequest,
|
||||
geminiMdFileCount,
|
||||
streamingState,
|
||||
initError,
|
||||
|
||||
@@ -8,35 +8,6 @@ import { formatDuration } from '../utils/formatters.js';
|
||||
import { CommandKind, type SlashCommand } from './types.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
export const quitConfirmCommand: SlashCommand = {
|
||||
name: 'quit-confirm',
|
||||
get description() {
|
||||
return t('Show quit confirmation dialog');
|
||||
},
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: (context) => {
|
||||
const now = Date.now();
|
||||
const { sessionStartTime } = context.session.stats;
|
||||
const wallDuration = now - sessionStartTime.getTime();
|
||||
|
||||
return {
|
||||
type: 'quit_confirmation',
|
||||
messages: [
|
||||
{
|
||||
type: 'user',
|
||||
text: `/quit-confirm`,
|
||||
id: now - 1,
|
||||
},
|
||||
{
|
||||
type: 'quit_confirmation',
|
||||
duration: formatDuration(wallDuration),
|
||||
id: now,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const quitCommand: SlashCommand = {
|
||||
name: 'quit',
|
||||
altNames: ['exit'],
|
||||
|
||||
@@ -100,12 +100,6 @@ export interface QuitActionReturn {
|
||||
messages: HistoryItem[];
|
||||
}
|
||||
|
||||
/** The return type for a command action that requests quit confirmation. */
|
||||
export interface QuitConfirmationActionReturn {
|
||||
type: 'quit_confirmation';
|
||||
messages: HistoryItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The return type for a command action that results in a simple message
|
||||
* being displayed to the user.
|
||||
@@ -182,7 +176,6 @@ export type SlashCommandActionReturn =
|
||||
| ToolActionReturn
|
||||
| MessageActionReturn
|
||||
| QuitActionReturn
|
||||
| QuitConfirmationActionReturn
|
||||
| OpenDialogActionReturn
|
||||
| LoadHistoryActionReturn
|
||||
| SubmitPromptActionReturn
|
||||
|
||||
@@ -36,10 +36,6 @@ import { WelcomeBackDialog } from './WelcomeBackDialog.js';
|
||||
import { ModelSwitchDialog } from './ModelSwitchDialog.js';
|
||||
import { AgentCreationWizard } from './subagents/create/AgentCreationWizard.js';
|
||||
import { AgentsManagerDialog } from './subagents/manage/AgentsManagerDialog.js';
|
||||
import {
|
||||
QuitConfirmationDialog,
|
||||
QuitChoice,
|
||||
} from './QuitConfirmationDialog.js';
|
||||
|
||||
interface DialogManagerProps {
|
||||
addItem: UseHistoryManagerReturn['addItem'];
|
||||
@@ -127,24 +123,6 @@ export const DialogManager = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (uiState.quitConfirmationRequest) {
|
||||
return (
|
||||
<QuitConfirmationDialog
|
||||
onSelect={(choice: QuitChoice) => {
|
||||
if (choice === QuitChoice.CANCEL) {
|
||||
uiState.quitConfirmationRequest?.onConfirm(false, 'cancel');
|
||||
} else if (choice === QuitChoice.QUIT) {
|
||||
uiState.quitConfirmationRequest?.onConfirm(true, 'quit');
|
||||
} else if (choice === QuitChoice.SUMMARY_AND_QUIT) {
|
||||
uiState.quitConfirmationRequest?.onConfirm(
|
||||
true,
|
||||
'summary_and_quit',
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (uiState.confirmationRequest) {
|
||||
return (
|
||||
<ConsentPrompt
|
||||
|
||||
@@ -108,9 +108,6 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
|
||||
{itemForDisplay.type === 'quit' && (
|
||||
<SessionSummaryDisplay duration={itemForDisplay.duration} />
|
||||
)}
|
||||
{itemForDisplay.type === 'quit_confirmation' && (
|
||||
<SessionSummaryDisplay duration={itemForDisplay.duration} />
|
||||
)}
|
||||
{itemForDisplay.type === 'tool_group' && (
|
||||
<ToolGroupMessage
|
||||
toolCalls={itemForDisplay.tools}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Box, Text } from 'ink';
|
||||
import type React from 'react';
|
||||
import { Colors } from '../colors.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
export enum QuitChoice {
|
||||
CANCEL = 'cancel',
|
||||
QUIT = 'quit',
|
||||
SUMMARY_AND_QUIT = 'summary_and_quit',
|
||||
}
|
||||
|
||||
interface QuitConfirmationDialogProps {
|
||||
onSelect: (choice: QuitChoice) => void;
|
||||
}
|
||||
|
||||
export const QuitConfirmationDialog: React.FC<QuitConfirmationDialogProps> = ({
|
||||
onSelect,
|
||||
}) => {
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (key.name === 'escape') {
|
||||
onSelect(QuitChoice.CANCEL);
|
||||
}
|
||||
},
|
||||
{ isActive: true },
|
||||
);
|
||||
|
||||
const options: Array<RadioSelectItem<QuitChoice>> = [
|
||||
{
|
||||
key: 'quit',
|
||||
label: t('Quit immediately (/quit)'),
|
||||
value: QuitChoice.QUIT,
|
||||
},
|
||||
{
|
||||
key: 'summary-and-quit',
|
||||
label: t('Generate summary and quit (/summary)'),
|
||||
value: QuitChoice.SUMMARY_AND_QUIT,
|
||||
},
|
||||
{
|
||||
key: 'cancel',
|
||||
label: t('Cancel (stay in application)'),
|
||||
value: QuitChoice.CANCEL,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor={Colors.AccentYellow}
|
||||
padding={1}
|
||||
width="100%"
|
||||
marginLeft={1}
|
||||
>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text>{t('What would you like to do before exiting?')}</Text>
|
||||
</Box>
|
||||
|
||||
<RadioButtonSelect items={options} onSelect={onSelect} isFocused />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -12,7 +12,6 @@ import type {
|
||||
ShellConfirmationRequest,
|
||||
ConfirmationRequest,
|
||||
LoopDetectionConfirmationRequest,
|
||||
QuitConfirmationRequest,
|
||||
HistoryItemWithoutId,
|
||||
StreamingState,
|
||||
} from '../types.js';
|
||||
@@ -69,7 +68,6 @@ export interface UIState {
|
||||
confirmationRequest: ConfirmationRequest | null;
|
||||
confirmUpdateExtensionRequests: ConfirmationRequest[];
|
||||
loopDetectionConfirmationRequest: LoopDetectionConfirmationRequest | null;
|
||||
quitConfirmationRequest: QuitConfirmationRequest | null;
|
||||
geminiMdFileCount: number;
|
||||
streamingState: StreamingState;
|
||||
initError: string | null;
|
||||
|
||||
@@ -918,7 +918,6 @@ describe('useSlashCommandProcessor', () => {
|
||||
vi.fn(), // toggleVimEnabled
|
||||
vi.fn(), // setIsProcessing
|
||||
vi.fn(), // setGeminiMdFileCount
|
||||
vi.fn(), // _showQuitConfirmation
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
IdeClient,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { formatDuration } from '../utils/formatters.js';
|
||||
import type {
|
||||
Message,
|
||||
HistoryItemWithoutId,
|
||||
@@ -53,7 +52,6 @@ function serializeHistoryItemForRecording(
|
||||
|
||||
const SLASH_COMMANDS_SKIP_RECORDING = new Set([
|
||||
'quit',
|
||||
'quit-confirm',
|
||||
'exit',
|
||||
'clear',
|
||||
'reset',
|
||||
@@ -75,7 +73,6 @@ interface SlashCommandProcessorActions {
|
||||
addConfirmUpdateExtensionRequest: (request: ConfirmationRequest) => void;
|
||||
openSubagentCreateDialog: () => void;
|
||||
openAgentsManagerDialog: () => void;
|
||||
_showQuitConfirmation: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,10 +112,6 @@ export const useSlashCommandProcessor = (
|
||||
prompt: React.ReactNode;
|
||||
onConfirm: (confirmed: boolean) => void;
|
||||
}>(null);
|
||||
const [quitConfirmationRequest, setQuitConfirmationRequest] =
|
||||
useState<null | {
|
||||
onConfirm: (shouldQuit: boolean, action?: string) => void;
|
||||
}>(null);
|
||||
|
||||
const [sessionShellAllowlist, setSessionShellAllowlist] = useState(
|
||||
new Set<string>(),
|
||||
@@ -174,11 +167,6 @@ export const useSlashCommandProcessor = (
|
||||
type: 'quit',
|
||||
duration: message.duration,
|
||||
};
|
||||
} else if (message.type === MessageType.QUIT_CONFIRMATION) {
|
||||
historyItemContent = {
|
||||
type: 'quit_confirmation',
|
||||
duration: message.duration,
|
||||
};
|
||||
} else if (message.type === MessageType.COMPRESSION) {
|
||||
historyItemContent = {
|
||||
type: 'compression',
|
||||
@@ -449,66 +437,6 @@ export const useSlashCommandProcessor = (
|
||||
});
|
||||
return { type: 'handled' };
|
||||
}
|
||||
case 'quit_confirmation':
|
||||
// Show quit confirmation dialog instead of immediately quitting
|
||||
setQuitConfirmationRequest({
|
||||
onConfirm: (shouldQuit: boolean, action?: string) => {
|
||||
setQuitConfirmationRequest(null);
|
||||
if (!shouldQuit) {
|
||||
// User cancelled the quit operation - do nothing
|
||||
return;
|
||||
}
|
||||
if (shouldQuit) {
|
||||
if (action === 'summary_and_quit') {
|
||||
// Generate summary and then quit
|
||||
handleSlashCommand('/summary')
|
||||
.then(() => {
|
||||
// Wait for user to see the summary result
|
||||
setTimeout(() => {
|
||||
handleSlashCommand('/quit');
|
||||
}, 1200);
|
||||
})
|
||||
.catch((error) => {
|
||||
// If summary fails, still quit but show error
|
||||
addItemWithRecording(
|
||||
{
|
||||
type: 'error',
|
||||
text: `Failed to generate summary before quit: ${
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: String(error)
|
||||
}`,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
// Give user time to see the error message
|
||||
setTimeout(() => {
|
||||
handleSlashCommand('/quit');
|
||||
}, 1000);
|
||||
});
|
||||
} else {
|
||||
// Just quit immediately - trigger the actual quit action
|
||||
const now = Date.now();
|
||||
const { sessionStartTime } = sessionStats;
|
||||
const wallDuration = now - sessionStartTime.getTime();
|
||||
|
||||
actions.quit([
|
||||
{
|
||||
type: 'user',
|
||||
text: `/quit`,
|
||||
id: now - 1,
|
||||
},
|
||||
{
|
||||
type: 'quit',
|
||||
duration: formatDuration(wallDuration),
|
||||
id: now,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
return { type: 'handled' };
|
||||
|
||||
case 'quit':
|
||||
actions.quit(result.messages);
|
||||
@@ -692,7 +620,6 @@ export const useSlashCommandProcessor = (
|
||||
setSessionShellAllowlist,
|
||||
setIsProcessing,
|
||||
setConfirmationRequest,
|
||||
sessionStats,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -703,6 +630,5 @@ export const useSlashCommandProcessor = (
|
||||
commandContext,
|
||||
shellConfirmationRequest,
|
||||
confirmationRequest,
|
||||
quitConfirmationRequest,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -44,11 +44,6 @@ export interface DialogCloseOptions {
|
||||
// Welcome back dialog
|
||||
showWelcomeBackDialog: boolean;
|
||||
handleWelcomeBackClose: () => void;
|
||||
|
||||
// Quit confirmation dialog
|
||||
quitConfirmationRequest: {
|
||||
onConfirm: (shouldQuit: boolean, action?: string) => void;
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,9 +91,6 @@ export function useDialogClose(options: DialogCloseOptions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: quitConfirmationRequest is NOT handled here anymore
|
||||
// It's handled specially in handleExit - ctrl+c in quit-confirm should exit immediately
|
||||
|
||||
// No dialog was open
|
||||
return false;
|
||||
}, [options]);
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { QuitChoice } from '../components/QuitConfirmationDialog.js';
|
||||
|
||||
export const useQuitConfirmation = () => {
|
||||
const [isQuitConfirmationOpen, setIsQuitConfirmationOpen] = useState(false);
|
||||
|
||||
const showQuitConfirmation = useCallback(() => {
|
||||
setIsQuitConfirmationOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleQuitConfirmationSelect = useCallback((choice: QuitChoice) => {
|
||||
setIsQuitConfirmationOpen(false);
|
||||
|
||||
if (choice === QuitChoice.CANCEL) {
|
||||
return { shouldQuit: false, action: 'cancel' };
|
||||
} else if (choice === QuitChoice.QUIT) {
|
||||
return { shouldQuit: true, action: 'quit' };
|
||||
} else if (choice === QuitChoice.SUMMARY_AND_QUIT) {
|
||||
return { shouldQuit: true, action: 'summary_and_quit' };
|
||||
}
|
||||
|
||||
// Default to cancel if unknown choice
|
||||
return { shouldQuit: false, action: 'cancel' };
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isQuitConfirmationOpen,
|
||||
showQuitConfirmation,
|
||||
handleQuitConfirmationSelect,
|
||||
};
|
||||
};
|
||||
@@ -161,11 +161,6 @@ export type HistoryItemQuit = HistoryItemBase & {
|
||||
duration: string;
|
||||
};
|
||||
|
||||
export type HistoryItemQuitConfirmation = HistoryItemBase & {
|
||||
type: 'quit_confirmation';
|
||||
duration: string;
|
||||
};
|
||||
|
||||
export type HistoryItemToolGroup = HistoryItemBase & {
|
||||
type: 'tool_group';
|
||||
tools: IndividualToolCallDisplay[];
|
||||
@@ -256,7 +251,6 @@ export type HistoryItemWithoutId =
|
||||
| HistoryItemModelStats
|
||||
| HistoryItemToolStats
|
||||
| HistoryItemQuit
|
||||
| HistoryItemQuitConfirmation
|
||||
| HistoryItemCompression
|
||||
| HistoryItemSummary
|
||||
| HistoryItemCompression
|
||||
@@ -278,7 +272,6 @@ export enum MessageType {
|
||||
MODEL_STATS = 'model_stats',
|
||||
TOOL_STATS = 'tool_stats',
|
||||
QUIT = 'quit',
|
||||
QUIT_CONFIRMATION = 'quit_confirmation',
|
||||
GEMINI = 'gemini',
|
||||
COMPRESSION = 'compression',
|
||||
SUMMARY = 'summary',
|
||||
@@ -342,12 +335,6 @@ export type Message =
|
||||
duration: string;
|
||||
content?: string;
|
||||
}
|
||||
| {
|
||||
type: MessageType.QUIT_CONFIRMATION;
|
||||
timestamp: Date;
|
||||
duration: string;
|
||||
content?: string;
|
||||
}
|
||||
| {
|
||||
type: MessageType.COMPRESSION;
|
||||
compression: CompressionProps;
|
||||
@@ -404,7 +391,3 @@ export interface ConfirmationRequest {
|
||||
export interface LoopDetectionConfirmationRequest {
|
||||
onComplete: (result: { userSelection: 'disable' | 'keep' }) => void;
|
||||
}
|
||||
|
||||
export interface QuitConfirmationRequest {
|
||||
onConfirm: (shouldQuit: boolean, action?: string) => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user