From 5120f73f493d293e29201ca40b77e13cf9eff78c Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 29 Dec 2025 00:06:06 +0700 Subject: [PATCH] Add host selector to import agent modal --- .../app/src/components/create-agent-modal.tsx | 101 +++++++++++++++++- plan.md | 3 +- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/packages/app/src/components/create-agent-modal.tsx b/packages/app/src/components/create-agent-modal.tsx index 93493d075..bb575c59e 100644 --- a/packages/app/src/components/create-agent-modal.tsx +++ b/packages/app/src/components/create-agent-modal.tsx @@ -35,7 +35,7 @@ import Animated, { runOnJS, } from "react-native-reanimated"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; -import { Mic, Check, X, RefreshCcw } from "lucide-react-native"; +import { Mic, Check, X, RefreshCcw, Monitor } from "lucide-react-native"; import { theme as defaultTheme } from "@/styles/theme"; import { useRecentPaths } from "@/hooks/use-recent-paths"; import { useRouter } from "expo-router"; @@ -169,7 +169,13 @@ type ImportCandidate = { }; type ProviderFilter = "all" | AgentProvider; -type DropdownKey = "assistant" | "permissions" | "model" | "workingDir" | "baseBranch"; +type DropdownKey = + | "assistant" + | "permissions" + | "model" + | "workingDir" + | "baseBranch" + | "host"; type RepoInfoState = { cwd: string; @@ -426,6 +432,7 @@ function AgentFlowModal({ "Selected host"; const selectedDaemonStatusLabel = formatConnectionStatus(selectedDaemonStatus); const hasSelectedDaemon = Boolean(selectedServerId); + const hostBadgeLabel = hasSelectedDaemon ? selectedDaemonLabel : "Select host"; const selectedDaemonIsOffline = selectedDaemonStatus !== "online"; const selectedDaemonLastError = selectedDaemonConnection?.lastError?.trim(); const daemonAvailabilityError = !hasSelectedDaemon @@ -1787,6 +1794,23 @@ function AgentFlowModal({ paddingRight={horizontalPaddingRight} onClose={handleClose} title={modalTitle} + rightContent={ + isImportFlow ? ( + openDropdownSheet("host")} + > + + {hostBadgeLabel} + + + ) : undefined + } /> {isCreateFlow ? ( <> @@ -2133,6 +2157,40 @@ function AgentFlowModal({ )} )} + + {daemonEntries.length === 0 ? ( + No hosts available yet. + ) : ( + + {daemonEntries.map(({ daemon, status }) => { + const isSelected = daemon.id === selectedServerId; + const label = daemon.label ?? daemon.wsUrl ?? daemon.id; + return ( + { + setSelectedServerIdFromUser(daemon.id); + closeDropdown(); + }} + > + {label} + + {formatConnectionStatus(status)} + + + ); + })} + + )} + @@ -2175,6 +2233,7 @@ interface ModalHeaderProps { paddingRight: number; onClose: () => void; title: string; + rightContent?: ReactNode; } function ModalHeader({ @@ -2183,13 +2242,17 @@ function ModalHeader({ paddingRight, onClose, title, + rightContent, }: ModalHeaderProps): ReactElement { return ( {title} - - - + + {rightContent} + + + + ); } @@ -2652,12 +2715,40 @@ const styles = StyleSheet.create(((theme: any) => ({ fontSize: theme.fontSize["2xl"], fontWeight: theme.fontWeight.semibold, }, + headerActions: { + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[3], + }, closeButton: { padding: theme.spacing[2], borderRadius: theme.borderRadius.full, alignItems: "center", justifyContent: "center", }, + hostBadge: { + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + paddingHorizontal: theme.spacing[3], + paddingVertical: theme.spacing[2], + backgroundColor: theme.colors.muted, + borderRadius: theme.borderRadius.full, + }, + hostBadgeLabel: { + color: theme.colors.mutedForeground, + fontSize: theme.fontSize.xs, + fontWeight: theme.fontWeight.semibold, + }, + hostStatusDot: { + width: 6, + height: 6, + borderRadius: theme.borderRadius.full, + backgroundColor: theme.colors.mutedForeground, + }, + hostStatusDotOnline: { + backgroundColor: theme.colors.palette.green[500], + }, scroll: { flex: 1, }, diff --git a/plan.md b/plan.md index 41a7e9a1f..9b12ccd74 100644 --- a/plan.md +++ b/plan.md @@ -58,11 +58,12 @@ Improvements to the new agent screen in the app. - Add daemon-level E2E test to verify import works correctly. - **Done (2025-12-28 23:58)**: WHAT: `packages/server/src/server/agent/providers/codex-mcp-agent.ts:4133-4476` adds Codex persisted session listing from rollout files plus JSON/JSONL timeline parsing and session metadata extraction; `packages/server/src/server/daemon.e2e.test.ts:2597-2662` adds Codex persisted agent import E2E coverage. RESULT: Codex import list now populates from on-disk rollouts with timeline previews and is covered by a daemon E2E. EVIDENCE: Not run (not requested). -- [ ] **Iteration 5**: Add host selector to import agent screen. +- [x] **Iteration 5**: Add host selector to import agent screen. - When importing an agent, user should be able to choose which host to import to. - Use the same UI pattern as the new agent screen: host selector in the header. - Reference the new agent screen implementation for consistency. + - **Done (2025-12-29 00:05)**: WHAT: `packages/app/src/components/create-agent-modal.tsx:172-2193` adds a host dropdown key, renders the header host badge in the import flow, and adds a host selection sheet; `packages/app/src/components/create-agent-modal.tsx:2718-2751` adds host badge/header action styles. RESULT: import agent modal now lets users pick the target host from the header selector. EVIDENCE: Not run (not requested). - [x] **Iteration 6**: Fix gallery 15s timeout causing full-screen error crash.