Merge pull request #61 from getpaseo/fix/agent-archive-loading-state
Fix archive UX with shared pending state and agent-screen overlay
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
Pressable,
|
Pressable,
|
||||||
Image,
|
Image,
|
||||||
Platform,
|
Platform,
|
||||||
|
ActivityIndicator,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useQueries } from "@tanstack/react-query";
|
import { useQueries } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
@@ -49,6 +50,7 @@ import { parseSidebarAgentKey } from "@/utils/sidebar-shortcuts";
|
|||||||
import { parseRepoNameFromRemoteUrl } from "@/utils/agent-grouping";
|
import { parseRepoNameFromRemoteUrl } from "@/utils/agent-grouping";
|
||||||
import { formatTimeAgo } from "@/utils/time";
|
import { formatTimeAgo } from "@/utils/time";
|
||||||
import { isSidebarActiveAgent } from "@/utils/sidebar-agent-state";
|
import { isSidebarActiveAgent } from "@/utils/sidebar-agent-state";
|
||||||
|
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
||||||
|
|
||||||
type EntryData = SidebarAgentListEntry;
|
type EntryData = SidebarAgentListEntry;
|
||||||
|
|
||||||
@@ -127,10 +129,11 @@ interface SidebarAgentRowProps {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
isInSelectionMode: boolean;
|
isInSelectionMode: boolean;
|
||||||
isBatchSelected: boolean;
|
isBatchSelected: boolean;
|
||||||
|
isArchiving: boolean;
|
||||||
shortcutNumber: number | null;
|
shortcutNumber: number | null;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
onLongPress: () => void;
|
onLongPress: () => void;
|
||||||
onArchive: () => void;
|
onArchive: () => Promise<void>;
|
||||||
onToggleBatch: () => void;
|
onToggleBatch: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +156,7 @@ function SidebarAgentRow({
|
|||||||
isSelected,
|
isSelected,
|
||||||
isInSelectionMode,
|
isInSelectionMode,
|
||||||
isBatchSelected,
|
isBatchSelected,
|
||||||
|
isArchiving,
|
||||||
shortcutNumber,
|
shortcutNumber,
|
||||||
onPress,
|
onPress,
|
||||||
onLongPress,
|
onLongPress,
|
||||||
@@ -176,7 +180,7 @@ function SidebarAgentRow({
|
|||||||
const showArchive =
|
const showArchive =
|
||||||
!isInSelectionMode &&
|
!isInSelectionMode &&
|
||||||
shortcutNumber === null &&
|
shortcutNumber === null &&
|
||||||
(isHovered || isArchiveHovered || isArchiveConfirmVisible);
|
(isHovered || isArchiveHovered || isArchiveConfirmVisible || isArchiving);
|
||||||
|
|
||||||
const clearHoverOutTimeout = useCallback(() => {
|
const clearHoverOutTimeout = useCallback(() => {
|
||||||
if (!hoverOutTimeoutRef.current) {
|
if (!hoverOutTimeoutRef.current) {
|
||||||
@@ -190,6 +194,12 @@ function SidebarAgentRow({
|
|||||||
return () => clearHoverOutTimeout();
|
return () => clearHoverOutTimeout();
|
||||||
}, [clearHoverOutTimeout]);
|
}, [clearHoverOutTimeout]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isArchiving) {
|
||||||
|
setIsArchiveConfirmVisible(false);
|
||||||
|
}
|
||||||
|
}, [isArchiving]);
|
||||||
|
|
||||||
const handleHoverIn = useCallback(() => {
|
const handleHoverIn = useCallback(() => {
|
||||||
clearHoverOutTimeout();
|
clearHoverOutTimeout();
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
@@ -270,22 +280,27 @@ function SidebarAgentRow({
|
|||||||
}}
|
}}
|
||||||
onPress={(event) => {
|
onPress={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
if (isArchiving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isArchiveConfirmVisible) {
|
if (!isArchiveConfirmVisible) {
|
||||||
setIsArchiveConfirmVisible(true);
|
setIsArchiveConfirmVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onArchive();
|
void onArchive();
|
||||||
setIsArchiveConfirmVisible(false);
|
|
||||||
}}
|
}}
|
||||||
style={styles.archiveButton}
|
style={styles.archiveButton}
|
||||||
|
disabled={isArchiving}
|
||||||
testID={
|
testID={
|
||||||
isArchiveConfirmVisible
|
isArchiveConfirmVisible || isArchiving
|
||||||
? `agent-archive-confirm-${entry.agent.serverId}-${entry.agent.id}`
|
? `agent-archive-confirm-${entry.agent.serverId}-${entry.agent.id}`
|
||||||
: `agent-archive-${entry.agent.serverId}-${entry.agent.id}`
|
: `agent-archive-${entry.agent.serverId}-${entry.agent.id}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ hovered: archiveHovered }) =>
|
{({ hovered: archiveHovered }) =>
|
||||||
isArchiveConfirmVisible ? (
|
isArchiving ? (
|
||||||
|
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||||
|
) : isArchiveConfirmVisible ? (
|
||||||
<Check size={12} color={theme.colors.foreground} />
|
<Check size={12} color={theme.colors.foreground} />
|
||||||
) : (
|
) : (
|
||||||
<Archive
|
<Archive
|
||||||
@@ -446,6 +461,7 @@ export function SidebarAgentList({
|
|||||||
|
|
||||||
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
||||||
const [selectedBatchKeys, setSelectedBatchKeys] = useState<Set<string>>(new Set());
|
const [selectedBatchKeys, setSelectedBatchKeys] = useState<Set<string>>(new Set());
|
||||||
|
const { archiveAgent, isArchivingAgent } = useArchiveAgent();
|
||||||
|
|
||||||
const altDown = useKeyboardShortcutsStore((s) => s.altDown);
|
const altDown = useKeyboardShortcutsStore((s) => s.altDown);
|
||||||
const cmdOrCtrlDown = useKeyboardShortcutsStore((s) => s.cmdOrCtrlDown);
|
const cmdOrCtrlDown = useKeyboardShortcutsStore((s) => s.cmdOrCtrlDown);
|
||||||
@@ -621,15 +637,17 @@ export function SidebarAgentList({
|
|||||||
setSelectedBatchKeys(new Set([key]));
|
setSelectedBatchKeys(new Set([key]));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleArchiveSingle = useCallback((entry: SidebarAgentListEntry) => {
|
const handleArchiveSingle = useCallback(
|
||||||
const client = useSessionStore.getState().sessions[entry.agent.serverId]?.client ?? null;
|
async (entry: SidebarAgentListEntry): Promise<void> => {
|
||||||
if (!client) {
|
await archiveAgent({
|
||||||
return;
|
serverId: entry.agent.serverId,
|
||||||
}
|
agentId: entry.agent.id,
|
||||||
void client.archiveAgent(entry.agent.id).catch((error) => {
|
}).catch((error) => {
|
||||||
console.warn("[archive_agent] failed", error);
|
console.warn("[archive_agent] failed", error);
|
||||||
});
|
});
|
||||||
}, []);
|
},
|
||||||
|
[archiveAgent]
|
||||||
|
);
|
||||||
|
|
||||||
const handleArchiveBatch = useCallback(() => {
|
const handleArchiveBatch = useCallback(() => {
|
||||||
if (selectedBatchKeys.size === 0) {
|
if (selectedBatchKeys.size === 0) {
|
||||||
@@ -638,23 +656,18 @@ export function SidebarAgentList({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requests: Promise<void>[] = [];
|
const requests: Promise<void>[] = [];
|
||||||
const store = useSessionStore.getState();
|
|
||||||
for (const key of selectedBatchKeys) {
|
for (const key of selectedBatchKeys) {
|
||||||
const parsed = parseSidebarAgentKey(key);
|
const parsed = parseSidebarAgentKey(key);
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const client = store.sessions[parsed.serverId]?.client ?? null;
|
|
||||||
if (!client) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
requests.push(
|
requests.push(
|
||||||
client
|
archiveAgent({
|
||||||
.archiveAgent(parsed.agentId)
|
serverId: parsed.serverId,
|
||||||
.then(() => undefined)
|
agentId: parsed.agentId,
|
||||||
.catch((error) => {
|
}).catch((error) => {
|
||||||
console.warn("[archive_agent_batch] failed", { key, error });
|
console.warn("[archive_agent_batch] failed", { key, error });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,7 +675,7 @@ export function SidebarAgentList({
|
|||||||
setSelectedBatchKeys(new Set());
|
setSelectedBatchKeys(new Set());
|
||||||
setIsSelectionMode(false);
|
setIsSelectionMode(false);
|
||||||
});
|
});
|
||||||
}, [selectedBatchKeys]);
|
}, [archiveAgent, selectedBatchKeys]);
|
||||||
|
|
||||||
const handleSelectionBack = useCallback(() => {
|
const handleSelectionBack = useCallback(() => {
|
||||||
setSelectedBatchKeys(new Set());
|
setSelectedBatchKeys(new Set());
|
||||||
@@ -687,6 +700,10 @@ export function SidebarAgentList({
|
|||||||
isSelected={selectedAgentId === key}
|
isSelected={selectedAgentId === key}
|
||||||
isInSelectionMode={isSelectionMode}
|
isInSelectionMode={isSelectionMode}
|
||||||
isBatchSelected={selectedBatchKeys.has(key)}
|
isBatchSelected={selectedBatchKeys.has(key)}
|
||||||
|
isArchiving={isArchivingAgent({
|
||||||
|
serverId: item.agent.serverId,
|
||||||
|
agentId: item.agent.id,
|
||||||
|
})}
|
||||||
shortcutNumber={
|
shortcutNumber={
|
||||||
showShortcutBadges ? (shortcutIndexByAgentKey.get(key) ?? null) : null
|
showShortcutBadges ? (shortcutIndexByAgentKey.get(key) ?? null) : null
|
||||||
}
|
}
|
||||||
@@ -711,6 +728,7 @@ export function SidebarAgentList({
|
|||||||
handleAgentLongPress,
|
handleAgentLongPress,
|
||||||
handleAgentPress,
|
handleAgentPress,
|
||||||
handleArchiveSingle,
|
handleArchiveSingle,
|
||||||
|
isArchivingAgent,
|
||||||
isSelectionMode,
|
isSelectionMode,
|
||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
selectedBatchKeys,
|
selectedBatchKeys,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useRef, ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
import { useRef, ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { AppState, Platform } from "react-native";
|
import { AppState, Platform } from "react-native";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useDaemonClient } from "@/hooks/use-daemon-client";
|
import { useDaemonClient } from "@/hooks/use-daemon-client";
|
||||||
import { useAudioPlayer } from "@/hooks/use-audio-player";
|
import { useAudioPlayer } from "@/hooks/use-audio-player";
|
||||||
import { useClientActivity } from "@/hooks/use-client-activity";
|
import { useClientActivity } from "@/hooks/use-client-activity";
|
||||||
import { usePushTokenRegistration } from "@/hooks/use-push-token-registration";
|
import { usePushTokenRegistration } from "@/hooks/use-push-token-registration";
|
||||||
|
import { clearArchiveAgentPending } from "@/hooks/use-archive-agent";
|
||||||
import {
|
import {
|
||||||
applyStreamEvent,
|
applyStreamEvent,
|
||||||
generateMessageId,
|
generateMessageId,
|
||||||
@@ -219,6 +220,7 @@ export function SessionProvider({
|
|||||||
activeConnection,
|
activeConnection,
|
||||||
daemonPublicKeyB64,
|
daemonPublicKeyB64,
|
||||||
}: SessionProviderProps) {
|
}: SessionProviderProps) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const client = useDaemonClient(serverUrl, { daemonPublicKeyB64 });
|
const client = useDaemonClient(serverUrl, { daemonPublicKeyB64 });
|
||||||
const [connectionSnapshot, setConnectionSnapshot] =
|
const [connectionSnapshot, setConnectionSnapshot] =
|
||||||
useState<DaemonConnectionSnapshot>(() =>
|
useState<DaemonConnectionSnapshot>(() =>
|
||||||
@@ -489,6 +491,7 @@ export function SessionProvider({
|
|||||||
const agentId = update.agentId;
|
const agentId = update.agentId;
|
||||||
previousAgentStatusRef.current.delete(agentId);
|
previousAgentStatusRef.current.delete(agentId);
|
||||||
pendingAgentUpdatesRef.current.delete(agentId);
|
pendingAgentUpdatesRef.current.delete(agentId);
|
||||||
|
clearArchiveAgentPending({ queryClient, serverId, agentId });
|
||||||
|
|
||||||
setAgents(serverId, (prev) => {
|
setAgents(serverId, (prev) => {
|
||||||
if (!prev.has(agentId)) {
|
if (!prev.has(agentId)) {
|
||||||
@@ -552,6 +555,14 @@ export function SessionProvider({
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (agent.archivedAt) {
|
||||||
|
clearArchiveAgentPending({
|
||||||
|
queryClient,
|
||||||
|
serverId,
|
||||||
|
agentId: agent.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Update agentLastActivity slice (top-level)
|
// Update agentLastActivity slice (top-level)
|
||||||
setAgentLastActivity(agent.id, agent.lastActivityAt);
|
setAgentLastActivity(agent.id, agent.lastActivityAt);
|
||||||
|
|
||||||
@@ -595,6 +606,7 @@ export function SessionProvider({
|
|||||||
previousAgentStatusRef.current.set(agent.id, agent.status);
|
previousAgentStatusRef.current.set(agent.id, agent.status);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
queryClient,
|
||||||
serverId,
|
serverId,
|
||||||
setAgents,
|
setAgents,
|
||||||
setAgentLastActivity,
|
setAgentLastActivity,
|
||||||
@@ -1277,6 +1289,7 @@ export function SessionProvider({
|
|||||||
const { agentId } = message.payload;
|
const { agentId } = message.payload;
|
||||||
console.log("[Session] Agent deleted:", agentId);
|
console.log("[Session] Agent deleted:", agentId);
|
||||||
pendingAgentUpdatesRef.current.delete(agentId);
|
pendingAgentUpdatesRef.current.delete(agentId);
|
||||||
|
clearArchiveAgentPending({ queryClient, serverId, agentId });
|
||||||
|
|
||||||
setAgents(serverId, (prev) => {
|
setAgents(serverId, (prev) => {
|
||||||
if (!prev.has(agentId)) {
|
if (!prev.has(agentId)) {
|
||||||
@@ -1358,6 +1371,7 @@ export function SessionProvider({
|
|||||||
}
|
}
|
||||||
const { agentId, archivedAt } = message.payload;
|
const { agentId, archivedAt } = message.payload;
|
||||||
console.log("[Session] Agent archived:", agentId);
|
console.log("[Session] Agent archived:", agentId);
|
||||||
|
clearArchiveAgentPending({ queryClient, serverId, agentId });
|
||||||
|
|
||||||
setAgents(serverId, (prev) => {
|
setAgents(serverId, (prev) => {
|
||||||
const existing = prev.get(agentId);
|
const existing = prev.get(agentId);
|
||||||
@@ -1390,6 +1404,7 @@ export function SessionProvider({
|
|||||||
}, [
|
}, [
|
||||||
client,
|
client,
|
||||||
audioPlayer,
|
audioPlayer,
|
||||||
|
queryClient,
|
||||||
serverId,
|
serverId,
|
||||||
setIsPlayingAudio,
|
setIsPlayingAudio,
|
||||||
setMessages,
|
setMessages,
|
||||||
|
|||||||
@@ -86,13 +86,15 @@ export function useAllAgentsList(options?: {
|
|||||||
const snapshot = entry.agent;
|
const snapshot = entry.agent;
|
||||||
const normalized = normalizeAgentSnapshot(snapshot, serverId);
|
const normalized = normalizeAgentSnapshot(snapshot, serverId);
|
||||||
const live = liveAgents?.get(snapshot.id);
|
const live = liveAgents?.get(snapshot.id);
|
||||||
list.push(
|
const aggregated = toAggregatedAgent({
|
||||||
toAggregatedAgent({
|
source: live ?? normalized,
|
||||||
source: live ?? normalized,
|
serverId,
|
||||||
serverId,
|
serverLabel,
|
||||||
serverLabel,
|
});
|
||||||
})
|
if (aggregated.archivedAt) {
|
||||||
);
|
continue;
|
||||||
|
}
|
||||||
|
list.push(aggregated);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.sort((left, right) => {
|
list.sort((left, right) => {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { QueryClient } from "@tanstack/react-query";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { __private__ } from "./use-archive-agent";
|
||||||
|
|
||||||
|
describe("useArchiveAgent", () => {
|
||||||
|
it("tracks pending archive state in shared react-query cache", () => {
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
__private__.isAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: "server-a",
|
||||||
|
agentId: "agent-1",
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
__private__.setAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: "server-a",
|
||||||
|
agentId: "agent-1",
|
||||||
|
isArchiving: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
__private__.isAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: "server-a",
|
||||||
|
agentId: "agent-1",
|
||||||
|
})
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
__private__.isAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: "server-a",
|
||||||
|
agentId: "agent-2",
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
__private__.setAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: "server-a",
|
||||||
|
agentId: "agent-1",
|
||||||
|
isArchiving: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
__private__.isAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: "server-a",
|
||||||
|
agentId: "agent-1",
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { useMutation, useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query";
|
||||||
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
|
||||||
|
export const ARCHIVE_AGENT_PENDING_QUERY_KEY = ["archive-agent-pending"] as const;
|
||||||
|
|
||||||
|
export interface ArchiveAgentInput {
|
||||||
|
serverId: string;
|
||||||
|
agentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArchiveAgentPendingState = Record<string, true>;
|
||||||
|
|
||||||
|
interface SetAgentArchivingInput extends ArchiveAgentInput {
|
||||||
|
queryClient: QueryClient;
|
||||||
|
isArchiving: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IsAgentArchivingInput extends ArchiveAgentInput {
|
||||||
|
queryClient: QueryClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArchiveKey(input: ArchiveAgentInput): string {
|
||||||
|
const serverId = input.serverId.trim();
|
||||||
|
const agentId = input.agentId.trim();
|
||||||
|
if (!serverId || !agentId) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return `${serverId}:${agentId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPendingState(queryClient: QueryClient): ArchiveAgentPendingState {
|
||||||
|
return (
|
||||||
|
queryClient.getQueryData<ArchiveAgentPendingState>(
|
||||||
|
ARCHIVE_AGENT_PENDING_QUERY_KEY
|
||||||
|
) ?? {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAgentArchiving(input: SetAgentArchivingInput): void {
|
||||||
|
const key = toArchiveKey(input);
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.queryClient.setQueryData<ArchiveAgentPendingState>(
|
||||||
|
ARCHIVE_AGENT_PENDING_QUERY_KEY,
|
||||||
|
(current) => {
|
||||||
|
const state = current ?? {};
|
||||||
|
if (input.isArchiving) {
|
||||||
|
if (state[key]) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return { ...state, [key]: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state[key]) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = { ...state };
|
||||||
|
delete next[key];
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAgentArchiving(input: IsAgentArchivingInput): boolean {
|
||||||
|
const key = toArchiveKey(input);
|
||||||
|
if (!key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Boolean(readPendingState(input.queryClient)[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearArchiveAgentPending(input: IsAgentArchivingInput): void {
|
||||||
|
setAgentArchiving({
|
||||||
|
...input,
|
||||||
|
isArchiving: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useArchiveAgent() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const pendingQuery = useQuery({
|
||||||
|
queryKey: ARCHIVE_AGENT_PENDING_QUERY_KEY,
|
||||||
|
queryFn: async (): Promise<ArchiveAgentPendingState> => ({}),
|
||||||
|
initialData: {} as ArchiveAgentPendingState,
|
||||||
|
staleTime: Infinity,
|
||||||
|
gcTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const archiveMutation = useMutation({
|
||||||
|
mutationFn: async (input: ArchiveAgentInput) => {
|
||||||
|
const client = useSessionStore.getState().sessions[input.serverId]?.client ?? null;
|
||||||
|
if (!client) {
|
||||||
|
throw new Error("Daemon client not available");
|
||||||
|
}
|
||||||
|
await client.archiveAgent(input.agentId);
|
||||||
|
},
|
||||||
|
onMutate: (input) => {
|
||||||
|
setAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: input.serverId,
|
||||||
|
agentId: input.agentId,
|
||||||
|
isArchiving: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (_error, input) => {
|
||||||
|
clearArchiveAgentPending({
|
||||||
|
queryClient,
|
||||||
|
serverId: input.serverId,
|
||||||
|
agentId: input.agentId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const archiveAgent = useCallback(
|
||||||
|
async (input: ArchiveAgentInput): Promise<void> => {
|
||||||
|
if (
|
||||||
|
isAgentArchiving({
|
||||||
|
queryClient,
|
||||||
|
serverId: input.serverId,
|
||||||
|
agentId: input.agentId,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await archiveMutation.mutateAsync(input);
|
||||||
|
},
|
||||||
|
[archiveMutation, queryClient]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isArchivingAgent = useCallback(
|
||||||
|
(input: ArchiveAgentInput): boolean => {
|
||||||
|
const key = toArchiveKey(input);
|
||||||
|
if (!key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Boolean((pendingQuery.data ?? {})[key]);
|
||||||
|
},
|
||||||
|
[pendingQuery.data]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
archiveAgent,
|
||||||
|
isArchivingAgent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const __private__ = {
|
||||||
|
toArchiveKey,
|
||||||
|
readPendingState,
|
||||||
|
setAgentArchiving,
|
||||||
|
isAgentArchiving,
|
||||||
|
};
|
||||||
@@ -66,6 +66,7 @@ import {
|
|||||||
useCheckoutStatusQuery,
|
useCheckoutStatusQuery,
|
||||||
} from "@/hooks/use-checkout-status-query";
|
} from "@/hooks/use-checkout-status-query";
|
||||||
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
||||||
|
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
||||||
import { useToast } from "@/contexts/toast-context";
|
import { useToast } from "@/contexts/toast-context";
|
||||||
import { getInitDeferred, getInitKey } from "@/utils/agent-initialization";
|
import { getInitDeferred, getInitKey } from "@/utils/agent-initialization";
|
||||||
import {
|
import {
|
||||||
@@ -221,6 +222,7 @@ function AgentScreenContent({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const resolvedAgentId = agentId;
|
const resolvedAgentId = agentId;
|
||||||
|
const { isArchivingAgent } = useArchiveAgent();
|
||||||
|
|
||||||
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
||||||
|
|
||||||
@@ -605,6 +607,10 @@ function AgentScreenContent({
|
|||||||
}, [showConnectedNotice]);
|
}, [showConnectedNotice]);
|
||||||
|
|
||||||
const isGitCheckout = activeExplorerCheckout?.isGit ?? false;
|
const isGitCheckout = activeExplorerCheckout?.isGit ?? false;
|
||||||
|
const isArchivingCurrentAgent = Boolean(
|
||||||
|
resolvedAgentId &&
|
||||||
|
isArchivingAgent({ serverId, agentId: resolvedAgentId })
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!resolvedAgentId) {
|
if (!resolvedAgentId) {
|
||||||
@@ -985,7 +991,10 @@ function AgentScreenContent({
|
|||||||
|
|
||||||
const mainContent = (
|
const mainContent = (
|
||||||
<View style={styles.outerContainer}>
|
<View style={styles.outerContainer}>
|
||||||
<FileDropZone onFilesDropped={handleFilesDropped} disabled={isInitializing || shouldBlockForHistorySync}>
|
<FileDropZone
|
||||||
|
onFilesDropped={handleFilesDropped}
|
||||||
|
disabled={isInitializing || shouldBlockForHistorySync || isArchivingCurrentAgent}
|
||||||
|
>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<MenuHeader
|
<MenuHeader
|
||||||
@@ -1247,7 +1256,10 @@ function AgentScreenContent({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Agent Input Area */}
|
{/* Agent Input Area */}
|
||||||
{agent && resolvedAgentId && !shouldBlockForHistorySync && (
|
{agent &&
|
||||||
|
resolvedAgentId &&
|
||||||
|
!shouldBlockForHistorySync &&
|
||||||
|
!isArchivingCurrentAgent && (
|
||||||
<AgentInputArea
|
<AgentInputArea
|
||||||
agentId={resolvedAgentId}
|
agentId={resolvedAgentId}
|
||||||
serverId={serverId}
|
serverId={serverId}
|
||||||
@@ -1268,6 +1280,16 @@ function AgentScreenContent({
|
|||||||
isGit={isGitCheckout}
|
isGit={isGitCheckout}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isArchivingCurrentAgent ? (
|
||||||
|
<View style={styles.archivingOverlay} testID="agent-archiving-overlay">
|
||||||
|
<ActivityIndicator size="large" color={theme.colors.foreground} />
|
||||||
|
<Text style={styles.archivingTitle}>Archiving agent...</Text>
|
||||||
|
<Text style={styles.archivingSubtitle}>
|
||||||
|
Please wait while we archive this agent.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1429,6 +1451,30 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
gap: 16,
|
gap: 16,
|
||||||
},
|
},
|
||||||
|
archivingOverlay: {
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
backgroundColor: "rgba(8, 10, 14, 0.86)",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingHorizontal: theme.spacing[8],
|
||||||
|
gap: theme.spacing[3],
|
||||||
|
zIndex: 50,
|
||||||
|
},
|
||||||
|
archivingTitle: {
|
||||||
|
fontSize: theme.fontSize.lg,
|
||||||
|
fontWeight: theme.fontWeight.semibold,
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
archivingSubtitle: {
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
loadingText: {
|
loadingText: {
|
||||||
fontSize: theme.fontSize.base,
|
fontSize: theme.fontSize.base,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
|
|||||||
Reference in New Issue
Block a user