diff --git a/packages/cli/src/services/BuiltinCommandLoader.ts b/packages/cli/src/services/BuiltinCommandLoader.ts index d3877a8a..c9fc5801 100644 --- a/packages/cli/src/services/BuiltinCommandLoader.ts +++ b/packages/cli/src/services/BuiltinCommandLoader.ts @@ -29,6 +29,7 @@ import { modelCommand } from '../ui/commands/modelCommand.js'; import { permissionsCommand } from '../ui/commands/permissionsCommand.js'; import { quitCommand } from '../ui/commands/quitCommand.js'; import { restoreCommand } from '../ui/commands/restoreCommand.js'; +import { resumeCommand } from '../ui/commands/resumeCommand.js'; import { settingsCommand } from '../ui/commands/settingsCommand.js'; import { statsCommand } from '../ui/commands/statsCommand.js'; import { summaryCommand } from '../ui/commands/summaryCommand.js'; @@ -76,6 +77,7 @@ export class BuiltinCommandLoader implements ICommandLoader { ...(this.config?.getFolderTrust() ? [permissionsCommand] : []), quitCommand, restoreCommand(this.config), + resumeCommand, statsCommand, summaryCommand, themeCommand, diff --git a/packages/cli/src/ui/commands/resumeCommand.ts b/packages/cli/src/ui/commands/resumeCommand.ts new file mode 100644 index 00000000..96aac2c0 --- /dev/null +++ b/packages/cli/src/ui/commands/resumeCommand.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2025 Qwen Code + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { + SlashCommand, + SlashCommandActionReturn, + CommandContext, +} from './types.js'; +import { CommandKind } from './types.js'; +import { t } from '../../i18n/index.js'; +import { showResumeSessionPicker } from '../components/ResumeSessionPicker.js'; +import { + SessionService, + buildApiHistoryFromConversation, + replayUiTelemetryFromConversation, + uiTelemetryService, +} from '@qwen-code/qwen-code-core'; +import { buildResumedHistoryItems } from '../utils/resumeHistoryUtils.js'; + +export const resumeCommand: SlashCommand = { + name: 'resume', + kind: CommandKind.BUILT_IN, + get description() { + return t('Resume a previous session'); + }, + action: async ( + context: CommandContext, + ): Promise => { + const { config } = context.services; + + if (!config) { + return { + type: 'message', + messageType: 'error', + content: 'Config not available', + }; + } + + // Show the session picker + const cwd = config.getTargetDir(); + const selectedSessionId = await showResumeSessionPicker(cwd); + + if (!selectedSessionId) { + // User cancelled + return; + } + + // Load the session data + const sessionService = new SessionService(cwd); + const sessionData = await sessionService.loadSession(selectedSessionId); + + if (!sessionData) { + return { + type: 'message', + messageType: 'error', + content: `Could not load session: ${selectedSessionId}`, + }; + } + + // Reset and replay UI telemetry to restore metrics + uiTelemetryService.reset(); + replayUiTelemetryFromConversation(sessionData.conversation); + + // Build UI history items using existing utility + const uiHistoryWithIds = buildResumedHistoryItems(sessionData, config); + // Strip IDs for LoadHistoryActionReturn (IDs are re-assigned by loadHistory) + const uiHistory = uiHistoryWithIds.map(({ id: _id, ...rest }) => rest); + + // Build API history for the LLM client + const clientHistory = buildApiHistoryFromConversation( + sessionData.conversation, + ); + + // Update session in config and context + config.startNewSession(selectedSessionId); + if (context.session.startNewSession) { + context.session.startNewSession(selectedSessionId); + } + + return { + type: 'load_history', + history: uiHistory, + clientHistory, + }; + }, +};