Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
useAgentExecution,
useAgentCapabilities,
useMergeTransferHandlers,
useForkConversation,
useSummarizeAndContinue,
// Git
useFileTreeManagement,
Expand Down Expand Up @@ -1077,6 +1078,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,
Expand Down Expand Up @@ -2254,6 +2263,7 @@ function MaestroConsoleInner() {
handleMainPanelInputBlur,
handleOpenPromptComposer,
handleReplayMessage,
handleForkConversation,
handleMainPanelFileClick,
handleNavigateBack: handleFileTabNavigateBack,
handleNavigateForward: handleFileTabNavigateForward,
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/MainPanel/MainPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,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}
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/components/MainPanel/MainPanelContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,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?: (
Expand Down Expand Up @@ -319,6 +320,7 @@ export const MainPanelContent = React.memo(function MainPanelContent(props: Main
onInputBlur,
onOpenPromptComposer,
onReplayMessage,
onForkConversation,
fileTree,
onFileClick,
refreshFileTree,
Expand Down Expand Up @@ -531,6 +533,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)
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/MainPanel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,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
Expand Down
21 changes: 20 additions & 1 deletion src/renderer/components/TerminalOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Save,
Share2,
Zap,
GitFork,
} from 'lucide-react';
import type { Session, Theme, LogEntry, FocusArea, AgentError } from '../types';
import type { FileNode } from '../types/fileTree';
Expand Down Expand Up @@ -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';
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -913,6 +917,17 @@ const LogItemComponent = memo(
<Save className="w-3.5 h-3.5" />
</button>
)}
{/* Fork conversation from this message - AI and user messages only */}
{(log.source === 'ai' || log.source === 'user') && isAIMode && onForkConversation && (
<button
onClick={() => onForkConversation(log.id)}
className="p-1.5 rounded opacity-0 group-hover:opacity-50 hover:!opacity-100"
style={{ color: theme.colors.textDim }}
title="Fork conversation from here"
>
<GitFork className="w-3.5 h-3.5" />
</button>
)}
{/* Publish to GitHub Gist - only for AI responses when gh CLI available */}
{log.source !== 'user' && isAIMode && ghCliAvailable && onPublishGist && (
<button
Expand Down Expand Up @@ -1023,7 +1038,8 @@ const LogItemComponent = memo(
prevProps.markdownEditMode === nextProps.markdownEditMode &&
prevProps.fontFamily === nextProps.fontFamily &&
prevProps.userMessageAlignment === nextProps.userMessageAlignment &&
prevProps.ghCliAvailable === nextProps.ghCliAvailable
prevProps.ghCliAvailable === nextProps.ghCliAvailable &&
prevProps.onForkConversation === nextProps.onForkConversation
);
}
);
Expand Down Expand Up @@ -1057,6 +1073,7 @@ interface TerminalOutputProps {
markdownEditMode: boolean; // Whether to show raw markdown or rendered markdown for AI responses
setMarkdownEditMode: (value: boolean) => void; // Toggle markdown mode
onReplayMessage?: (text: string, images?: string[]) => void; // Replay a user message
onForkConversation?: (logId: string) => void; // Fork conversation from a specific message
fileTree?: FileNode[]; // File tree for linking file references
cwd?: string; // Current working directory for proximity-based matching
projectRoot?: string; // Project root absolute path for converting absolute paths to relative
Expand Down Expand Up @@ -1102,6 +1119,7 @@ export const TerminalOutput = memo(
markdownEditMode,
setMarkdownEditMode,
onReplayMessage,
onForkConversation,
fileTree,
cwd,
projectRoot,
Expand Down Expand Up @@ -1780,6 +1798,7 @@ export const TerminalOutput = memo(
markdownEditMode={markdownEditMode}
onToggleMarkdownEditMode={toggleMarkdownEditMode}
onReplayMessage={onReplayMessage}
onForkConversation={onForkConversation}
fileTree={fileTree}
cwd={cwd}
projectRoot={projectRoot}
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/hooks/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ export type {
UseMergeTransferHandlersReturn,
} from './useMergeTransferHandlers';

// Fork conversation (create new session from conversation history)
export { useForkConversation } from './useForkConversation';

// Agent IPC listeners (process event routing)
export { useAgentListeners, getErrorTitleForType } from './useAgentListeners';
export type { UseAgentListenersDeps } from './useAgentListeners';
Expand Down
Loading