Fix: Clear attention when agent finishes while user is viewing
When a user is viewing an agent screen and the agent transitions from running to idle, immediately call clearAgentAttention since the user witnessed the completion. This prevents the "requires attention" state from appearing when the user navigates back to the home screen. Added a useEffect in AgentScreenContent that: 1. Tracks the previous agent status via a ref 2. Detects transitions from "running" to "idle" 3. Clears attention immediately on such transitions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
5d20129a33
commit
11e7bc5fcc
@@ -14,15 +14,29 @@ import { useLocalSearchParams, useRouter } from "expo-router";
|
|||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
||||||
import ReanimatedAnimated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
import ReanimatedAnimated, {
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
} from "react-native-reanimated";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { MoreVertical, GitBranch, Folder, RotateCcw, PlusCircle, Download, Users, ChevronRight } from "lucide-react-native";
|
import {
|
||||||
|
MoreVertical,
|
||||||
|
GitBranch,
|
||||||
|
Folder,
|
||||||
|
RotateCcw,
|
||||||
|
Plus,
|
||||||
|
Download,
|
||||||
|
Users,
|
||||||
|
ChevronRight,
|
||||||
|
PlusIcon,
|
||||||
|
} from "lucide-react-native";
|
||||||
import { BackHeader } from "@/components/headers/back-header";
|
import { BackHeader } from "@/components/headers/back-header";
|
||||||
import { AgentStreamView } from "@/components/agent-stream-view";
|
import { AgentStreamView } from "@/components/agent-stream-view";
|
||||||
import { AgentInputArea } from "@/components/agent-input-area";
|
import { AgentInputArea } from "@/components/agent-input-area";
|
||||||
import { AgentList } from "@/components/agent-list";
|
import { AgentList } from "@/components/agent-list";
|
||||||
import { useAggregatedAgents } from "@/hooks/use-aggregated-agents";
|
import { useAggregatedAgents } from "@/hooks/use-aggregated-agents";
|
||||||
import { CreateAgentModal, ImportAgentModal, type CreateAgentInitialValues } from "@/components/create-agent-modal";
|
import { CreateAgentModal, ImportAgentModal } from "@/components/create-agent-modal";
|
||||||
|
import type { CreateAgentInitialValues } from "@/hooks/use-agent-form-state";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||||
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
||||||
import { formatConnectionStatus } from "@/utils/daemons";
|
import { formatConnectionStatus } from "@/utils/daemons";
|
||||||
@@ -51,7 +65,10 @@ type BranchStatus = "idle" | "loading" | "ready" | "error";
|
|||||||
|
|
||||||
export default function AgentScreen() {
|
export default function AgentScreen() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { serverId, agentId } = useLocalSearchParams<{ serverId: string; agentId: string }>();
|
const { serverId, agentId } = useLocalSearchParams<{
|
||||||
|
serverId: string;
|
||||||
|
agentId: string;
|
||||||
|
}>();
|
||||||
const resolvedAgentId = typeof agentId === "string" ? agentId : undefined;
|
const resolvedAgentId = typeof agentId === "string" ? agentId : undefined;
|
||||||
const resolvedServerId = typeof serverId === "string" ? serverId : undefined;
|
const resolvedServerId = typeof serverId === "string" ? serverId : undefined;
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { connectionStates } = useDaemonConnections();
|
||||||
@@ -62,10 +79,14 @@ export default function AgentScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const connectionServerId = resolvedServerId ?? null;
|
const connectionServerId = resolvedServerId ?? null;
|
||||||
const connection = connectionServerId ? connectionStates.get(connectionServerId) : null;
|
const connection = connectionServerId
|
||||||
const serverLabel = connection?.daemon.label ?? connectionServerId ?? "Selected host";
|
? connectionStates.get(connectionServerId)
|
||||||
|
: null;
|
||||||
|
const serverLabel =
|
||||||
|
connection?.daemon.label ?? connectionServerId ?? "Selected host";
|
||||||
const isUnknownDaemon = Boolean(connectionServerId && !connection);
|
const isUnknownDaemon = Boolean(connectionServerId && !connection);
|
||||||
const connectionStatus = connection?.status ?? (isUnknownDaemon ? "offline" : "idle");
|
const connectionStatus =
|
||||||
|
connection?.status ?? (isUnknownDaemon ? "offline" : "idle");
|
||||||
const connectionStatusLabel = formatConnectionStatus(connectionStatus);
|
const connectionStatusLabel = formatConnectionStatus(connectionStatus);
|
||||||
const lastConnectionError = connection?.lastError ?? null;
|
const lastConnectionError = connection?.lastError ?? null;
|
||||||
|
|
||||||
@@ -100,7 +121,10 @@ export default function AgentScreen() {
|
|||||||
if (!resolvedAgentId || !focusServerId) {
|
if (!resolvedAgentId || !focusServerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const navigationKey = buildAgentNavigationKey(focusServerId, resolvedAgentId);
|
const navigationKey = buildAgentNavigationKey(
|
||||||
|
focusServerId,
|
||||||
|
resolvedAgentId
|
||||||
|
);
|
||||||
endNavigationTiming(navigationKey, {
|
endNavigationTiming(navigationKey, {
|
||||||
screen: "agent",
|
screen: "agent",
|
||||||
status: navigationStatus,
|
status: navigationStatus,
|
||||||
@@ -139,28 +163,39 @@ type AgentScreenContentProps = {
|
|||||||
const SIDEBAR_WIDTH = 280;
|
const SIDEBAR_WIDTH = 280;
|
||||||
const LARGE_SCREEN_BREAKPOINT = 768;
|
const LARGE_SCREEN_BREAKPOINT = 768;
|
||||||
|
|
||||||
function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentProps) {
|
function AgentScreenContent({
|
||||||
|
serverId,
|
||||||
|
agentId,
|
||||||
|
onBack,
|
||||||
|
}: AgentScreenContentProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
||||||
const isLargeScreen = windowWidth >= LARGE_SCREEN_BREAKPOINT;
|
const isLargeScreen = windowWidth >= LARGE_SCREEN_BREAKPOINT;
|
||||||
|
|
||||||
const { agents: aggregatedAgents, isRevalidating, refreshAll } = useAggregatedAgents();
|
const {
|
||||||
|
agents: aggregatedAgents,
|
||||||
|
isRevalidating,
|
||||||
|
refreshAll,
|
||||||
|
} = useAggregatedAgents();
|
||||||
const [menuVisible, setMenuVisible] = useState(false);
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
||||||
const [menuContentHeight, setMenuContentHeight] = useState(0);
|
const [menuContentHeight, setMenuContentHeight] = useState(0);
|
||||||
const menuButtonRef = useRef<View>(null);
|
const menuButtonRef = useRef<View>(null);
|
||||||
const [showCreateAgentModal, setShowCreateAgentModal] = useState(false);
|
const [showCreateAgentModal, setShowCreateAgentModal] = useState(false);
|
||||||
const [showImportAgentModal, setShowImportAgentModal] = useState(false);
|
const [showImportAgentModal, setShowImportAgentModal] = useState(false);
|
||||||
const [createAgentInitialValues, setCreateAgentInitialValues] =
|
const [createAgentInitialValues, setCreateAgentInitialValues] = useState<
|
||||||
useState<CreateAgentInitialValues | undefined>();
|
CreateAgentInitialValues | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
const resolvedAgentId = agentId;
|
const resolvedAgentId = agentId;
|
||||||
|
|
||||||
// Select only the specific agent
|
// Select only the specific agent
|
||||||
const agent = useSessionStore((state) =>
|
const agent = useSessionStore((state) =>
|
||||||
resolvedAgentId ? state.sessions[serverId]?.agents?.get(resolvedAgentId) : undefined
|
resolvedAgentId
|
||||||
|
? state.sessions[serverId]?.agents?.get(resolvedAgentId)
|
||||||
|
: undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get parent agent ID for back navigation
|
// Get parent agent ID for back navigation
|
||||||
@@ -184,7 +219,9 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
}, [parentAgentId, router, serverId, onBack]);
|
}, [parentAgentId, router, serverId, onBack]);
|
||||||
|
|
||||||
// Select the agents Map directly - this is a stable reference that only changes when agents are added/removed
|
// Select the agents Map directly - this is a stable reference that only changes when agents are added/removed
|
||||||
const allAgents = useSessionStore((state) => state.sessions[serverId]?.agents);
|
const allAgents = useSessionStore(
|
||||||
|
(state) => state.sessions[serverId]?.agents
|
||||||
|
);
|
||||||
|
|
||||||
// Derive child agents in useMemo to avoid creating new references on every store update
|
// Derive child agents in useMemo to avoid creating new references on every store update
|
||||||
// This prevents infinite loops caused by useShallow not doing deep equality on object arrays
|
// This prevents infinite loops caused by useShallow not doing deep equality on object arrays
|
||||||
@@ -198,7 +235,12 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
}> = [];
|
}> = [];
|
||||||
for (const [id, a] of allAgents) {
|
for (const [id, a] of allAgents) {
|
||||||
if (a.parentAgentId === resolvedAgentId) {
|
if (a.parentAgentId === resolvedAgentId) {
|
||||||
children.push({ id, title: a.title, createdAt: a.createdAt, status: a.status });
|
children.push({
|
||||||
|
id,
|
||||||
|
title: a.title,
|
||||||
|
createdAt: a.createdAt,
|
||||||
|
status: a.status,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
children.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
children.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
||||||
@@ -207,18 +249,23 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
|
|
||||||
// Select only the specific stream state - use stable empty array to avoid infinite loop
|
// Select only the specific stream state - use stable empty array to avoid infinite loop
|
||||||
const streamItemsRaw = useSessionStore((state) =>
|
const streamItemsRaw = useSessionStore((state) =>
|
||||||
resolvedAgentId ? state.sessions[serverId]?.agentStreamState?.get(resolvedAgentId) : undefined
|
resolvedAgentId
|
||||||
|
? state.sessions[serverId]?.agentStreamState?.get(resolvedAgentId)
|
||||||
|
: undefined
|
||||||
);
|
);
|
||||||
const streamItems = streamItemsRaw ?? EMPTY_STREAM_ITEMS;
|
const streamItems = streamItemsRaw ?? EMPTY_STREAM_ITEMS;
|
||||||
|
|
||||||
// Select only the specific initializing state
|
// Select only the specific initializing state
|
||||||
const isInitializingFromMap = useSessionStore((state) =>
|
const isInitializingFromMap = useSessionStore((state) =>
|
||||||
resolvedAgentId ? (state.sessions[serverId]?.initializingAgents?.get(resolvedAgentId) ?? false) : false
|
resolvedAgentId
|
||||||
|
? state.sessions[serverId]?.initializingAgents?.get(resolvedAgentId) ??
|
||||||
|
false
|
||||||
|
: false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Select raw pending permissions - filter with useMemo to avoid new Map on every render
|
// Select raw pending permissions - filter with useMemo to avoid new Map on every render
|
||||||
const allPendingPermissions = useSessionStore((state) =>
|
const allPendingPermissions = useSessionStore(
|
||||||
state.sessions[serverId]?.pendingPermissions
|
(state) => state.sessions[serverId]?.pendingPermissions
|
||||||
);
|
);
|
||||||
const pendingPermissions = useMemo(() => {
|
const pendingPermissions = useMemo(() => {
|
||||||
if (!allPendingPermissions || !resolvedAgentId) return new Map();
|
if (!allPendingPermissions || !resolvedAgentId) return new Map();
|
||||||
@@ -288,16 +335,17 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
message.payload.error ? new Error(message.payload.error) : null,
|
message.payload.error ? new Error(message.payload.error) : null,
|
||||||
keepPreviousData: false,
|
keepPreviousData: false,
|
||||||
});
|
});
|
||||||
const { execute: fetchGitRepoInfo, reset: resetGitRepoInfo } = gitRepoInfoRequest;
|
const { execute: fetchGitRepoInfo, reset: resetGitRepoInfo } =
|
||||||
|
gitRepoInfoRequest;
|
||||||
const branchStatus: BranchStatus = !agent?.cwd
|
const branchStatus: BranchStatus = !agent?.cwd
|
||||||
? "idle"
|
? "idle"
|
||||||
: gitRepoInfoRequest.status === "loading"
|
: gitRepoInfoRequest.status === "loading"
|
||||||
? "loading"
|
? "loading"
|
||||||
: gitRepoInfoRequest.status === "error"
|
: gitRepoInfoRequest.status === "error"
|
||||||
? "error"
|
? "error"
|
||||||
: gitRepoInfoRequest.status === "success"
|
: gitRepoInfoRequest.status === "success"
|
||||||
? "ready"
|
? "ready"
|
||||||
: "idle";
|
: "idle";
|
||||||
const branchLabel = gitRepoInfoRequest.data?.currentBranch ?? null;
|
const branchLabel = gitRepoInfoRequest.data?.currentBranch ?? null;
|
||||||
const branchError = gitRepoInfoRequest.error?.message ?? null;
|
const branchError = gitRepoInfoRequest.error?.message ?? null;
|
||||||
const branchDisplayValue =
|
const branchDisplayValue =
|
||||||
@@ -325,7 +373,9 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
};
|
};
|
||||||
}, [resolvedAgentId, setFocusedAgentId]);
|
}, [resolvedAgentId, setFocusedAgentId]);
|
||||||
|
|
||||||
const isInitializing = resolvedAgentId ? isInitializingFromMap !== false : false;
|
const isInitializing = resolvedAgentId
|
||||||
|
? isInitializingFromMap !== false
|
||||||
|
: false;
|
||||||
|
|
||||||
// Track which agent we've already triggered initialization for
|
// Track which agent we've already triggered initialization for
|
||||||
const initializedAgentRef = useRef<string | null>(null);
|
const initializedAgentRef = useRef<string | null>(null);
|
||||||
@@ -366,6 +416,26 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
document.title = title;
|
document.title = title;
|
||||||
}, [agent?.title]);
|
}, [agent?.title]);
|
||||||
|
|
||||||
|
// Track previous agent status to detect completion while viewing
|
||||||
|
const previousStatusRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
// Clear attention when agent finishes while user is viewing this screen
|
||||||
|
useEffect(() => {
|
||||||
|
if (!resolvedAgentId || !agent || !ws) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousStatus = previousStatusRef.current;
|
||||||
|
const currentStatus = agent.status;
|
||||||
|
previousStatusRef.current = currentStatus;
|
||||||
|
|
||||||
|
// If agent transitioned from running to idle while we're viewing,
|
||||||
|
// immediately clear attention since user witnessed the completion
|
||||||
|
if (previousStatus === "running" && currentStatus === "idle") {
|
||||||
|
ws.clearAgentAttention(resolvedAgentId);
|
||||||
|
}
|
||||||
|
}, [resolvedAgentId, agent?.status, ws]);
|
||||||
|
|
||||||
const recalculateMenuPosition = useCallback(
|
const recalculateMenuPosition = useCallback(
|
||||||
(onMeasured?: () => void) => {
|
(onMeasured?: () => void) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -383,7 +453,10 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
const horizontalMargin = 16;
|
const horizontalMargin = 16;
|
||||||
const desiredLeft = x + width - DROPDOWN_WIDTH;
|
const desiredLeft = x + width - DROPDOWN_WIDTH;
|
||||||
const maxLeft = windowWidth - DROPDOWN_WIDTH - horizontalMargin;
|
const maxLeft = windowWidth - DROPDOWN_WIDTH - horizontalMargin;
|
||||||
const clampedLeft = Math.min(Math.max(desiredLeft, horizontalMargin), maxLeft);
|
const clampedLeft = Math.min(
|
||||||
|
Math.max(desiredLeft, horizontalMargin),
|
||||||
|
maxLeft
|
||||||
|
);
|
||||||
|
|
||||||
// Position menu below button using the raw coordinates from measureInWindow
|
// Position menu below button using the raw coordinates from measureInWindow
|
||||||
const buttonBottom = y + height;
|
const buttonBottom = y + height;
|
||||||
@@ -392,9 +465,10 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
// If menu would go off screen, clamp to visible area
|
// If menu would go off screen, clamp to visible area
|
||||||
const bottomEdge = top + menuContentHeight;
|
const bottomEdge = top + menuContentHeight;
|
||||||
const maxBottom = windowHeight - horizontalMargin;
|
const maxBottom = windowHeight - horizontalMargin;
|
||||||
const clampedTop = bottomEdge > maxBottom
|
const clampedTop =
|
||||||
? Math.max(verticalOffset, maxBottom - menuContentHeight)
|
bottomEdge > maxBottom
|
||||||
: top;
|
? Math.max(verticalOffset, maxBottom - menuContentHeight)
|
||||||
|
: top;
|
||||||
|
|
||||||
setMenuPosition({
|
setMenuPosition({
|
||||||
top: clampedTop,
|
top: clampedTop,
|
||||||
@@ -477,14 +551,21 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleCloseMenu();
|
handleCloseMenu();
|
||||||
setCreateAgentInitialValues({
|
const params: Record<string, string> = { serverId };
|
||||||
workingDir: agent.cwd,
|
if (agent.cwd) {
|
||||||
provider: agent.provider,
|
params.workingDir = agent.cwd;
|
||||||
modeId: agent.currentModeId,
|
}
|
||||||
model: agentModel ?? undefined,
|
if (agent.provider) {
|
||||||
});
|
params.provider = agent.provider;
|
||||||
setShowCreateAgentModal(true);
|
}
|
||||||
}, [agent, agentModel, handleCloseMenu]);
|
if (agent.currentModeId) {
|
||||||
|
params.modeId = agent.currentModeId;
|
||||||
|
}
|
||||||
|
if (agentModel) {
|
||||||
|
params.model = agentModel;
|
||||||
|
}
|
||||||
|
router.push({ pathname: "/agent/new", params });
|
||||||
|
}, [agent, agentModel, handleCloseMenu, router, serverId]);
|
||||||
|
|
||||||
const handleCloseCreateAgentModal = useCallback(() => {
|
const handleCloseCreateAgentModal = useCallback(() => {
|
||||||
setShowCreateAgentModal(false);
|
setShowCreateAgentModal(false);
|
||||||
@@ -548,10 +629,31 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={[styles.mainLayout, isLargeScreen && styles.mainLayoutRow]}>
|
<View
|
||||||
|
style={[styles.mainLayout, isLargeScreen && styles.mainLayoutRow]}
|
||||||
|
>
|
||||||
{/* Sidebar - only on large screens */}
|
{/* Sidebar - only on large screens */}
|
||||||
{isLargeScreen && (
|
{isLargeScreen && (
|
||||||
<View style={[styles.sidebar, { width: SIDEBAR_WIDTH }]}>
|
<View style={[styles.sidebar, { width: SIDEBAR_WIDTH }]}>
|
||||||
|
<View style={styles.sidebarHeader}>
|
||||||
|
<Pressable
|
||||||
|
style={[
|
||||||
|
styles.newAgentButton,
|
||||||
|
{ backgroundColor: theme.colors.primary },
|
||||||
|
]}
|
||||||
|
onPress={handleCreateNewAgent}
|
||||||
|
>
|
||||||
|
<Plus size={18} color={theme.colors.primaryForeground} />
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.newAgentButtonText,
|
||||||
|
{ color: theme.colors.primaryForeground },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
New Agent
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
<AgentList
|
<AgentList
|
||||||
agents={aggregatedAgents}
|
agents={aggregatedAgents}
|
||||||
isRefreshing={isRevalidating}
|
isRefreshing={isRevalidating}
|
||||||
@@ -578,10 +680,15 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
|
|
||||||
{/* Content Area with Keyboard Animation */}
|
{/* Content Area with Keyboard Animation */}
|
||||||
<View style={styles.contentContainer}>
|
<View style={styles.contentContainer}>
|
||||||
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
|
<ReanimatedAnimated.View
|
||||||
|
style={[styles.content, animatedKeyboardStyle]}
|
||||||
|
>
|
||||||
{isInitializing ? (
|
{isInitializing ? (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
<ActivityIndicator
|
||||||
|
size="large"
|
||||||
|
color={theme.colors.primary}
|
||||||
|
/>
|
||||||
<Text style={styles.loadingText}>Loading agent...</Text>
|
<Text style={styles.loadingText}>Loading agent...</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@@ -656,13 +763,17 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
size="small"
|
size="small"
|
||||||
color={theme.colors.mutedForeground}
|
color={theme.colors.mutedForeground}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.menuMetaPendingText}>Fetching…</Text>
|
<Text style={styles.menuMetaPendingText}>
|
||||||
|
Fetching…
|
||||||
|
</Text>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.menuMetaValue,
|
styles.menuMetaValue,
|
||||||
branchStatus === "error" ? styles.menuMetaValueError : null,
|
branchStatus === "error"
|
||||||
|
? styles.menuMetaValueError
|
||||||
|
: null,
|
||||||
]}
|
]}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
ellipsizeMode="middle"
|
ellipsizeMode="middle"
|
||||||
@@ -707,7 +818,10 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
{child.status === "running" ? (
|
{child.status === "running" ? (
|
||||||
<View style={styles.menuSubAgentDot} />
|
<View style={styles.menuSubAgentDot} />
|
||||||
) : null}
|
) : null}
|
||||||
<ChevronRight size={16} color={theme.colors.mutedForeground} />
|
<ChevronRight
|
||||||
|
size={16}
|
||||||
|
color={theme.colors.mutedForeground}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
@@ -718,19 +832,19 @@ function AgentScreenContent({ serverId, agentId, onBack }: AgentScreenContentPro
|
|||||||
<View style={styles.menuDivider} />
|
<View style={styles.menuDivider} />
|
||||||
|
|
||||||
<Pressable onPress={handleViewChanges} style={styles.menuItem}>
|
<Pressable onPress={handleViewChanges} style={styles.menuItem}>
|
||||||
<GitBranch size={16} color={theme.colors.foreground} />
|
<GitBranch size={16} color={theme.colors.foreground} />
|
||||||
<Text style={styles.menuItemText}>View Changes</Text>
|
<Text style={styles.menuItemText}>View Changes</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable onPress={handleBrowseFiles} style={styles.menuItem}>
|
<Pressable onPress={handleBrowseFiles} style={styles.menuItem}>
|
||||||
<Folder size={16} color={theme.colors.foreground} />
|
<Folder size={16} color={theme.colors.foreground} />
|
||||||
<Text style={styles.menuItemText}>Browse Files</Text>
|
<Text style={styles.menuItemText}>Browse Files</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable onPress={handleImportAgent} style={styles.menuItem}>
|
<Pressable onPress={handleImportAgent} style={styles.menuItem}>
|
||||||
<Download size={16} color={theme.colors.foreground} />
|
<Download size={16} color={theme.colors.foreground} />
|
||||||
<Text style={styles.menuItemText}>Import Agent</Text>
|
<Text style={styles.menuItemText}>Import Agent</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable onPress={handleCreateNewAgent} style={styles.menuItem}>
|
<Pressable onPress={handleCreateNewAgent} style={styles.menuItem}>
|
||||||
<PlusCircle size={16} color={theme.colors.foreground} />
|
<PlusIcon size={16} color={theme.colors.foreground} />
|
||||||
<Text style={styles.menuItemText}>New Agent</Text>
|
<Text style={styles.menuItemText}>New Agent</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -784,10 +898,12 @@ function AgentSessionUnavailableState({
|
|||||||
<BackHeader title="Agent" onBack={onBack} />
|
<BackHeader title="Agent" onBack={onBack} />
|
||||||
<View style={styles.centerState}>
|
<View style={styles.centerState}>
|
||||||
<Text style={styles.errorText}>
|
<Text style={styles.errorText}>
|
||||||
Cannot open this agent because {serverLabel} is not configured on this device.
|
Cannot open this agent because {serverLabel} is not configured on
|
||||||
|
this device.
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.statusText}>
|
<Text style={styles.statusText}>
|
||||||
Add the host in Settings or open an agent on a configured server to continue.
|
Add the host in Settings or open an agent on a configured server to
|
||||||
|
continue.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -803,8 +919,12 @@ function AgentSessionUnavailableState({
|
|||||||
{isConnecting ? (
|
{isConnecting ? (
|
||||||
<>
|
<>
|
||||||
<ActivityIndicator size="large" />
|
<ActivityIndicator size="large" />
|
||||||
<Text style={styles.loadingText}>Connecting to {serverLabel}...</Text>
|
<Text style={styles.loadingText}>
|
||||||
<Text style={styles.statusText}>We will show this agent once the host is online.</Text>
|
Connecting to {serverLabel}...
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statusText}>
|
||||||
|
We will show this agent once the host is online.
|
||||||
|
</Text>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -812,9 +932,12 @@ function AgentSessionUnavailableState({
|
|||||||
{serverLabel} is currently {connectionStatusLabel.toLowerCase()}.
|
{serverLabel} is currently {connectionStatusLabel.toLowerCase()}.
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.offlineDescription}>
|
<Text style={styles.offlineDescription}>
|
||||||
We'll reconnect automatically and show this agent as soon as the host comes back online.
|
We'll reconnect automatically and show this agent as soon as the
|
||||||
|
host comes back online.
|
||||||
</Text>
|
</Text>
|
||||||
{lastError ? <Text style={styles.offlineDetails}>{lastError}</Text> : null}
|
{lastError ? (
|
||||||
|
<Text style={styles.offlineDetails}>{lastError}</Text>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -837,6 +960,23 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
borderRightColor: theme.colors.border,
|
borderRightColor: theme.colors.border,
|
||||||
},
|
},
|
||||||
|
sidebarHeader: {
|
||||||
|
paddingHorizontal: theme.spacing[4],
|
||||||
|
paddingTop: theme.spacing[4],
|
||||||
|
paddingBottom: theme.spacing[2],
|
||||||
|
},
|
||||||
|
newAgentButton: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
paddingVertical: theme.spacing[3],
|
||||||
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
},
|
||||||
|
newAgentButtonText: {
|
||||||
|
fontSize: theme.fontSize.base,
|
||||||
|
fontWeight: theme.fontWeight.normal,
|
||||||
|
},
|
||||||
agentPanel: {
|
agentPanel: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user