mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
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 f56a367e0c.
* perf(app): narrow settings reads in agent stream
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
@@ -303,6 +303,8 @@ npm run build:app-deps # highlight -> protocol -> client -> expo-two-way-aud
|
||||
|
||||
Use `npm run build:server` whenever you have changed any daemon/server-facing package and need clean cross-package types or runtime behavior.
|
||||
|
||||
The app Metro config disables Watchman and uses Metro's node crawler for exports. Keep that invariant unless you have verified production app exports on machines with and without Watchman installed; distro Watchman builds can differ in capabilities and change Metro's crawl behavior.
|
||||
|
||||
For tighter loops, you can rebuild a single workspace:
|
||||
|
||||
- Changed `packages/protocol/src/*` or `packages/client/src/*`: `npm run build:client`.
|
||||
|
||||
@@ -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, "\\$&"))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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: "الافتراضي للنظام",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "システムデフォルト",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "Система по умолчанию",
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
Reference in New Issue
Block a user