From 9af2957ca0c83c717d2dd5e580f0b5480f81044c Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 24 Jan 2026 21:43:51 +0700 Subject: [PATCH 1/3] Update files --- .../app/src/hooks/use-agent-form-state.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/app/src/hooks/use-agent-form-state.ts b/packages/app/src/hooks/use-agent-form-state.ts index a18f37b15..337baefd1 100644 --- a/packages/app/src/hooks/use-agent-form-state.ts +++ b/packages/app/src/hooks/use-agent-form-state.ts @@ -9,6 +9,7 @@ import type { AgentProvider, } from "@server/server/agent/agent-sdk-types"; import { useSessionStore } from "@/stores/session-store"; +import { useDaemonRegistry } from "@/contexts/daemon-registry-context"; import { useFormPreferences, type FormPreferences } from "./use-form-preferences"; // Explicit overrides from URL params or "New Agent" button @@ -104,7 +105,8 @@ function resolveFormState( preferences: FormPreferences | null, availableModels: AgentModelDefinition[] | null, userModified: UserModifiedFields, - currentState: FormState + currentState: FormState, + validServerIds: Set ): FormState { // Start with current state - we only update non-user-modified fields const result = { ...currentState }; @@ -177,10 +179,11 @@ function resolveFormState( } // 4. Resolve serverId (independent) + // Only use stored serverId if the host still exists in the registry if (!userModified.serverId) { if (initialValues?.serverId !== undefined) { result.serverId = initialValues.serverId; - } else if (preferences?.serverId) { + } else if (preferences?.serverId && validServerIds.has(preferences.serverId)) { result.serverId = preferences.serverId; } // else keep current @@ -240,6 +243,14 @@ export function useAgentFormState( updateProviderPreferences, } = useFormPreferences(); + const { daemons } = useDaemonRegistry(); + + // Build a set of valid server IDs for preference validation + const validServerIds = useMemo( + () => new Set(daemons.map((d) => d.id)), + [daemons] + ); + // Track which fields the user has explicitly modified const [userModified, setUserModified] = useState(INITIAL_USER_MODIFIED); @@ -303,7 +314,8 @@ export function useAgentFormState( preferences, availableModels, userModified, - formStateRef.current + formStateRef.current, + validServerIds ); // Only update if something changed @@ -326,6 +338,7 @@ export function useAgentFormState( preferences, availableModels, userModified, + validServerIds, ]); // Persist inferred serverId so reloads keep the selection (e.g. URL serverId or first-time load). From 6503d3b4082985e63bd3324b16486f8407f77356 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 24 Jan 2026 22:05:44 +0700 Subject: [PATCH 2/3] Update files --- packages/app/src/components/agent-list.tsx | 35 +--------------- .../src/components/headers/menu-header.tsx | 34 +++++++++++---- .../src/screens/agent/agent-ready-screen.tsx | 24 +++++++++++ packages/app/src/utils/agent-display-info.ts | 41 +++++++++++++++++++ scripts/dev.sh | 8 ++-- 5 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 packages/app/src/utils/agent-display-info.ts diff --git a/packages/app/src/components/agent-list.tsx b/packages/app/src/components/agent-list.tsx index a444976d2..1ecbeaab3 100644 --- a/packages/app/src/components/agent-list.tsx +++ b/packages/app/src/components/agent-list.tsx @@ -14,12 +14,12 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { useQueryClient } from "@tanstack/react-query"; import { formatTimeAgo } from "@/utils/time"; import { shortenPath } from "@/utils/shorten-path"; +import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info"; import { type AggregatedAgent } from "@/hooks/use-aggregated-agents"; import { useSessionStore } from "@/stores/session-store"; import { CHECKOUT_STATUS_STALE_TIME, checkoutStatusQueryKey, - type CheckoutStatusPayload, useCheckoutStatusCacheOnly, } from "@/hooks/use-checkout-status-query"; import { @@ -105,35 +105,6 @@ export function AgentList({ setActionAgent(null); }, [actionAgent, deleteAgent]); - const deriveBranchLabel = useCallback((checkout: CheckoutStatusPayload | null): string | null => { - if (!checkout || !checkout.isGit) { - return null; - } - const currentBranch: string | null = checkout.currentBranch ?? null; - const baseRef: string | null = checkout.baseRef ?? null; - if (!currentBranch) { - return null; - } - if (baseRef && currentBranch === baseRef) { - return null; - } - return currentBranch; - }, []); - - const deriveProjectPath = useCallback( - (agent: AggregatedAgent, checkout: CheckoutStatusPayload | null): string => { - const basePath = checkout?.isGit ? (checkout.repoRoot ?? agent.cwd) : agent.cwd; - const worktreeMarker = ".paseo/worktrees/"; - const idx = basePath.indexOf(worktreeMarker); - if (idx !== -1) { - const afterMarker = basePath.slice(idx + worktreeMarker.length); - return afterMarker; - } - return basePath; - }, - [] - ); - const viewabilityConfig = useMemo( () => ({ itemVisiblePercentThreshold: 30 }), [] @@ -190,7 +161,7 @@ export function AgentList({ agentId: agent.id, }); const checkout = checkoutQuery.data ?? null; - const projectPath = deriveProjectPath(agent, checkout); + const projectPath = deriveProjectPath(agent.cwd, checkout); const branchLabel = deriveBranchLabel(checkout); return ( @@ -233,8 +204,6 @@ export function AgentList({ ); }, [ - deriveBranchLabel, - deriveProjectPath, handleAgentLongPress, handleAgentPress, selectedAgentId, diff --git a/packages/app/src/components/headers/menu-header.tsx b/packages/app/src/components/headers/menu-header.tsx index c211e7072..b861cf4c5 100644 --- a/packages/app/src/components/headers/menu-header.tsx +++ b/packages/app/src/components/headers/menu-header.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import { Pressable, Text } from "react-native"; +import { Pressable, Text, View } from "react-native"; import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles"; import { Menu, PanelLeft } from "lucide-react-native"; import { ScreenHeader } from "./screen-header"; @@ -7,10 +7,11 @@ import { usePanelStore } from "@/stores/panel-store"; interface MenuHeaderProps { title?: string; + subtitle?: string; rightContent?: ReactNode; } -export function MenuHeader({ title, rightContent }: MenuHeaderProps) { +export function MenuHeader({ title, subtitle, rightContent }: MenuHeaderProps) { const { theme } = useUnistyles(); const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; @@ -32,9 +33,16 @@ export function MenuHeader({ title, rightContent }: MenuHeaderProps) { {title && ( - - {title} - + + + {title} + + {subtitle && ( + + {subtitle} + + )} + )} } @@ -55,13 +63,21 @@ const styles = StyleSheet.create((theme) => ({ }, borderRadius: theme.borderRadius.lg, }, - title: { + titleContainer: { flex: 1, - fontSize: theme.fontSize.lg, + gap: theme.spacing[0], + }, + title: { + fontSize: theme.fontSize.base, fontWeight: { - xs: theme.fontWeight.semibold, - md: "400", + xs: "400", + md: "300", }, color: theme.colors.foreground, }, + subtitle: { + fontSize: theme.fontSize.sm, + fontWeight: "300", + color: theme.colors.foregroundMuted, + }, })); diff --git a/packages/app/src/screens/agent/agent-ready-screen.tsx b/packages/app/src/screens/agent/agent-ready-screen.tsx index 3c7b2148e..f3e007ab6 100644 --- a/packages/app/src/screens/agent/agent-ready-screen.tsx +++ b/packages/app/src/screens/agent/agent-ready-screen.tsx @@ -57,6 +57,8 @@ import { import { extractAgentModel } from "@/utils/extract-agent-model"; import { startPerfMonitor } from "@/utils/perf-monitor"; import { shortenPath } from "@/utils/shorten-path"; +import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info"; +import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query"; import { DropdownMenu, DropdownMenuContent, @@ -406,6 +408,13 @@ function AgentScreenContent({ ? branchError ?? "Unavailable" : branchLabel ?? "Unknown"; + // Checkout status for header subtitle + const checkoutStatusQuery = useCheckoutStatusQuery({ + serverId, + agentId: resolvedAgentId ?? "", + }); + const checkout = checkoutStatusQuery.status; + useEffect(() => { if (!resolvedAgentId) { setFocusedAgentId(null); @@ -490,6 +499,20 @@ function AgentScreenContent({ const effectiveAgent = agent ?? placeholderAgent; + // Header subtitle: project path + branch (matching agent list row format) + const headerProjectPath = effectiveAgent + ? deriveProjectPath(effectiveAgent.cwd, checkout) + : null; + const headerBranchLabel = deriveBranchLabel(checkout); + const headerSubtitle = useMemo(() => { + if (!headerProjectPath) return undefined; + const path = shortenPath(headerProjectPath); + if (headerBranchLabel) { + return `${path} · ${headerBranchLabel}`; + } + return path; + }, [headerProjectPath, headerBranchLabel]); + useEffect(() => { if (!isPendingCreateForRoute || !pendingCreate) { return; @@ -596,6 +619,7 @@ function AgentScreenContent({ {/* Header */} diff --git a/packages/app/src/utils/agent-display-info.ts b/packages/app/src/utils/agent-display-info.ts new file mode 100644 index 000000000..8fb4351b0 --- /dev/null +++ b/packages/app/src/utils/agent-display-info.ts @@ -0,0 +1,41 @@ +import type { CheckoutStatusPayload } from "@/hooks/use-checkout-status-query"; + +/** + * Derives the branch label to display for an agent. + * Returns null if there's no branch to show (not a git repo, or on the base branch). + */ +export function deriveBranchLabel( + checkout: CheckoutStatusPayload | null +): string | null { + if (!checkout || !checkout.isGit) { + return null; + } + const currentBranch: string | null = checkout.currentBranch ?? null; + const baseRef: string | null = checkout.baseRef ?? null; + if (!currentBranch) { + return null; + } + if (baseRef && currentBranch === baseRef) { + return null; + } + return currentBranch; +} + +/** + * Derives the project path to display for an agent. + * If inside a Paseo worktree, shows just the worktree-relative path. + * Otherwise uses the repo root or cwd. + */ +export function deriveProjectPath( + cwd: string, + checkout: CheckoutStatusPayload | null +): string { + const basePath = checkout?.isGit ? (checkout.repoRoot ?? cwd) : cwd; + const worktreeMarker = ".paseo/worktrees/"; + const idx = basePath.indexOf(worktreeMarker); + if (idx !== -1) { + const afterMarker = basePath.slice(idx + worktreeMarker.length); + return afterMarker; + } + return basePath; +} diff --git a/scripts/dev.sh b/scripts/dev.sh index 630f3e618..5ca49bec5 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -9,9 +9,9 @@ export PATH="$SCRIPT_DIR/../node_modules/.bin:$PATH" DAEMON_PORT=$(get-port 6767 6768 6769 6770 6771 6772 6773) METRO_PORT=$(get-port 8081 8082 8083 8084 8085 8086 8087) -# Use a temporary PASEO_HOME to avoid conflicts between dev instances -export PASEO_HOME=$(mktemp -d "${TMPDIR:-/tmp}/paseo-dev.XXXXXX") -trap "rm -rf '$PASEO_HOME'" EXIT +# # Use a temporary PASEO_HOME to avoid conflicts between dev instances +# export PASEO_HOME=$(mktemp -d "${TMPDIR:-/tmp}/paseo-dev.XXXXXX") +# trap "rm -rf '$PASEO_HOME'" EXIT # Build CORS origins for this Expo instance CORS_ORIGINS="http://localhost:${METRO_PORT},http://127.0.0.1:${METRO_PORT}" @@ -24,7 +24,7 @@ echo " Paseo Dev" echo "══════════════════════════════════════════════════════" echo " Daemon: http://localhost:${DAEMON_PORT}" echo " Metro: http://localhost:${METRO_PORT}" -echo " Home: ${PASEO_HOME}" +# echo " Home: ${PASEO_HOME}" echo "══════════════════════════════════════════════════════" # Export for child processes (overrides .env values) From 63c765b209567e8a11bb8d0ee8160fb4cd5f7733 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 24 Jan 2026 22:07:07 +0700 Subject: [PATCH 3/3] chore(dev): use persistent PASEO_HOME and bind to all interfaces --- scripts/dev.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/dev.sh b/scripts/dev.sh index 630f3e618..9cc99e7c6 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -9,9 +9,9 @@ export PATH="$SCRIPT_DIR/../node_modules/.bin:$PATH" DAEMON_PORT=$(get-port 6767 6768 6769 6770 6771 6772 6773) METRO_PORT=$(get-port 8081 8082 8083 8084 8085 8086 8087) -# Use a temporary PASEO_HOME to avoid conflicts between dev instances -export PASEO_HOME=$(mktemp -d "${TMPDIR:-/tmp}/paseo-dev.XXXXXX") -trap "rm -rf '$PASEO_HOME'" EXIT +# # Use a temporary PASEO_HOME to avoid conflicts between dev instances +# export PASEO_HOME=$(mktemp -d "${TMPDIR:-/tmp}/paseo-dev.XXXXXX") +# trap "rm -rf '$PASEO_HOME'" EXIT # Build CORS origins for this Expo instance CORS_ORIGINS="http://localhost:${METRO_PORT},http://127.0.0.1:${METRO_PORT}" @@ -24,11 +24,11 @@ echo " Paseo Dev" echo "══════════════════════════════════════════════════════" echo " Daemon: http://localhost:${DAEMON_PORT}" echo " Metro: http://localhost:${METRO_PORT}" -echo " Home: ${PASEO_HOME}" +# echo " Home: ${PASEO_HOME}" echo "══════════════════════════════════════════════════════" # Export for child processes (overrides .env values) -export PASEO_LISTEN="127.0.0.1:${DAEMON_PORT}" +export PASEO_LISTEN="0.0.0.0:${DAEMON_PORT}" export PASEO_CORS_ORIGINS="${CORS_ORIGINS}" # Run both with concurrently