From 4bd52cf3ed19acbb9c041f344df7b5f9632e5c48 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 5 Dec 2025 22:53:51 +0100 Subject: [PATCH] feat(coding-agent): add /resume command to switch sessions mid-conversation - Opens interactive session selector - Properly aborts in-flight agent turns before switching - Restores model and thinking level from resumed session - Clears UI state (queued messages, pending tools, etc.) closes #117 --- packages/coding-agent/src/tui/tui-renderer.ts | 80 ++++++++++++++----- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/packages/coding-agent/src/tui/tui-renderer.ts b/packages/coding-agent/src/tui/tui-renderer.ts index 6254ec45..bd21490d 100644 --- a/packages/coding-agent/src/tui/tui-renderer.ts +++ b/packages/coding-agent/src/tui/tui-renderer.ts @@ -1489,26 +1489,9 @@ export class TuiRenderer { // Create session selector this.sessionSelector = new SessionSelectorComponent( this.sessionManager, - (sessionPath) => { - // Set the selected session as active - this.sessionManager.setSessionFile(sessionPath); - - // Reload the session - const loaded = loadSessionFromEntries(this.sessionManager.loadEntries()); - this.agent.replaceMessages(loaded.messages); - - // Clear and re-render the chat - this.chatContainer.clear(); - this.isFirstUserMessage = true; - this.renderInitialMessages(this.agent.state); - - // Show confirmation message - this.chatContainer.addChild(new Spacer(1)); - this.chatContainer.addChild(new Text(theme.fg("dim", "Resumed session"), 1, 0)); - - // Hide selector and show editor again + async (sessionPath) => { this.hideSessionSelector(); - this.ui.requestRender(); + await this.handleResumeSession(sessionPath); }, () => { // Just hide the selector @@ -1524,6 +1507,65 @@ export class TuiRenderer { this.ui.requestRender(); } + private async handleResumeSession(sessionPath: string): Promise { + // Unsubscribe first to prevent processing events during transition + this.unsubscribe?.(); + + // Abort and wait for completion + this.agent.abort(); + await this.agent.waitForIdle(); + + // Stop loading animation + if (this.loadingAnimation) { + this.loadingAnimation.stop(); + this.loadingAnimation = null; + } + this.statusContainer.clear(); + + // Clear UI state + this.queuedMessages = []; + this.pendingMessagesContainer.clear(); + this.streamingComponent = null; + this.pendingTools.clear(); + + // Set the selected session as active + this.sessionManager.setSessionFile(sessionPath); + + // Reload the session + const loaded = loadSessionFromEntries(this.sessionManager.loadEntries()); + this.agent.replaceMessages(loaded.messages); + + // Restore model if saved in session + const savedModel = this.sessionManager.loadModel(); + if (savedModel) { + const availableModels = (await getAvailableModels()).models; + const match = availableModels.find((m) => m.provider === savedModel.provider && m.id === savedModel.modelId); + if (match) { + this.agent.setModel(match); + } + } + + // Restore thinking level if saved in session + const savedThinking = this.sessionManager.loadThinkingLevel(); + if (savedThinking) { + this.agent.setThinkingLevel(savedThinking as ThinkingLevel); + } + + // Resubscribe to agent + this.subscribeToAgent(); + + // Clear and re-render the chat + this.chatContainer.clear(); + this.isFirstUserMessage = true; + this.renderInitialMessages(this.agent.state); + + // Show confirmation message + this.chatContainer.addChild(new Spacer(1)); + this.chatContainer.addChild(new Text(theme.fg("dim", "Resumed session"), 1, 0)); + + this.ui.requestRender(); + } + private hideSessionSelector(): void { // Replace selector with editor in the container this.editorContainer.clear();