Files
paseo/packages/app/src/hooks/use-archive-agent.ts
T
Mohamed Boudra e3962e1753 Merge origin/main into dev (second round)
Resolve 21 conflicts from latest main (provider profiles, Windows fixes, 0.1.55-rc.1).
Fix all type errors and syntax issues from merge artifacts.
2026-04-14 04:07:36 +07:00

363 lines
9.5 KiB
TypeScript

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;
}
export interface ArchivedAgentCloseResult {
agentId: string;
archivedAt: string;
}
type ArchiveAgentPendingState = Record<string, true>;
interface SetAgentArchivingInput extends ArchiveAgentInput {
queryClient: QueryClient;
isArchiving: boolean;
}
interface IsAgentArchivingInput extends ArchiveAgentInput {
queryClient: QueryClient;
}
interface AgentsListQueryData {
entries?: Array<{ agent?: { id?: string | null } | null } | null>;
}
interface ArchivedAgentListCacheSnapshot {
sidebarAgentsList: AgentsListQueryData | undefined;
allAgents: AgentsListQueryData | undefined;
}
interface ArchiveAgentMutationContext {
agent: ReturnType<typeof getStoredAgentSnapshot>;
lists: ArchivedAgentListCacheSnapshot;
}
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]);
}
function removeAgentFromListPayload<T extends AgentsListQueryData | undefined>(
payload: T,
agentId: string,
): T {
if (!payload || !Array.isArray(payload.entries) || !agentId) {
return payload;
}
const filtered = payload.entries.filter((entry) => entry?.agent?.id !== agentId);
if (filtered.length === payload.entries.length) {
return payload;
}
return {
...payload,
entries: filtered,
} as T;
}
function removeAgentFromCachedLists(queryClient: QueryClient, input: ArchiveAgentInput): void {
const agentId = input.agentId.trim();
if (!agentId) {
return;
}
queryClient.setQueryData<AgentsListQueryData | undefined>(
["sidebarAgentsList", input.serverId],
(current) => removeAgentFromListPayload(current, agentId),
);
queryClient.setQueryData<AgentsListQueryData | undefined>(
["allAgents", input.serverId],
(current) => removeAgentFromListPayload(current, agentId),
);
}
function getStoredAgentSnapshot(input: ArchiveAgentInput) {
return useSessionStore.getState().sessions[input.serverId]?.agents.get(input.agentId);
}
function restoreAgentSnapshot(
input: ArchiveAgentInput & { agent: ReturnType<typeof getStoredAgentSnapshot> },
): void {
const setAgents = useSessionStore.getState().setAgents;
setAgents(input.serverId, (prev) => {
const hasAgent = prev.has(input.agentId);
if (!input.agent) {
if (!hasAgent) {
return prev;
}
const next = new Map(prev);
next.delete(input.agentId);
return next;
}
const current = prev.get(input.agentId);
if (current === input.agent) {
return prev;
}
const next = new Map(prev);
next.set(input.agentId, input.agent);
return next;
});
}
function getArchivedAgentListCacheSnapshot(
queryClient: QueryClient,
serverId: string,
): ArchivedAgentListCacheSnapshot {
return {
sidebarAgentsList: queryClient.getQueryData<AgentsListQueryData | undefined>([
"sidebarAgentsList",
serverId,
]),
allAgents: queryClient.getQueryData<AgentsListQueryData | undefined>(["allAgents", serverId]),
};
}
function restoreCachedListSnapshot(
queryClient: QueryClient,
queryKey: readonly [string, string],
snapshot: AgentsListQueryData | undefined,
): void {
if (snapshot === undefined) {
queryClient.removeQueries({ queryKey, exact: true });
return;
}
queryClient.setQueryData(queryKey, snapshot);
}
function restoreArchivedAgentListCacheSnapshot(
queryClient: QueryClient,
serverId: string,
snapshot: ArchivedAgentListCacheSnapshot,
): void {
restoreCachedListSnapshot(
queryClient,
["sidebarAgentsList", serverId],
snapshot.sidebarAgentsList,
);
restoreCachedListSnapshot(queryClient, ["allAgents", serverId], snapshot.allAgents);
}
function markAgentArchivedInStore(input: ArchiveAgentInput & { archivedAt: string }): void {
const archivedAt = new Date(input.archivedAt);
if (Number.isNaN(archivedAt.getTime())) {
return;
}
const setAgents = useSessionStore.getState().setAgents;
setAgents(input.serverId, (prev) => {
const existing = prev.get(input.agentId);
if (!existing) {
return prev;
}
if (existing.archivedAt && existing.archivedAt.getTime() === archivedAt.getTime()) {
return prev;
}
const next = new Map(prev);
next.set(input.agentId, {
...existing,
archivedAt,
});
return next;
});
}
interface ApplyArchivedAgentCloseResultsInput {
queryClient: QueryClient;
serverId: string;
results: ArchivedAgentCloseResult[];
invalidateQueries?: boolean;
}
export function applyArchivedAgentCloseResults(input: ApplyArchivedAgentCloseResultsInput): void {
if (input.results.length === 0) {
return;
}
for (const result of input.results) {
markAgentArchivedInStore({
serverId: input.serverId,
agentId: result.agentId,
archivedAt: result.archivedAt,
});
removeAgentFromCachedLists(input.queryClient, {
serverId: input.serverId,
agentId: result.agentId,
});
}
if (input.invalidateQueries ?? true) {
void input.queryClient.invalidateQueries({
queryKey: ["sidebarAgentsList", input.serverId],
});
void input.queryClient.invalidateQueries({
queryKey: ["allAgents", input.serverId],
});
}
}
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): Promise<{ archivedAt: string }> => {
const client = useSessionStore.getState().sessions[input.serverId]?.client ?? null;
if (!client) {
throw new Error("Daemon client not available");
}
return await client.archiveAgent(input.agentId);
},
onMutate: (input) => {
const context: ArchiveAgentMutationContext = {
agent: getStoredAgentSnapshot(input),
lists: getArchivedAgentListCacheSnapshot(queryClient, input.serverId),
};
const archivedAt = new Date().toISOString();
applyArchivedAgentCloseResults({
queryClient,
serverId: input.serverId,
results: [{ agentId: input.agentId, archivedAt }],
invalidateQueries: false,
});
setAgentArchiving({
queryClient,
serverId: input.serverId,
agentId: input.agentId,
isArchiving: true,
});
return context;
},
onSuccess: (result, input) => {
markAgentArchivedInStore({
serverId: input.serverId,
agentId: input.agentId,
archivedAt: result.archivedAt,
});
},
onError: (_error, input, context) => {
if (!context) {
return;
}
restoreAgentSnapshot({
serverId: input.serverId,
agentId: input.agentId,
agent: context.agent,
});
restoreArchivedAgentListCacheSnapshot(queryClient, input.serverId, context.lists);
},
onSettled: (_result, _error, input) => {
clearArchiveAgentPending({
queryClient,
serverId: input.serverId,
agentId: input.agentId,
});
void queryClient.invalidateQueries({
queryKey: ["sidebarAgentsList", input.serverId],
});
void queryClient.invalidateQueries({
queryKey: ["allAgents", input.serverId],
});
},
});
const archiveMutateAsync = archiveMutation.mutateAsync;
const archiveAgent = useCallback(
async (input: ArchiveAgentInput): Promise<void> => {
await archiveMutateAsync(input);
},
[archiveMutateAsync],
);
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,
removeAgentFromListPayload,
};