From 217524125b8153d1f98f2fbcc19da6f6aa204983 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Mon, 22 Dec 2025 18:10:13 -0600 Subject: [PATCH 01/18] MAESTRO: Add supportsThinkingDisplay capability and thinking types (Phase 1) - Add supportsThinkingDisplay to AgentCapabilities interface - Set capability to true for claude-code, codex, and opencode agents - Set capability to false for terminal, gemini-cli, qwen3-coder, and aider - Add 'thinking' to LogEntry.source type for thinking content entries - Add showThinking field to AITab interface for per-tab toggle state - Update agent-capabilities test to include new capability --- src/__tests__/main/agent-capabilities.test.ts | 1 + src/main/agent-capabilities.ts | 11 +++++++++++ src/renderer/types/index.ts | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/__tests__/main/agent-capabilities.test.ts b/src/__tests__/main/agent-capabilities.test.ts index bf2a33a3..75c1a532 100644 --- a/src/__tests__/main/agent-capabilities.test.ts +++ b/src/__tests__/main/agent-capabilities.test.ts @@ -257,6 +257,7 @@ describe('agent-capabilities', () => { 'supportsResultMessages', 'supportsModelSelection', 'requiresPromptToStart', + 'supportsThinkingDisplay', ]; const defaultKeys = Object.keys(DEFAULT_CAPABILITIES); diff --git a/src/main/agent-capabilities.ts b/src/main/agent-capabilities.ts index e2a7fb93..96a9acee 100644 --- a/src/main/agent-capabilities.ts +++ b/src/main/agent-capabilities.ts @@ -60,6 +60,9 @@ export interface AgentCapabilities { /** Agent supports --input-format stream-json for image input via stdin */ supportsStreamJsonInput: boolean; + + /** Agent emits streaming thinking/reasoning content that can be displayed */ + supportsThinkingDisplay: boolean; } /** @@ -83,6 +86,7 @@ export const DEFAULT_CAPABILITIES: AgentCapabilities = { supportsResultMessages: false, supportsModelSelection: false, supportsStreamJsonInput: false, + supportsThinkingDisplay: false, }; /** @@ -118,6 +122,7 @@ export const AGENT_CAPABILITIES: Record = { supportsResultMessages: true, // "result" event type supportsModelSelection: false, // Model is configured via Anthropic account supportsStreamJsonInput: true, // --input-format stream-json for images via stdin + supportsThinkingDisplay: true, // Emits streaming assistant messages }, /** @@ -141,6 +146,7 @@ export const AGENT_CAPABILITIES: Record = { supportsResultMessages: false, supportsModelSelection: false, supportsStreamJsonInput: false, + supportsThinkingDisplay: false, // Terminal is not an AI agent }, /** @@ -167,6 +173,7 @@ export const AGENT_CAPABILITIES: Record = { supportsResultMessages: false, // All messages are agent_message type (no distinct result) - Verified supportsModelSelection: true, // -m, --model flag - Documented supportsStreamJsonInput: false, // Uses -i, --image flag instead + supportsThinkingDisplay: true, // Emits reasoning tokens (o3/o4-mini) }, /** @@ -192,6 +199,7 @@ export const AGENT_CAPABILITIES: Record = { supportsResultMessages: false, supportsModelSelection: false, // Not yet investigated supportsStreamJsonInput: false, + supportsThinkingDisplay: false, // Not yet investigated }, /** @@ -217,6 +225,7 @@ export const AGENT_CAPABILITIES: Record = { supportsResultMessages: false, supportsModelSelection: false, // Not yet investigated supportsStreamJsonInput: false, + supportsThinkingDisplay: false, // Not yet investigated }, /** @@ -243,6 +252,7 @@ export const AGENT_CAPABILITIES: Record = { supportsResultMessages: false, // Not yet investigated supportsModelSelection: true, // --model flag supportsStreamJsonInput: false, + supportsThinkingDisplay: false, // Not yet investigated }, /** @@ -269,6 +279,7 @@ export const AGENT_CAPABILITIES: Record = { supportsResultMessages: true, // step_finish with part.reason:"stop" - Verified supportsModelSelection: true, // --model provider/model (e.g., 'ollama/qwen3:8b') - Verified supportsStreamJsonInput: false, // Uses -f, --file flag instead + supportsThinkingDisplay: true, // Emits streaming text chunks }, }; diff --git a/src/renderer/types/index.ts b/src/renderer/types/index.ts index f8a7a6dc..2437de70 100644 --- a/src/renderer/types/index.ts +++ b/src/renderer/types/index.ts @@ -62,7 +62,7 @@ export interface FileArtifact { export interface LogEntry { id: string; timestamp: number; - source: 'stdout' | 'stderr' | 'system' | 'user' | 'ai' | 'error'; + source: 'stdout' | 'stderr' | 'system' | 'user' | 'ai' | 'error' | 'thinking'; text: string; interactive?: boolean; options?: string[]; @@ -282,6 +282,7 @@ export interface AITab { state: 'idle' | 'busy'; // Tab-level state for write-mode tracking readOnlyMode?: boolean; // When true, agent operates in plan/read-only mode saveToHistory?: boolean; // When true, synopsis is requested after each completion and saved to History + showThinking?: boolean; // When true, show streaming thinking/reasoning content in real-time awaitingSessionId?: boolean; // True when this tab sent a message and is awaiting its session ID thinkingStartTime?: number; // Timestamp when tab started thinking (for elapsed time display) scrollTop?: number; // Saved scroll position for this tab's output view @@ -468,6 +469,7 @@ export interface AgentCapabilities { supportsStreaming: boolean; supportsResultMessages: boolean; supportsModelSelection?: boolean; + supportsThinkingDisplay?: boolean; } export interface AgentConfig { From 58dcb73f220d3f8f37e903877f208bf52622540f Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Mon, 22 Dec 2025 18:21:02 -0600 Subject: [PATCH 02/18] MAESTRO: Add defaultShowThinking setting and wire up to tab creation (Phase 2) - Add defaultShowThinking state and setter to useSettings.ts - Add settings UI with Brain icon in SettingsModal.tsx - Add showThinking option to CreateTabOptions interface - Update createTab() to accept and apply showThinking - Wire up all createTab call sites to pass defaultShowThinking: - App.tsx (3 locations) - useMainKeyboardHandler.ts (Cmd+T new tab) - useRemoteIntegration.ts (remote new tab) - useAgentSessionManagement.ts (resume session) - Pass defaultShowThinking through hook dependencies --- src/renderer/App.tsx | 15 ++++++++++----- src/renderer/components/SettingsModal.tsx | 15 ++++++++++++++- src/renderer/hooks/useAgentSessionManagement.ts | 8 ++++++-- src/renderer/hooks/useMainKeyboardHandler.ts | 2 +- src/renderer/hooks/useRemoteIntegration.ts | 5 ++++- src/renderer/hooks/useSettings.ts | 16 ++++++++++++++++ src/renderer/utils/tabHelpers.ts | 7 +++++-- 7 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index e8af257d..036fa6fa 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -187,6 +187,7 @@ export default function MaestroConsole() { enterToSendAI, setEnterToSendAI, enterToSendTerminal, setEnterToSendTerminal, defaultSaveToHistory, setDefaultSaveToHistory, + defaultShowThinking, setDefaultShowThinking, leftSidebarWidth, setLeftSidebarWidth, rightPanelWidth, setRightPanelWidth, markdownEditMode, setMarkdownEditMode, @@ -2208,14 +2209,14 @@ export default function MaestroConsole() { // Create a new tab in the session to start fresh setSessions(prev => prev.map(s => { if (s.id !== sessionId) return s; - const result = createTab(s); + const result = createTab(s, { saveToHistory: defaultSaveToHistory, showThinking: defaultShowThinking }); if (!result) return s; return result.session; })); // Focus the input after creating new tab setTimeout(() => inputRef.current?.focus(), 0); - }, [sessions, handleClearAgentError]); + }, [sessions, handleClearAgentError, defaultSaveToHistory, defaultShowThinking]); // Handler to retry after error (recovery action) const handleRetryAfterError = useCallback((sessionId: string) => { @@ -2302,6 +2303,7 @@ export default function MaestroConsole() { setSessions, setActiveSessionId, defaultSaveToHistory, + defaultShowThinking, }); // Web broadcasting hook - handles external history change notifications @@ -2604,6 +2606,7 @@ export default function MaestroConsole() { setAgentSessionsOpen, rightPanelRef, defaultSaveToHistory, + defaultShowThinking, }); // Note: spawnBackgroundSynopsisRef and spawnAgentWithPromptRef are now updated in useAgentExecution hook @@ -6134,7 +6137,7 @@ export default function MaestroConsole() { processMonitorOpen, logViewerOpen, createGroupModalOpen, confirmModalOpen, renameInstanceModalOpen, renameGroupModalOpen, activeSession, previewFile, fileTreeFilter, fileTreeFilterOpen, gitDiffPreview, gitLogOpen, lightboxImage, hasOpenLayers, hasOpenModal, visibleSessions, sortedSessions, groups, - bookmarksCollapsed, leftSidebarOpen, editingSessionId, editingGroupId, markdownEditMode, defaultSaveToHistory, + bookmarksCollapsed, leftSidebarOpen, editingSessionId, editingGroupId, markdownEditMode, defaultSaveToHistory, defaultShowThinking, setLeftSidebarOpen, setRightPanelOpen, addNewSession, deleteSession, setQuickActionInitialMode, setQuickActionOpen, cycleSession, toggleInputMode, setShortcutsHelpOpen, setSettingsModalOpen, setSettingsTab, setActiveRightTab, handleSetActiveRightTab, setActiveFocus, setBookmarksCollapsed, setGroups, @@ -7600,7 +7603,7 @@ export default function MaestroConsole() { if (activeSession) { setSessions(prev => prev.map(s => { if (s.id !== activeSession.id) return s; - const result = createTab(s, { saveToHistory: defaultSaveToHistory }); + const result = createTab(s, { saveToHistory: defaultSaveToHistory, showThinking: defaultShowThinking }); if (!result) return s; return result.session; })); @@ -7800,7 +7803,7 @@ export default function MaestroConsole() { // Use functional setState to compute from fresh state (avoids stale closure issues) setSessions(prev => prev.map(s => { if (s.id !== activeSession.id) return s; - const result = createTab(s, { saveToHistory: defaultSaveToHistory }); + const result = createTab(s, { saveToHistory: defaultSaveToHistory, showThinking: defaultShowThinking }); if (!result) return s; return result.session; })); @@ -8388,6 +8391,8 @@ export default function MaestroConsole() { setEnterToSendTerminal={setEnterToSendTerminal} defaultSaveToHistory={defaultSaveToHistory} setDefaultSaveToHistory={setDefaultSaveToHistory} + defaultShowThinking={defaultShowThinking} + setDefaultShowThinking={setDefaultShowThinking} fontFamily={fontFamily} setFontFamily={setFontFamily} fontSize={fontSize} diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index 6acd9389..682ad0fe 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef, memo } from 'react'; -import { X, Key, Moon, Sun, Keyboard, Check, Terminal, Bell, Cpu, Settings, Palette, Sparkles, History, Download, Bug, Cloud, FolderSync, RotateCcw, Folder, ChevronDown, Plus, Trash2 } from 'lucide-react'; +import { X, Key, Moon, Sun, Keyboard, Check, Terminal, Bell, Cpu, Settings, Palette, Sparkles, History, Download, Bug, Cloud, FolderSync, RotateCcw, Folder, ChevronDown, Plus, Trash2, Brain } from 'lucide-react'; import type { Theme, ThemeColors, ThemeId, Shortcut, ShellInfo, CustomAICommand, LLMProvider } from '../types'; import { CustomThemeBuilder } from './CustomThemeBuilder'; import { useLayerStack } from '../contexts/LayerStackContext'; @@ -196,6 +196,8 @@ interface SettingsModalProps { setEnterToSendTerminal: (value: boolean) => void; defaultSaveToHistory: boolean; setDefaultSaveToHistory: (value: boolean) => void; + defaultShowThinking: boolean; + setDefaultShowThinking: (value: boolean) => void; osNotificationsEnabled: boolean; setOsNotificationsEnabled: (value: boolean) => void; audioFeedbackEnabled: boolean; @@ -1098,6 +1100,17 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro theme={theme} /> + {/* Default Thinking Toggle */} + + {/* Check for Updates on Startup */} ; /** Default value for saveToHistory on new tabs */ defaultSaveToHistory: boolean; + /** Default value for showThinking on new tabs */ + defaultShowThinking: boolean; } /** @@ -80,6 +82,7 @@ export function useAgentSessionManagement( setAgentSessionsOpen, rightPanelRef, defaultSaveToHistory, + defaultShowThinking, } = deps; // Refs for functions that need to be accessed from other callbacks @@ -228,7 +231,8 @@ export function useAgentSessionManagement( name, starred: isStarred, usageStats, - saveToHistory: defaultSaveToHistory + saveToHistory: defaultSaveToHistory, + showThinking: defaultShowThinking }); if (!result) return s; @@ -238,7 +242,7 @@ export function useAgentSessionManagement( } catch (error) { console.error('Failed to resume session:', error); } - }, [activeSession?.projectRoot, activeSession?.id, activeSession?.aiTabs, activeSession?.toolType, setSessions, setActiveAgentSessionId, defaultSaveToHistory]); + }, [activeSession?.projectRoot, activeSession?.id, activeSession?.aiTabs, activeSession?.toolType, setSessions, setActiveAgentSessionId, defaultSaveToHistory, defaultShowThinking]); // Update refs for slash command functions (so other handlers can access latest versions) addHistoryEntryRef.current = addHistoryEntry; diff --git a/src/renderer/hooks/useMainKeyboardHandler.ts b/src/renderer/hooks/useMainKeyboardHandler.ts index d0749f3b..856e8a49 100644 --- a/src/renderer/hooks/useMainKeyboardHandler.ts +++ b/src/renderer/hooks/useMainKeyboardHandler.ts @@ -344,7 +344,7 @@ export function useMainKeyboardHandler(): UseMainKeyboardHandlerReturn { } if (ctx.isTabShortcut(e, 'newTab')) { e.preventDefault(); - const result = ctx.createTab(ctx.activeSession, { saveToHistory: ctx.defaultSaveToHistory }); + const result = ctx.createTab(ctx.activeSession, { saveToHistory: ctx.defaultSaveToHistory, showThinking: ctx.defaultShowThinking }); if (result) { ctx.setSessions((prev: Session[]) => prev.map((s: Session) => s.id === ctx.activeSession!.id ? result.session : s diff --git a/src/renderer/hooks/useRemoteIntegration.ts b/src/renderer/hooks/useRemoteIntegration.ts index f7ee68cb..a4f819c4 100644 --- a/src/renderer/hooks/useRemoteIntegration.ts +++ b/src/renderer/hooks/useRemoteIntegration.ts @@ -21,6 +21,8 @@ export interface UseRemoteIntegrationDeps { setActiveSessionId: (id: string) => void; /** Default value for saveToHistory on new tabs */ defaultSaveToHistory: boolean; + /** Default value for showThinking on new tabs */ + defaultShowThinking: boolean; } /** @@ -57,6 +59,7 @@ export function useRemoteIntegration(deps: UseRemoteIntegrationDeps): UseRemoteI setSessions, setActiveSessionId, defaultSaveToHistory, + defaultShowThinking, } = deps; // Broadcast active session change to web clients @@ -227,7 +230,7 @@ export function useRemoteIntegration(deps: UseRemoteIntegrationDeps): UseRemoteI if (s.id !== sessionId) return s; // Use createTab helper - const result = createTab(s, { saveToHistory: defaultSaveToHistory }); + const result = createTab(s, { saveToHistory: defaultSaveToHistory, showThinking: defaultShowThinking }); if (!result) return s; newTabId = result.tab.id; return result.session; diff --git a/src/renderer/hooks/useSettings.ts b/src/renderer/hooks/useSettings.ts index 70fa6f91..296c0669 100644 --- a/src/renderer/hooks/useSettings.ts +++ b/src/renderer/hooks/useSettings.ts @@ -115,6 +115,10 @@ export interface UseSettingsReturn { setEnterToSendTerminal: (value: boolean) => void; defaultSaveToHistory: boolean; setDefaultSaveToHistory: (value: boolean) => void; + + // Default thinking toggle + defaultShowThinking: boolean; + setDefaultShowThinking: (value: boolean) => void; leftSidebarWidth: number; rightPanelWidth: number; markdownEditMode: boolean; @@ -249,6 +253,7 @@ export function useSettings(): UseSettingsReturn { const [enterToSendAI, setEnterToSendAIState] = useState(false); // AI mode defaults to Command+Enter const [enterToSendTerminal, setEnterToSendTerminalState] = useState(true); // Terminal defaults to Enter const [defaultSaveToHistory, setDefaultSaveToHistoryState] = useState(true); // History toggle defaults to on + const [defaultShowThinking, setDefaultShowThinkingState] = useState(false); // Thinking toggle defaults to off const [leftSidebarWidth, setLeftSidebarWidthState] = useState(256); const [rightPanelWidth, setRightPanelWidthState] = useState(384); const [markdownEditMode, setMarkdownEditModeState] = useState(false); @@ -390,6 +395,11 @@ export function useSettings(): UseSettingsReturn { window.maestro.settings.set('defaultSaveToHistory', value); }, []); + const setDefaultShowThinking = useCallback((value: boolean) => { + setDefaultShowThinkingState(value); + window.maestro.settings.set('defaultShowThinking', value); + }, []); + const setLeftSidebarWidth = useCallback((width: number) => { const clampedWidth = Math.max(256, Math.min(600, width)); setLeftSidebarWidthState(clampedWidth); @@ -865,6 +875,7 @@ export function useSettings(): UseSettingsReturn { const savedEnterToSendAI = await window.maestro.settings.get('enterToSendAI'); const savedEnterToSendTerminal = await window.maestro.settings.get('enterToSendTerminal'); const savedDefaultSaveToHistory = await window.maestro.settings.get('defaultSaveToHistory'); + const savedDefaultShowThinking = await window.maestro.settings.get('defaultShowThinking'); const savedLlmProvider = await window.maestro.settings.get('llmProvider'); const savedModelSlug = await window.maestro.settings.get('modelSlug'); @@ -909,6 +920,7 @@ export function useSettings(): UseSettingsReturn { if (savedEnterToSendAI !== undefined) setEnterToSendAIState(savedEnterToSendAI as boolean); if (savedEnterToSendTerminal !== undefined) setEnterToSendTerminalState(savedEnterToSendTerminal as boolean); if (savedDefaultSaveToHistory !== undefined) setDefaultSaveToHistoryState(savedDefaultSaveToHistory as boolean); + if (savedDefaultShowThinking !== undefined) setDefaultShowThinkingState(savedDefaultShowThinking as boolean); if (savedLlmProvider !== undefined) setLlmProviderState(savedLlmProvider as LLMProvider); if (savedModelSlug !== undefined) setModelSlugState(savedModelSlug as string); @@ -1101,6 +1113,8 @@ export function useSettings(): UseSettingsReturn { setEnterToSendTerminal, defaultSaveToHistory, setDefaultSaveToHistory, + defaultShowThinking, + setDefaultShowThinking, leftSidebarWidth, rightPanelWidth, markdownEditMode, @@ -1186,6 +1200,7 @@ export function useSettings(): UseSettingsReturn { enterToSendAI, enterToSendTerminal, defaultSaveToHistory, + defaultShowThinking, leftSidebarWidth, rightPanelWidth, markdownEditMode, @@ -1226,6 +1241,7 @@ export function useSettings(): UseSettingsReturn { setEnterToSendAI, setEnterToSendTerminal, setDefaultSaveToHistory, + setDefaultShowThinking, setLeftSidebarWidth, setRightPanelWidth, setMarkdownEditMode, diff --git a/src/renderer/utils/tabHelpers.ts b/src/renderer/utils/tabHelpers.ts index c892d85e..60a9dc12 100644 --- a/src/renderer/utils/tabHelpers.ts +++ b/src/renderer/utils/tabHelpers.ts @@ -91,6 +91,7 @@ export interface CreateTabOptions { starred?: boolean; // Whether session is starred usageStats?: UsageStats; // Token usage stats saveToHistory?: boolean; // Whether to save synopsis to history after completions + showThinking?: boolean; // Whether to show thinking/streaming content for this tab } /** @@ -133,7 +134,8 @@ export function createTab(session: Session, options: CreateTabOptions = {}): Cre name = null, starred = false, usageStats, - saveToHistory = true + saveToHistory = true, + showThinking = false } = options; // Create the new tab with default values @@ -148,7 +150,8 @@ export function createTab(session: Session, options: CreateTabOptions = {}): Cre usageStats, createdAt: Date.now(), state: 'idle', - saveToHistory + saveToHistory, + showThinking }; // Update the session with the new tab added and set as active From e3f8beae45ec6ba12fd1f4af1c01a62a1abebdd8 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Mon, 22 Dec 2025 18:25:23 -0600 Subject: [PATCH 03/18] MAESTRO: Add thinking-chunk IPC event for streaming AI reasoning (Phase 3) Implement the IPC infrastructure for Show Thinking feature: - Emit 'thinking-chunk' event in process-manager.ts when partial text events arrive - Forward thinking-chunk events to renderer via index.ts - Add onThinkingChunk handler to preload.ts process API - Add TypeScript interface declaration for onThinkingChunk This enables the renderer to receive real-time streaming content from AI agents (Claude Code, OpenCode, Codex) for display when the tab's showThinking setting is enabled. The renderer decides whether to display based on per-tab settings. --- src/main/index.ts | 7 +++++++ src/main/preload.ts | 9 +++++++++ src/main/process-manager.ts | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/src/main/index.ts b/src/main/index.ts index 2e22e1d1..a663b950 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -2234,6 +2234,13 @@ function setupProcessListeners() { mainWindow?.webContents.send('process:slash-commands', sessionId, slashCommands); }); + // Handle thinking/streaming content chunks from AI agents + // Emitted when agents produce partial text events (isPartial: true) + // Renderer decides whether to display based on tab's showThinking setting + processManager.on('thinking-chunk', (sessionId: string, content: string) => { + mainWindow?.webContents.send('process:thinking-chunk', sessionId, content); + }); + // Handle stderr separately from runCommand (for clean command execution) processManager.on('stderr', (sessionId: string, data: string) => { mainWindow?.webContents.send('process:stderr', sessionId, data); diff --git a/src/main/preload.ts b/src/main/preload.ts index 20dfdbfb..76ade49a 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -130,6 +130,14 @@ contextBridge.exposeInMainWorld('maestro', { ipcRenderer.on('process:slash-commands', handler); return () => ipcRenderer.removeListener('process:slash-commands', handler); }, + // Thinking/streaming content chunks from AI agents + // Emitted when agents produce partial text events (isPartial: true) + // Renderer decides whether to display based on tab's showThinking setting + onThinkingChunk: (callback: (sessionId: string, content: string) => void) => { + const handler = (_: any, sessionId: string, content: string) => callback(sessionId, content); + ipcRenderer.on('process:thinking-chunk', handler); + return () => ipcRenderer.removeListener('process:thinking-chunk', handler); + }, // Remote command execution from web interface // This allows web commands to go through the same code path as desktop commands // inputMode is optional - if provided, renderer should use it instead of session state @@ -1181,6 +1189,7 @@ export interface MaestroAPI { onExit: (callback: (sessionId: string, code: number) => void) => () => void; onSessionId: (callback: (sessionId: string, agentSessionId: string) => void) => () => void; onSlashCommands: (callback: (sessionId: string, slashCommands: string[]) => void) => () => void; + onThinkingChunk: (callback: (sessionId: string, content: string) => void) => () => void; onRemoteCommand: (callback: (sessionId: string, command: string) => void) => () => void; onRemoteSwitchMode: (callback: (sessionId: string, mode: 'ai' | 'terminal') => void) => () => void; onRemoteInterrupt: (callback: (sessionId: string) => void) => () => void; diff --git a/src/main/process-manager.ts b/src/main/process-manager.ts index fa8eeda2..53ac7299 100644 --- a/src/main/process-manager.ts +++ b/src/main/process-manager.ts @@ -743,6 +743,10 @@ export class ProcessManager extends EventEmitter { // Accumulate text from partial streaming events (OpenCode text messages) // Skip error events - they're handled separately by detectErrorFromLine if (event.type === 'text' && event.isPartial && event.text) { + // Emit thinking chunk for real-time display (let renderer decide to display based on tab setting) + this.emit('thinking-chunk', sessionId, event.text); + + // Existing: accumulate for result fallback managedProcess.streamedText = (managedProcess.streamedText || '') + event.text; } From 76912f4f6b2a6475edd38fae4f2f92abd638fcf5 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Mon, 22 Dec 2025 18:32:45 -0600 Subject: [PATCH 04/18] MAESTRO: Add Show Thinking toggle pill to InputArea (Phase 4) - Import Brain icon from lucide-react for the toggle button - Add tabShowThinking, onToggleTabShowThinking, and supportsThinking props to InputAreaProps interface - Add toggle pill after Read-Only button using accentText color styling - Add handler in App.tsx for toggling tab.showThinking state - Pass props through MainPanel to InputArea - Add supportsThinkingDisplay to renderer's AgentCapabilities interface to match main process capability definition --- src/renderer/App.tsx | 14 +++++++++++ src/renderer/components/InputArea.tsx | 27 ++++++++++++++++++++-- src/renderer/components/MainPanel.tsx | 4 ++++ src/renderer/hooks/useAgentCapabilities.ts | 4 ++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 036fa6fa..cc685415 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -7930,6 +7930,20 @@ export default function MaestroConsole() { }; })); }} + onToggleTabShowThinking={() => { + if (!activeSession) return; + const activeTab = getActiveTab(activeSession); + if (!activeTab) return; + setSessions(prev => prev.map(s => { + if (s.id !== activeSession.id) return s; + return { + ...s, + aiTabs: s.aiTabs.map(tab => + tab.id === activeTab.id ? { ...tab, showThinking: !tab.showThinking } : tab + ) + }; + })); + }} onScrollPositionChange={(scrollTop: number) => { if (!activeSession) return; // Save scroll position for the current view (AI tab or terminal) diff --git a/src/renderer/components/InputArea.tsx b/src/renderer/components/InputArea.tsx index 027ec4c4..de9280eb 100644 --- a/src/renderer/components/InputArea.tsx +++ b/src/renderer/components/InputArea.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo } from 'react'; -import { Terminal, Cpu, Keyboard, ImageIcon, X, ArrowUp, Eye, History, File, Folder, GitBranch, Tag, PenLine } from 'lucide-react'; +import { Terminal, Cpu, Keyboard, ImageIcon, X, ArrowUp, Eye, History, File, Folder, GitBranch, Tag, PenLine, Brain } from 'lucide-react'; import type { Session, Theme, BatchRunState } from '../types'; import type { TabCompletionSuggestion, TabCompletionFilter } from '../hooks/useTabCompletion'; import { ThinkingStatusPill } from './ThinkingStatusPill'; @@ -83,6 +83,10 @@ interface InputAreaProps { onOpenPromptComposer?: () => void; // Flash notification callback showFlashNotification?: (message: string) => void; + // Show Thinking toggle (per-tab) + tabShowThinking?: boolean; + onToggleTabShowThinking?: () => void; + supportsThinking?: boolean; // From agent capabilities } export const InputArea = React.memo(function InputArea(props: InputAreaProps) { @@ -110,7 +114,8 @@ export const InputArea = React.memo(function InputArea(props: InputAreaProps) { tabReadOnlyMode = false, onToggleTabReadOnlyMode, tabSaveToHistory = false, onToggleTabSaveToHistory, onOpenPromptComposer, - showFlashNotification + showFlashNotification, + tabShowThinking = false, onToggleTabShowThinking, supportsThinking = false } = props; // Get agent capabilities for conditional feature rendering @@ -730,6 +735,24 @@ export const InputArea = React.memo(function InputArea(props: InputAreaProps) { Read-only )} + {/* Show Thinking toggle - AI mode only, for agents that support it */} + {session.inputMode === 'ai' && supportsThinking && onToggleTabShowThinking && ( + + )}