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:
Mohamed Boudra
2026-02-18 18:27:03 +07:00
committed by GitHub
6 changed files with 329 additions and 37 deletions
@@ -86,13 +86,15 @@ export function useAllAgentsList(options?: {
const snapshot = entry.agent;
const normalized = normalizeAgentSnapshot(snapshot, serverId);
const live = liveAgents?.get(snapshot.id);
list.push(
toAggregatedAgent({
source: live ?? normalized,
serverId,
serverLabel,
})
);
const aggregated = toAggregatedAgent({
source: live ?? normalized,
serverId,
serverLabel,
});
if (aggregated.archivedAt) {
continue;
}
list.push(aggregated);
}
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);
});
});
+157
View File
@@ -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,
};