From 3c79183f9a189c7883eb15dfb18ceaba1093efab Mon Sep 17 00:00:00 2001 From: Matt Cowger Date: Mon, 6 Jul 2026 01:42:41 +0000 Subject: [PATCH] feat(app): add auto-expand reasoning setting --- packages/app/src/agent-stream/view.tsx | 6 +++- packages/app/src/components/message.tsx | 29 ++++++++++++------- packages/app/src/hooks/use-settings/index.ts | 3 ++ .../app/src/hooks/use-settings/storage.ts | 5 ++++ packages/app/src/i18n/resources/ar.ts | 4 +++ packages/app/src/i18n/resources/en.ts | 4 +++ packages/app/src/i18n/resources/es.ts | 5 ++++ packages/app/src/i18n/resources/fr.ts | 4 +++ packages/app/src/i18n/resources/ja.ts | 4 +++ packages/app/src/i18n/resources/pt-BR.ts | 5 ++++ packages/app/src/i18n/resources/ru.ts | 5 ++++ packages/app/src/i18n/resources/zh-CN.ts | 4 +++ packages/app/src/screens/settings-screen.tsx | 25 ++++++++++++++++ 13 files changed, 91 insertions(+), 12 deletions(-) diff --git a/packages/app/src/agent-stream/view.tsx b/packages/app/src/agent-stream/view.tsx index a73a17358..7d500ea71 100644 --- a/packages/app/src/agent-stream/view.tsx +++ b/packages/app/src/agent-stream/view.tsx @@ -52,6 +52,7 @@ import type { AgentScreenAgent } from "@/hooks/use-agent-screen-state-machine"; import { useSessionStore } from "@/stores/session-store"; import { useFileExplorerActions } from "@/hooks/use-file-explorer-actions"; import { useLoadOlderAgentHistory } from "@/hooks/use-load-older-agent-history"; +import { useSettings } from "@/hooks/use-settings"; import type { ToastApi } from "@/components/toast-host"; import type { DaemonClient } from "@getpaseo/client/internal/daemon-client"; import { ToolCallDetailsContent } from "@/components/tool-call-details"; @@ -318,6 +319,7 @@ const AgentStreamViewComponent = forwardRef(null); const isMobile = useIsCompactFormFactor(); const streamRenderStrategy = useMemo( @@ -633,10 +635,12 @@ const AgentStreamViewComponent = forwardRef ); }, - [setInlineDetailsExpanded], + [setInlineDetailsExpanded, settings.autoExpandReasoning], ); const renderToolCallItem = useCallback( diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index 6a8cf8922..6cdef2edd 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -3038,6 +3038,8 @@ interface ToolCallProps { onInlineDetailsHoverChange?: (hovered: boolean) => void; onInlineDetailsExpandedChange?: (expanded: boolean) => void; onOpenFilePath?: (filePath: string) => void; + defaultExpanded?: boolean; + forceInline?: boolean; } export const ToolCall = memo(function ToolCall({ @@ -3054,11 +3056,14 @@ export const ToolCall = memo(function ToolCall({ onInlineDetailsHoverChange, onInlineDetailsExpandedChange, onOpenFilePath, + defaultExpanded, + forceInline = false, }: ToolCallProps) { const { openToolCall } = useToolCallSheet(); - const [isExpanded, setIsExpanded] = useState(false); + const [isExpanded, setIsExpanded] = useState(defaultExpanded ?? false); const isMobile = useIsCompactFormFactor(); + const shouldRenderInline = !isMobile || forceInline; const effectiveDetail = useMemo(() => { if (detail) { @@ -3096,7 +3101,7 @@ export const ToolCall = memo(function ToolCall({ }, [presentation.openFilePath, onOpenFilePath]); const handleToggle = useCallback(() => { - if (isMobile) { + if (!shouldRenderInline) { openToolCall({ displayName: presentation.displayName, summary: presentation.summary, @@ -3109,7 +3114,7 @@ export const ToolCall = memo(function ToolCall({ setIsExpanded((prev) => !prev); } }, [ - isMobile, + shouldRenderInline, openToolCall, presentation.displayName, presentation.summary, @@ -3120,22 +3125,22 @@ export const ToolCall = memo(function ToolCall({ ]); useEffect(() => { - if (!onInlineDetailsHoverChange || isMobile || isExpanded) { + if (!onInlineDetailsHoverChange || !shouldRenderInline || isExpanded) { return; } onInlineDetailsHoverChange(false); - }, [isExpanded, isMobile, onInlineDetailsHoverChange]); + }, [isExpanded, shouldRenderInline, onInlineDetailsHoverChange]); useEffect(() => { if (!onInlineDetailsExpandedChange) { return; } - if (isMobile) { + if (!shouldRenderInline) { onInlineDetailsExpandedChange(false); return; } onInlineDetailsExpandedChange(isExpanded); - }, [isExpanded, isMobile, onInlineDetailsExpandedChange]); + }, [isExpanded, shouldRenderInline, onInlineDetailsExpandedChange]); useEffect(() => { if (!onInlineDetailsExpandedChange) { @@ -3148,7 +3153,7 @@ export const ToolCall = memo(function ToolCall({ // Render inline details for desktop const renderDetails = useCallback(() => { - if (isMobile) return null; + if (!shouldRenderInline) return null; return ( ); - }, [isMobile, effectiveDetail, presentation.errorText, presentation.isLoadingDetails]); + }, [shouldRenderInline, effectiveDetail, presentation.errorText, presentation.isLoadingDetails]); if (presentation.isPlan && effectiveDetail?.type === "plan") { return ( @@ -3175,10 +3180,10 @@ export const ToolCall = memo(function ToolCall({ label={presentation.displayName} secondaryLabel={presentation.summary} icon={presentation.icon} - isExpanded={!isMobile && isExpanded} + isExpanded={shouldRenderInline && isExpanded} onToggle={presentation.canOpenDetails ? handleToggle : undefined} onOpenFile={handleOpenFile} - renderDetails={presentation.canOpenDetails && !isMobile ? renderDetails : undefined} + renderDetails={presentation.canOpenDetails && shouldRenderInline ? renderDetails : undefined} isLoading={status === "running" || status === "executing"} isError={status === "failed"} isLastInSequence={isLastInSequence} @@ -3200,5 +3205,7 @@ function areToolCallPropsEqual(previous: ToolCallProps, next: ToolCallProps) { if (previous.isLastInSequence !== next.isLastInSequence) return false; if (previous.disableOuterSpacing !== next.disableOuterSpacing) return false; if (previous.onOpenFilePath !== next.onOpenFilePath) return false; + if (previous.defaultExpanded !== next.defaultExpanded) return false; + if (previous.forceInline !== next.forceInline) return false; return true; } diff --git a/packages/app/src/hooks/use-settings/index.ts b/packages/app/src/hooks/use-settings/index.ts index 19c9cb9fe..5f942e965 100644 --- a/packages/app/src/hooks/use-settings/index.ts +++ b/packages/app/src/hooks/use-settings/index.ts @@ -177,6 +177,9 @@ export function useSettings(): UseSettingsReturn { if (updates.workspaceTitleSource !== undefined) { appUpdates.workspaceTitleSource = updates.workspaceTitleSource; } + if (updates.autoExpandReasoning !== undefined) { + appUpdates.autoExpandReasoning = updates.autoExpandReasoning; + } const promises: Promise[] = []; if (Object.keys(appUpdates).length > 0) { promises.push(appSettings.updateSettings(appUpdates)); diff --git a/packages/app/src/hooks/use-settings/storage.ts b/packages/app/src/hooks/use-settings/storage.ts index 9bf897cef..116781227 100644 --- a/packages/app/src/hooks/use-settings/storage.ts +++ b/packages/app/src/hooks/use-settings/storage.ts @@ -39,6 +39,7 @@ export interface AppSettings { codeFontSize: number; // clamped px, default 12 syntaxTheme: SyntaxThemeId; // default "one" workspaceTitleSource: WorkspaceTitleSource; + autoExpandReasoning: boolean; } export interface Settings extends AppSettings { @@ -58,6 +59,7 @@ export const DEFAULT_CLIENT_SETTINGS: AppSettings = { codeFontSize: DEFAULT_CODE_FONT_SIZE, syntaxTheme: "one", workspaceTitleSource: "title", + autoExpandReasoning: false, }; export const DEFAULT_APP_SETTINGS: Settings = { @@ -204,6 +206,9 @@ function pickAppSettings(stored: Partial): Partial { ) { result.workspaceTitleSource = stored.workspaceTitleSource; } + if (typeof stored.autoExpandReasoning === "boolean") { + result.autoExpandReasoning = stored.autoExpandReasoning; + } return result; } diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index 88a6fe5eb..54a4e5d52 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -1457,6 +1457,10 @@ export const ar: TranslationResources = { description: "يتم الاحتفاظ بالخطوط في المخزن المؤقت الطرفي المدمج", accessibilityLabel: "خطوط التمرير Terminal", }, + autoExpandReasoning: { + label: "عرض التفكير دائماً", + description: "إظهار تفكير الوكيل وخطوات الاستدلال بشكل كامل بشكل افتراضي", + }, language: { label: "لغة", description: "لغة التطبيق", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index a785a25a2..f8f983376 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -1464,6 +1464,10 @@ export const en = { description: "Lines kept in the built-in terminal buffer", accessibilityLabel: "Terminal scrollback lines", }, + autoExpandReasoning: { + label: "Always expand reasoning", + description: "Show agent thinking and chain-of-thought blocks fully expanded by default", + }, language: { label: "Language", description: "App language", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index 1463b5e23..a239621de 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -1497,6 +1497,11 @@ export const es: TranslationResources = { description: "Líneas mantenidas en el búfer de terminal incorporado", accessibilityLabel: "Líneas del historial de terminal", }, + autoExpandReasoning: { + label: "Siempre expandir razonamiento", + description: + "Mostrar los bloques de pensamiento y razonamiento del agente totalmente expandidos de forma predeterminada", + }, language: { label: "Idioma", description: "Idioma de la aplicación", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index b6ce77fc1..bbdb3983e 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -1500,6 +1500,10 @@ export const fr: TranslationResources = { description: "Lignes conservées dans le tampon du terminal intégré", accessibilityLabel: "Lignes de défilementTerminal", }, + autoExpandReasoning: { + label: "Toujours afficher le raisonnement", + description: "Afficher le raisonnement de l'agent entièrement développé par défaut", + }, language: { label: "Langue", description: "Langue de l'application", diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index 940a69b29..8447967fb 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -1473,6 +1473,10 @@ export const ja: TranslationResources = { description: "組み込みターミナルバッファに保持する行数", accessibilityLabel: "ターミナルスクロールバック行数", }, + autoExpandReasoning: { + label: "常に思考プロセスを展開", + description: "デフォルトでAIのエージェント思考・推論ブロックを完全に展開して表示します", + }, language: { label: "言語", description: "アプリの言語", diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index 413538448..8fd825938 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -1482,6 +1482,11 @@ export const ptBR: TranslationResources = { description: "Linhas mantidas no buffer do terminal integrado", accessibilityLabel: "Linhas do scrollback do terminal", }, + autoExpandReasoning: { + label: "Sempre expandir raciocínio", + description: + "Mostrar os blocos de pensamento e raciocínio do agente totalmente expandidos por padrão", + }, language: { label: "Idioma", description: "Idioma do app", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index 8dce28187..49e744afc 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -1487,6 +1487,11 @@ export const ru: TranslationResources = { description: "Строки, хранящиеся во встроенном буфере терминала.", accessibilityLabel: "Линии прокрутки Terminal", }, + autoExpandReasoning: { + label: "Всегда разворачивать размышления", + description: + "По умолчанию показывать блоки размышлений и логики агента полностью развернутыми", + }, language: { label: "Язык", description: "Язык приложения", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index ef6bee331..dba69c8cf 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -1439,6 +1439,10 @@ export const zhCN: TranslationResources = { description: "内置终端缓冲区保留的行数", accessibilityLabel: "终端回滚行数", }, + autoExpandReasoning: { + label: "始终展开推理过程", + description: "默认情况下完全展开 AI 的思考和推理过程", + }, language: { label: "语言", description: "应用语言", diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index cfbfaa07d..d8479ae99 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -67,6 +67,7 @@ import { AddHostModal } from "@/components/add-host-modal"; import { PairLinkModal } from "@/components/pair-link-modal"; import { KeyboardShortcutsSection } from "@/screens/settings/keyboard-shortcuts-section"; import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; import { CommunityLinks } from "@/components/community-links"; import { SegmentedControl } from "@/components/ui/segmented-control"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu"; @@ -244,6 +245,7 @@ interface GeneralSectionProps { handleServiceUrlBehaviorChange: (behavior: ServiceUrlBehavior) => void; handleLanguageChange: (language: AppLanguage) => void; handleTerminalScrollbackLinesChange: (lines: number) => void; + handleAutoExpandReasoningChange: (autoExpand: boolean) => void; } interface ServiceUrlBehaviorMenuItemProps { @@ -300,6 +302,7 @@ function GeneralSection({ handleServiceUrlBehaviorChange, handleLanguageChange, handleTerminalScrollbackLinesChange, + handleAutoExpandReasoningChange, }: GeneralSectionProps) { const { t, i18n } = useTranslation(); const activeLocale = getActiveLocale(i18n.language); @@ -433,6 +436,20 @@ function GeneralSection({ accessibilityLabel={t("settings.general.terminalScrollback.accessibilityLabel")} /> + + + + {t("settings.general.autoExpandReasoning.label")} + + + {t("settings.general.autoExpandReasoning.description")} + + + + ); @@ -1180,6 +1197,13 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti [updateSettings], ); + const handleAutoExpandReasoningChange = useCallback( + (autoExpandReasoning: boolean) => { + void updateSettings({ autoExpandReasoning }); + }, + [updateSettings], + ); + const handlePlaybackTest = useCallback(async () => { if (!voiceAudioEngine || isPlaybackTestRunning) { return; @@ -1385,6 +1409,7 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti handleServiceUrlBehaviorChange={handleServiceUrlBehaviorChange} handleLanguageChange={handleLanguageChange} handleTerminalScrollbackLinesChange={handleTerminalScrollbackLinesChange} + handleAutoExpandReasoningChange={handleAutoExpandReasoningChange} /> ); case "daemon":