Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { apiClient } from "@/lib/trpc/client";
import { useAttachmentsSheet } from "@/screens/(authenticated)/hooks/useAttachmentsSheet";
import { useComposerDraft } from "@/screens/(authenticated)/hooks/useComposerDraft";
import { useCreateTerminalWorkspace } from "@/screens/(authenticated)/hooks/useCreateTerminalWorkspace";
import { useHostAgentConfigs } from "@/screens/(authenticated)/hooks/useHostAgentConfigs";
import { usePasteAttachments } from "@/screens/(authenticated)/hooks/usePasteAttachments";
import { HOME_DRAFT_KEY } from "@/screens/(authenticated)/stores/composerDraftsStore";
import {
Expand Down Expand Up @@ -96,19 +97,12 @@ export function NewChatWidget({

const createTerminalWorkspace = useCreateTerminalWorkspace();
const createCloudWorkspace = useCreateCloudWorkspace();
const { data: agentConfigs } = useQuery({
queryKey: ["host-agent-configs", selectedTarget?.machineId ?? null],
const { data: agentConfigs } = useHostAgentConfigs({
machineId: selectedTarget?.machineId ?? null,
hostUrl: selectedTarget?.hostUrl ?? null,
// A cloud target has no host to list agents from, and create doesn't
// launch one (the prompt only feeds the auto-name).
enabled: selectedTarget !== null && !isCloudTarget,
staleTime: 60_000,
networkMode: "always" as const,
queryFn: async () => {
if (!selectedTarget) return [];
return getHostServiceClientByUrl(
selectedTarget.hostUrl,
).settings.agentConfigs.list.query();
},
enabled: !isCloudTarget,
});
const selectedAgent = agentConfigs?.find(
(config) => config.presetId === agentId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import Ionicons from "@expo/vector-icons/Ionicons";
import { useQuery } from "@tanstack/react-query";
import { Stack, useRouter } from "expo-router";
import { SquareTerminal } from "lucide-react-native";
import { Image, Pressable, ScrollView } from "react-native";
import { Text } from "@/components/ui/text";
import { useTheme } from "@/hooks/useTheme";
import { agentIconSource } from "@/lib/agent-icons";
import { getHostServiceClientByUrl } from "@/lib/host-service/client";
import { useNewChatTargets } from "@/screens/(authenticated)/(home)/home/components/NewChatWidget/hooks/useNewChatTargets";
import { useNewSessionPreferencesStore } from "@/screens/(authenticated)/(home)/home/components/NewChatWidget/stores/newSessionPreferencesStore";
import { useHostAgentConfigs } from "@/screens/(authenticated)/hooks/useHostAgentConfigs";

export function AgentMark({
agentId,
Expand Down Expand Up @@ -44,17 +43,9 @@ export function AgentPickerScreen() {
const selectedTarget =
targets.find((target) => target.key === targetKey) ?? defaultTarget;

const configsQuery = useQuery({
queryKey: ["host-agent-configs", selectedTarget?.machineId ?? null],
enabled: selectedTarget !== null,
staleTime: 60_000,
networkMode: "always" as const,
queryFn: async () => {
if (!selectedTarget) return [];
return getHostServiceClientByUrl(
selectedTarget.hostUrl,
).settings.agentConfigs.list.query();
},
const configsQuery = useHostAgentConfigs({
machineId: selectedTarget?.machineId ?? null,
hostUrl: selectedTarget?.hostUrl ?? null,
});
const configs = configsQuery.data ?? [];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
getHostAgentConfigsQueryKey,
useHostAgentConfigs,
} from "./useHostAgentConfigs";
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useQuery } from "@tanstack/react-query";
import { getHostServiceClientByUrl } from "@/lib/host-service/client";

export function getHostAgentConfigsQueryKey(machineId: string | null) {
return ["host-agent-configs", machineId] as const;
}

/**
* The target host's configured agents, fetched live so the list matches what
* the host can actually run. Edits usually happen on the desktop while this
* app is backgrounded, so focus refetches unconditionally — returning to the
* app is the earliest moment the fresh list can matter.
*/
export function useHostAgentConfigs({
machineId,
hostUrl,
enabled = true,
}: {
machineId: string | null;
hostUrl: string | null;
enabled?: boolean;
}) {
return useQuery({
queryKey: getHostAgentConfigsQueryKey(machineId),
enabled: enabled && hostUrl !== null,
staleTime: 60_000,
refetchOnWindowFocus: "always" as const,
networkMode: "always" as const,
queryFn: async () => {
if (!hostUrl) return [];
return getHostServiceClientByUrl(
hostUrl,
).settings.agentConfigs.list.query();
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,17 +226,36 @@ export function WorkspaceScreen() {
const hostCompatibility = useHostCompatibility(hostUrl);

// The + sheet lands back here via dismissTo with the new session in
// ?tab= — adopt it over any manual pick so the fresh tab activates.
useEffect(() => {
if (params.tab) setPickedTerminalId(params.tab);
}, [params.tab]);

// Pin whatever ended up active, including the implicit first row: without
// this, reordering in the sessions sheet moves a different row into first
// place and the terminal you're watching switches out from under you.
// ?tab= — adopt it once its row arrives, since the terminals query hasn't
// heard of the session when the sheet closes. Otherwise pin whatever ended
// up active, including the implicit first row: without the pin, reordering
// in the sessions sheet moves a different row into first place and the
// terminal you're watching switches out from under you. One effect, so the
// adoption outranks the pin — as separate effects the pin re-asserted the
// old tab in the same commit the fresh row arrived, and the new session
// never activated.
const adoptedTabRef = useRef<string | null>(null);
// A manual pick while the ?tab= row is still pending consumes the
// adoption — the arriving row must not yank the user off their choice.
const pickTerminal = useCallback(
(terminalId: string) => {
adoptedTabRef.current = params.tab ?? null;
setPickedTerminalId(terminalId);
},
[params.tab],
);
useEffect(() => {
if (
params.tab &&
adoptedTabRef.current !== params.tab &&
rows.some((row) => row.terminalId === params.tab)
) {
adoptedTabRef.current = params.tab;
setPickedTerminalId(params.tab);
return;
}
if (activeTerminalId) setPickedTerminalId(activeTerminalId);
}, [activeTerminalId]);
}, [params.tab, rows, activeTerminalId]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

// Port of desktop's useClearActivePaneAttention: viewing the tab clears
// its `review` state by advancing the seen mark to the binding's last
Expand Down Expand Up @@ -473,7 +492,7 @@ export function WorkspaceScreen() {
<TerminalTabs
rows={rows}
activeTerminalId={activeTerminalId}
onSelect={setPickedTerminalId}
onSelect={pickTerminal}
onAdd={openAddMenu}
onManage={openSessions}
onClose={killTerminal}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useQueryClient } from "@tanstack/react-query";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { SquareTerminal } from "lucide-react-native";
import { useState } from "react";
Expand All @@ -13,6 +13,7 @@ import {
import { getHostTerminalsQueryKey } from "@/screens/(authenticated)/(home)/home/hooks/useHostTerminals";
import { AgentMark } from "@/screens/(authenticated)/(home)/new-session/agent";
import { ListRow } from "@/screens/(authenticated)/components/ListRow";
import { useHostAgentConfigs } from "@/screens/(authenticated)/hooks/useHostAgentConfigs";

/**
* Bottom sheet for the tab strip's + — the host's agent presets plus a plain
Expand All @@ -28,17 +29,9 @@ export function NewSessionSheet() {
? hostServiceUrl(host.organizationId, host.machineId)
: null;

const presetsQuery = useQuery({
queryKey: ["host-agent-configs", host?.machineId ?? null],
enabled: hostUrl !== null,
staleTime: 60_000,
networkMode: "always" as const,
queryFn: async () => {
if (!hostUrl) return [];
return getHostServiceClientByUrl(
hostUrl,
).settings.agentConfigs.list.query();
},
const presetsQuery = useHostAgentConfigs({
machineId: host?.machineId ?? null,
hostUrl,
});
const presets = presetsQuery.data ?? [];

Expand Down
14 changes: 13 additions & 1 deletion apps/mobile/screens/RootLayout/RootLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { PortalHost } from "@rn-primitives/portal";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
focusManager,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { Stack } from "expo-router";
import { ThemeProvider } from "expo-router/react-navigation";
import { AppState } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { Uniwind } from "uniwind";
import { useSession } from "@/lib/auth/client";
Expand All @@ -14,6 +19,13 @@ import { PostHogProvider } from "./providers/PostHogProvider";

const queryClient = new QueryClient();

// React Query cannot see app focus on native, so without this no query ever
// refetches on returning to the foreground — data went stale for the whole
// app session.
AppState.addEventListener("change", (status) => {
focusManager.setFocused(status === "active");
});

export function RootLayout() {
const { data: session, isPending } = useSession();

Expand Down
Loading