diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index e790aa80f..eb3f5d2a4 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -57,6 +57,7 @@ import { useAgentExecution, useAgentCapabilities, useMergeTransferHandlers, + useForkConversation, useSummarizeAndContinue, // Git useFileTreeManagement, @@ -1082,6 +1083,14 @@ function MaestroConsoleInner() { setActiveSessionId, }); + // Fork conversation hook - creates a new session from a point in conversation history + const handleForkConversation = useForkConversation( + sessions, + setSessions, + activeSessionId, + setActiveSessionId + ); + // Summarize & Continue hook for context compaction (non-blocking, per-tab) const { summarizeState, @@ -2277,6 +2286,7 @@ function MaestroConsoleInner() { handleMainPanelInputBlur, handleOpenPromptComposer, handleReplayMessage, + handleForkConversation, handleMainPanelFileClick, handleNavigateBack: handleFileTabNavigateBack, handleNavigateForward: handleFileTabNavigateForward, diff --git a/src/renderer/components/MainPanel/MainPanel.tsx b/src/renderer/components/MainPanel/MainPanel.tsx index 2142db5d9..a9eddfd7c 100644 --- a/src/renderer/components/MainPanel/MainPanel.tsx +++ b/src/renderer/components/MainPanel/MainPanel.tsx @@ -670,6 +670,7 @@ export const MainPanel = React.memo( onInputBlur={props.onInputBlur} onOpenPromptComposer={props.onOpenPromptComposer} onReplayMessage={props.onReplayMessage} + onForkConversation={props.onForkConversation} fileTree={props.fileTree} onFileClick={props.onFileClick} refreshFileTree={props.refreshFileTree} diff --git a/src/renderer/components/MainPanel/MainPanelContent.tsx b/src/renderer/components/MainPanel/MainPanelContent.tsx index 806d1dcca..236dcc38f 100644 --- a/src/renderer/components/MainPanel/MainPanelContent.tsx +++ b/src/renderer/components/MainPanel/MainPanelContent.tsx @@ -173,6 +173,7 @@ export interface MainPanelContentProps { onInputBlur?: () => void; onOpenPromptComposer?: () => void; onReplayMessage?: (text: string, images?: string[]) => void; + onForkConversation?: (logId: string) => void; fileTree?: FileNode[]; onFileClick?: (relativePath: string, options?: { openInNewTab?: boolean }) => void; refreshFileTree?: ( @@ -327,6 +328,7 @@ export const MainPanelContent = React.memo(function MainPanelContent(props: Main onInputBlur, onOpenPromptComposer, onReplayMessage, + onForkConversation, fileTree, onFileClick, refreshFileTree, @@ -547,6 +549,7 @@ export const MainPanelContent = React.memo(function MainPanelContent(props: Main markdownEditMode={chatRawTextMode} setMarkdownEditMode={useSettingsStore.getState().setChatRawTextMode} onReplayMessage={onReplayMessage} + onForkConversation={onForkConversation} fileTree={fileTree} cwd={ activeSession.cwd?.startsWith(activeSession.fullPath) diff --git a/src/renderer/components/MainPanel/types.ts b/src/renderer/components/MainPanel/types.ts index 9511476dd..c7e58001b 100644 --- a/src/renderer/components/MainPanel/types.ts +++ b/src/renderer/components/MainPanel/types.ts @@ -196,6 +196,7 @@ export interface MainPanelProps { onOpenPromptComposer?: () => void; // Replay a user message (AI mode) onReplayMessage?: (text: string, images?: string[]) => void; + onForkConversation?: (logId: string) => void; // File tree for linking file references in AI responses fileTree?: import('../../types/fileTree').FileNode[]; // Callback when a file link is clicked in AI response diff --git a/src/renderer/components/TerminalOutput.tsx b/src/renderer/components/TerminalOutput.tsx index 74c511c97..91185192f 100644 --- a/src/renderer/components/TerminalOutput.tsx +++ b/src/renderer/components/TerminalOutput.tsx @@ -13,6 +13,7 @@ import { Save, Share2, Hammer, + GitFork, } from 'lucide-react'; import type { Session, Theme, LogEntry, FocusArea, AgentError } from '../types'; import type { FileNode } from '../types/fileTree'; @@ -176,6 +177,8 @@ interface LogItemProps { // Publish to GitHub Gist (AI mode only, non-user messages, requires gh CLI) ghCliAvailable?: boolean; onPublishGist?: (text: string) => void; + // Fork conversation from this message (AI mode only, user and ai source messages) + onForkConversation?: (logId: string) => void; // Message alignment userMessageAlignment: 'left' | 'right'; } @@ -218,6 +221,7 @@ const LogItemComponent = memo( onSaveToFile, ghCliAvailable, onPublishGist, + onForkConversation, userMessageAlignment, }: LogItemProps) => { // Ref for the log item container - used for scroll-into-view on expand @@ -913,6 +917,17 @@ const LogItemComponent = memo( )} + {/* Fork conversation from this message - AI and user messages only */} + {(log.source === 'ai' || log.source === 'user') && isAIMode && onForkConversation && ( + + )} {/* Publish to GitHub Gist - only for AI responses when gh CLI available */} {log.source !== 'user' && isAIMode && ghCliAvailable && onPublishGist && (