Show reasoning output automatically when enabled (#1943)

* feat(app): add auto-expand reasoning setting

* chore: add cross-env mise tool

* fix(app): stabilize web export and settings detail placement

* Revert "chore: add cross-env mise tool"

This reverts commit f56a367e0cd6766140db2d3b30a1c57bff4d9a14.

* perf(app): narrow settings reads in agent stream

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
Matt Cowger
2026-07-08 21:39:03 +00:00
committed by GitHub
co-authored by Mohamed Boudra
parent 29a3261dd6
commit d96d599466
15 changed files with 155 additions and 19 deletions
+6
View File
@@ -14,6 +14,12 @@ const customWebPlatform = (process.env.PASEO_WEB_PLATFORM ?? "")
const config = getDefaultConfig(projectRoot);
const defaultResolveRequest = config.resolver.resolveRequest ?? resolve;
// Keep app exports deterministic across dev machines and CI. Metro's Watchman
// crawler behavior depends on the host Watchman build/capabilities, while the
// node crawler is the path used when Watchman is absent.
config.resolver.useWatchman = false;
const escapedAppSrcRoot = appSrcRoot
.split(path.sep)
.map((segment) => segment.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&"))
+5 -1
View File
@@ -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<AgentStreamViewHandle, AgentStreamVi
) {
const { t } = useTranslation();
const router = useRouter();
const autoExpandReasoning = useSettings((settings) => settings.autoExpandReasoning);
const viewportRef = useRef<StreamViewportHandle | null>(null);
const isMobile = useIsCompactFormFactor();
const streamRenderStrategy = useMemo(
@@ -633,10 +635,12 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
args={item.text}
status={item.status === "ready" ? "completed" : "executing"}
isLastInSequence={layoutItem.isLastInToolSequence}
defaultExpanded={autoExpandReasoning}
forceInline={autoExpandReasoning}
/>
);
},
[setInlineDetailsExpanded],
[autoExpandReasoning, setInlineDetailsExpanded],
);
const renderToolCallItem = useCallback(
+18 -11
View File
@@ -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<ToolCallDetail | undefined>(() => {
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 (
<ToolCallDetailsContent
detail={effectiveDetail}
@@ -3157,7 +3162,7 @@ export const ToolCall = memo(function ToolCall({
showLoadingSkeleton={presentation.isLoadingDetails}
/>
);
}, [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;
}
+22 -7
View File
@@ -96,6 +96,8 @@ export interface UseSettingsReturn {
resetSettings: () => Promise<void>;
}
type SettingsSelector<TSelected> = (settings: Settings) => TSelected;
export function useAppSettings(): UseAppSettingsReturn {
const queryClient = useQueryClient();
const { data, isPending, error } = useQuery({
@@ -137,7 +139,11 @@ export function useAppSettings(): UseAppSettingsReturn {
};
}
export function useSettings(): UseSettingsReturn {
export function useSettings(): UseSettingsReturn;
export function useSettings<TSelected>(selector: SettingsSelector<TSelected>): TSelected;
export function useSettings<TSelected>(
selector?: SettingsSelector<TSelected>,
): UseSettingsReturn | TSelected {
const appSettings = useAppSettings();
const desktopSettings = useDesktopSettings();
@@ -177,6 +183,9 @@ export function useSettings(): UseSettingsReturn {
if (updates.workspaceTitleSource !== undefined) {
appUpdates.workspaceTitleSource = updates.workspaceTitleSource;
}
if (updates.autoExpandReasoning !== undefined) {
appUpdates.autoExpandReasoning = updates.autoExpandReasoning;
}
const promises: Promise<void>[] = [];
if (Object.keys(appUpdates).length > 0) {
promises.push(appSettings.updateSettings(appUpdates));
@@ -210,13 +219,19 @@ export function useSettings(): UseSettingsReturn {
await Promise.all(resets);
}, [appSettings, desktopSettings]);
const settings = {
...DEFAULT_APP_SETTINGS,
...appSettings.settings,
manageBuiltInDaemon: desktopSettings.settings.daemon.manageBuiltInDaemon,
releaseChannel: desktopSettings.settings.releaseChannel,
};
if (selector) {
return selector(settings);
}
return {
settings: {
...DEFAULT_APP_SETTINGS,
...appSettings.settings,
manageBuiltInDaemon: desktopSettings.settings.daemon.manageBuiltInDaemon,
releaseChannel: desktopSettings.settings.releaseChannel,
},
settings,
isLoading: appSettings.isLoading || desktopSettings.isLoading,
error: appSettings.error ?? desktopSettings.error,
updateSettings,
@@ -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<AppSettings>): Partial<AppSettings> {
) {
result.workspaceTitleSource = stored.workspaceTitleSource;
}
if (typeof stored.autoExpandReasoning === "boolean") {
result.autoExpandReasoning = stored.autoExpandReasoning;
}
return result;
}
+7
View File
@@ -1471,6 +1471,10 @@ export const ar: TranslationResources = {
description: "يتم الاحتفاظ بالخطوط في المخزن المؤقت الطرفي المدمج",
accessibilityLabel: "خطوط التمرير Terminal",
},
autoExpandReasoning: {
label: "عرض التفكير دائماً",
description: "إظهار تفكير الوكيل وخطوات الاستدلال بشكل كامل بشكل افتراضي",
},
language: {
label: "لغة",
description: "لغة التطبيق",
@@ -1552,6 +1556,9 @@ export const ar: TranslationResources = {
auto: "نظام",
},
},
detailLevel: {
title: "مستوى التفاصيل",
},
fonts: {
title: "الخطوط",
systemDefault: "الافتراضي للنظام",
+7
View File
@@ -1478,6 +1478,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",
@@ -1559,6 +1563,9 @@ export const en = {
auto: "System",
},
},
detailLevel: {
title: "Detail level",
},
fonts: {
title: "Fonts",
systemDefault: "System default",
+8
View File
@@ -1511,6 +1511,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",
@@ -1592,6 +1597,9 @@ export const es: TranslationResources = {
auto: "Sistema",
},
},
detailLevel: {
title: "Nivel de detalle",
},
fonts: {
title: "Fuentes",
systemDefault: "Valor predeterminado del sistema",
+7
View File
@@ -1514,6 +1514,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",
@@ -1596,6 +1600,9 @@ export const fr: TranslationResources = {
auto: "Système",
},
},
detailLevel: {
title: "Niveau de détail",
},
fonts: {
title: "Polices",
systemDefault: "Valeur par défaut du système",
+7
View File
@@ -1487,6 +1487,10 @@ export const ja: TranslationResources = {
description: "組み込みターミナルバッファに保持する行数",
accessibilityLabel: "ターミナルスクロールバック行数",
},
autoExpandReasoning: {
label: "常に思考プロセスを展開",
description: "デフォルトでAIのエージェント思考・推論ブロックを完全に展開して表示します",
},
language: {
label: "言語",
description: "アプリの言語",
@@ -1568,6 +1572,9 @@ export const ja: TranslationResources = {
auto: "システム",
},
},
detailLevel: {
title: "詳細レベル",
},
fonts: {
title: "フォント",
systemDefault: "システムデフォルト",
+8
View File
@@ -1496,6 +1496,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",
@@ -1577,6 +1582,9 @@ export const ptBR: TranslationResources = {
auto: "Sistema",
},
},
detailLevel: {
title: "Nível de detalhe",
},
fonts: {
title: "Fontes",
systemDefault: "Sistema padrão",
+8
View File
@@ -1501,6 +1501,11 @@ export const ru: TranslationResources = {
description: "Строки, хранящиеся во встроенном буфере терминала.",
accessibilityLabel: "Линии прокрутки Terminal",
},
autoExpandReasoning: {
label: "Всегда разворачивать размышления",
description:
"По умолчанию показывать блоки размышлений и логики агента полностью развернутыми",
},
language: {
label: "Язык",
description: "Язык приложения",
@@ -1583,6 +1588,9 @@ export const ru: TranslationResources = {
auto: "Система",
},
},
detailLevel: {
title: "Уровень детализации",
},
fonts: {
title: "Шрифты",
systemDefault: "Система по умолчанию",
+7
View File
@@ -1453,6 +1453,10 @@ export const zhCN: TranslationResources = {
description: "内置终端缓冲区保留的行数",
accessibilityLabel: "终端回滚行数",
},
autoExpandReasoning: {
label: "始终展开推理过程",
description: "默认情况下完全展开 AI 的思考和推理过程",
},
language: {
label: "语言",
description: "应用语言",
@@ -1534,6 +1538,9 @@ export const zhCN: TranslationResources = {
auto: "系统",
},
},
detailLevel: {
title: "详细程度",
},
fonts: {
title: "字体",
systemDefault: "系统默认",
@@ -16,6 +16,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Switch } from "@/components/ui/switch";
import { SettingsSection } from "@/screens/settings/settings-section";
import {
MAX_CODE_FONT_SIZE,
@@ -189,6 +190,28 @@ function ThemeRow({ value, onChange }: ThemeRowProps) {
);
}
interface AutoExpandReasoningRowProps {
value: boolean;
onChange: (value: boolean) => void;
}
function AutoExpandReasoningRow({ value, onChange }: AutoExpandReasoningRowProps) {
const { t } = useTranslation();
return (
<View style={settingsStyles.row}>
<View style={settingsStyles.rowContent}>
<Text style={settingsStyles.rowTitle}>
{t("settings.general.autoExpandReasoning.label")}
</Text>
<Text style={settingsStyles.rowHint}>
{t("settings.general.autoExpandReasoning.description")}
</Text>
</View>
<Switch value={value} onValueChange={onChange} />
</View>
);
}
// ---------------------------------------------------------------------------
// Fonts: family text fields + numeric size fields (commit on blur/submit)
// ---------------------------------------------------------------------------
@@ -397,6 +420,13 @@ export function AppearanceSection() {
[updateSettings],
);
const handleAutoExpandReasoningChange = useCallback(
(autoExpandReasoning: boolean) => {
void updateSettings({ autoExpandReasoning });
},
[updateSettings],
);
const commitUiFontFamily = useCallback(
(value: string) => {
const sanitized = sanitizeFontFamily(value);
@@ -477,6 +507,14 @@ export function AppearanceSection() {
<ThemeRow value={settings.theme} onChange={handleThemeChange} />
</View>
</SettingsSection>
<SettingsSection title={t("settings.appearance.detailLevel.title")}>
<View style={settingsStyles.card}>
<AutoExpandReasoningRow
value={settings.autoExpandReasoning}
onChange={handleAutoExpandReasoningChange}
/>
</View>
</SettingsSection>
<SettingsSection title={t("settings.appearance.fonts.title")}>
<View style={settingsStyles.card}>
{showFontFamilyRows ? (