fix(app): restore archive and composer CI checks
This commit is contained in:
@@ -22,6 +22,7 @@ import { buildHostAgentDetailRoute } from "@/utils/host-routes";
|
|||||||
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
|
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
|
||||||
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
|
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
|
||||||
import type { Agent } from "@/stores/session-store";
|
import type { Agent } from "@/stores/session-store";
|
||||||
|
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
||||||
|
|
||||||
interface AgentListProps {
|
interface AgentListProps {
|
||||||
agents: AggregatedAgent[];
|
agents: AggregatedAgent[];
|
||||||
@@ -280,6 +281,7 @@ export function AgentList({
|
|||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null);
|
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null);
|
||||||
const isMobile = useIsCompactFormFactor();
|
const isMobile = useIsCompactFormFactor();
|
||||||
|
const { archiveAgent } = useArchiveAgent();
|
||||||
|
|
||||||
const actionClient = useSessionStore((state) =>
|
const actionClient = useSessionStore((state) =>
|
||||||
actionAgent?.serverId ? (state.sessions[actionAgent.serverId]?.client ?? null) : null,
|
actionAgent?.serverId ? (state.sessions[actionAgent.serverId]?.client ?? null) : null,
|
||||||
@@ -320,20 +322,23 @@ export function AgentList({
|
|||||||
[isActionSheetVisible, onAgentSelect],
|
[isActionSheetVisible, onAgentSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAgentLongPress = useCallback((agent: AggregatedAgent) => {
|
const handleAgentLongPress = useCallback(
|
||||||
const isRunning = agent.status === "running" || agent.status === "initializing";
|
(agent: AggregatedAgent) => {
|
||||||
if (isRunning) {
|
const isRunning = agent.status === "running" || agent.status === "initializing";
|
||||||
setActionAgent(agent);
|
if (isRunning) {
|
||||||
return;
|
setActionAgent(agent);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const client = useSessionStore.getState().sessions[agent.serverId]?.client ?? null;
|
const client = useSessionStore.getState().sessions[agent.serverId]?.client ?? null;
|
||||||
if (!client) {
|
if (!client) {
|
||||||
setActionAgent(agent);
|
setActionAgent(agent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void client.archiveAgent(agent.id);
|
void archiveAgent({ serverId: agent.serverId, agentId: agent.id }).catch(() => {});
|
||||||
}, []);
|
},
|
||||||
|
[archiveAgent],
|
||||||
|
);
|
||||||
|
|
||||||
const handleCloseActionSheet = useCallback(() => {
|
const handleCloseActionSheet = useCallback(() => {
|
||||||
setActionAgent(null);
|
setActionAgent(null);
|
||||||
@@ -344,9 +349,9 @@ export function AgentList({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Timeout errors are swallowed — the daemon will still process the archive
|
// Timeout errors are swallowed — the daemon will still process the archive
|
||||||
void actionClient.archiveAgent(actionAgent.id).catch(() => {});
|
void archiveAgent({ serverId: actionAgent.serverId, agentId: actionAgent.id }).catch(() => {});
|
||||||
setActionAgent(null);
|
setActionAgent(null);
|
||||||
}, [actionAgent, actionClient]);
|
}, [actionAgent, actionClient, archiveAgent]);
|
||||||
|
|
||||||
const flatItems = useMemo((): FlatListItem[] => {
|
const flatItems = useMemo((): FlatListItem[] => {
|
||||||
const order = ["Today", "Yesterday", "This week", "This month", "Older"] as const;
|
const order = ["Today", "Yesterday", "This week", "This month", "Older"] as const;
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ vi.mock("@/contexts/toast-context", () => ({
|
|||||||
useToast: () => ({ error: vi.fn() }),
|
useToast: () => ({ error: vi.fn() }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@/utils/scroll-jank-investigation", () => ({
|
vi.mock("@/utils/scroll-jank", () => ({
|
||||||
markScrollInvestigationRender: markScrollInvestigationRenderMock,
|
markScrollInvestigationRender: markScrollInvestigationRenderMock,
|
||||||
markScrollInvestigationEvent: vi.fn(),
|
markScrollInvestigationEvent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function agentHistoryQueryKey(serverId: string | null) {
|
||||||
|
return ["agentHistory", serverId] as const;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { useCallback, useMemo } from "react";
|
|||||||
import type { AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
import type { AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||||
import { useHostRuntimeClient, useHostRuntimeIsConnected, useHosts } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected, useHosts } from "@/runtime/host-runtime";
|
||||||
import { buildAgentDirectoryState } from "@/utils/agent-directory-sync";
|
import { buildAgentDirectoryState } from "@/utils/agent-directory-sync";
|
||||||
|
import { agentHistoryQueryKey } from "./agent-history-query-key";
|
||||||
|
|
||||||
const AGENT_HISTORY_PAGE_LIMIT = 200;
|
const AGENT_HISTORY_PAGE_LIMIT = 200;
|
||||||
const AGENT_HISTORY_SORT: NonNullable<FetchAgentHistoryOptions["sort"]> = [
|
const AGENT_HISTORY_SORT: NonNullable<FetchAgentHistoryOptions["sort"]> = [
|
||||||
@@ -30,10 +31,6 @@ type AgentHistoryPage = {
|
|||||||
pageInfo: FetchAgentHistoryPageInfo;
|
pageInfo: FetchAgentHistoryPageInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function agentHistoryQueryKey(serverId: string | null) {
|
|
||||||
return ["agentHistory", serverId] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAgentHistoryPage(input: {
|
async function fetchAgentHistoryPage(input: {
|
||||||
client: DaemonClient;
|
client: DaemonClient;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { QueryClient } from "@tanstack/react-query";
|
|||||||
import { beforeEach, describe, expect, it } from "vitest";
|
import { beforeEach, describe, expect, it } from "vitest";
|
||||||
import type { Agent } from "@/stores/session-store";
|
import type { Agent } from "@/stores/session-store";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
import { agentHistoryQueryKey } from "./agent-history-query-key";
|
||||||
import { __private__, applyArchivedAgentCloseResults } from "./use-archive-agent";
|
import { __private__, applyArchivedAgentCloseResults } from "./use-archive-agent";
|
||||||
|
|
||||||
function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
||||||
@@ -112,6 +113,17 @@ describe("useArchiveAgent", () => {
|
|||||||
queryClient.setQueryData(["allAgents", "server-a"], {
|
queryClient.setQueryData(["allAgents", "server-a"], {
|
||||||
entries: [{ agent: { id: "agent-1" } }, { agent: { id: "agent-2" } }],
|
entries: [{ agent: { id: "agent-1" } }, { agent: { id: "agent-2" } }],
|
||||||
});
|
});
|
||||||
|
queryClient.setQueryData(agentHistoryQueryKey("server-a"), {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
agents: [
|
||||||
|
{ id: "agent-1", archivedAt: null },
|
||||||
|
{ id: "agent-2", archivedAt: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageParams: [null],
|
||||||
|
});
|
||||||
|
|
||||||
applyArchivedAgentCloseResults({
|
applyArchivedAgentCloseResults({
|
||||||
queryClient,
|
queryClient,
|
||||||
@@ -131,6 +143,17 @@ describe("useArchiveAgent", () => {
|
|||||||
expect(queryClient.getQueryData(["allAgents", "server-a"])).toEqual({
|
expect(queryClient.getQueryData(["allAgents", "server-a"])).toEqual({
|
||||||
entries: [{ agent: { id: "agent-2" } }],
|
entries: [{ agent: { id: "agent-2" } }],
|
||||||
});
|
});
|
||||||
|
expect(queryClient.getQueryData(agentHistoryQueryKey("server-a"))).toEqual({
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
agents: [
|
||||||
|
{ id: "agent-1", archivedAt: new Date("2026-04-01T04:00:00.000Z") },
|
||||||
|
{ id: "agent-2", archivedAt: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageParams: [null],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can apply archived agent close results without invalidating cached lists", () => {
|
it("can apply archived agent close results without invalidating cached lists", () => {
|
||||||
@@ -143,6 +166,14 @@ describe("useArchiveAgent", () => {
|
|||||||
queryClient.setQueryData(["allAgents", "server-a"], {
|
queryClient.setQueryData(["allAgents", "server-a"], {
|
||||||
entries: [{ agent: { id: "agent-1" } }, { agent: { id: "agent-2" } }],
|
entries: [{ agent: { id: "agent-1" } }, { agent: { id: "agent-2" } }],
|
||||||
});
|
});
|
||||||
|
queryClient.setQueryData(agentHistoryQueryKey("server-a"), {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
agents: [{ id: "agent-1", archivedAt: null }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageParams: [null],
|
||||||
|
});
|
||||||
|
|
||||||
applyArchivedAgentCloseResults({
|
applyArchivedAgentCloseResults({
|
||||||
queryClient,
|
queryClient,
|
||||||
@@ -153,5 +184,14 @@ describe("useArchiveAgent", () => {
|
|||||||
|
|
||||||
expect(queryClient.getQueryState(["sidebarAgentsList", "server-a"])?.isInvalidated).toBe(false);
|
expect(queryClient.getQueryState(["sidebarAgentsList", "server-a"])?.isInvalidated).toBe(false);
|
||||||
expect(queryClient.getQueryState(["allAgents", "server-a"])?.isInvalidated).toBe(false);
|
expect(queryClient.getQueryState(["allAgents", "server-a"])?.isInvalidated).toBe(false);
|
||||||
|
expect(queryClient.getQueryState(agentHistoryQueryKey("server-a"))?.isInvalidated).toBe(false);
|
||||||
|
expect(queryClient.getQueryData(agentHistoryQueryKey("server-a"))).toEqual({
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
agents: [{ id: "agent-1", archivedAt: new Date("2026-04-01T04:00:00.000Z") }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageParams: [null],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
import { agentHistoryQueryKey } from "./agent-history-query-key";
|
||||||
|
|
||||||
export const ARCHIVE_AGENT_PENDING_QUERY_KEY = ["archive-agent-pending"] as const;
|
export const ARCHIVE_AGENT_PENDING_QUERY_KEY = ["archive-agent-pending"] as const;
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ interface AgentsListQueryData {
|
|||||||
interface ArchivedAgentListCacheSnapshot {
|
interface ArchivedAgentListCacheSnapshot {
|
||||||
sidebarAgentsList: AgentsListQueryData | undefined;
|
sidebarAgentsList: AgentsListQueryData | undefined;
|
||||||
allAgents: AgentsListQueryData | undefined;
|
allAgents: AgentsListQueryData | undefined;
|
||||||
|
agentHistory: AgentHistoryQueryData | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArchiveAgentMutationContext {
|
interface ArchiveAgentMutationContext {
|
||||||
@@ -39,6 +41,19 @@ interface ArchiveAgentMutationContext {
|
|||||||
lists: ArchivedAgentListCacheSnapshot;
|
lists: ArchivedAgentListCacheSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AgentHistoryQueryAgent {
|
||||||
|
id?: string | null;
|
||||||
|
archivedAt?: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AgentHistoryQueryPage {
|
||||||
|
agents?: AgentHistoryQueryAgent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AgentHistoryQueryData {
|
||||||
|
pages?: AgentHistoryQueryPage[];
|
||||||
|
}
|
||||||
|
|
||||||
function toArchiveKey(input: ArchiveAgentInput): string {
|
function toArchiveKey(input: ArchiveAgentInput): string {
|
||||||
const serverId = input.serverId.trim();
|
const serverId = input.serverId.trim();
|
||||||
const agentId = input.agentId.trim();
|
const agentId = input.agentId.trim();
|
||||||
@@ -121,6 +136,54 @@ function removeAgentFromCachedLists(queryClient: QueryClient, input: ArchiveAgen
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markAgentArchivedInHistoryPayload<T extends AgentHistoryQueryData | undefined>(
|
||||||
|
payload: T,
|
||||||
|
input: ArchiveAgentInput & { archivedAt: string },
|
||||||
|
): T {
|
||||||
|
if (!payload || !Array.isArray(payload.pages) || !input.agentId) {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
const archivedAt = new Date(input.archivedAt);
|
||||||
|
if (Number.isNaN(archivedAt.getTime())) {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
const pages = payload.pages.map((page) => {
|
||||||
|
if (!Array.isArray(page.agents)) {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageChanged = false;
|
||||||
|
const agents = page.agents.map((agent) => {
|
||||||
|
if (agent.id !== input.agentId) {
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
pageChanged = true;
|
||||||
|
changed = true;
|
||||||
|
return {
|
||||||
|
...agent,
|
||||||
|
archivedAt,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return pageChanged ? { ...page, agents } : page;
|
||||||
|
});
|
||||||
|
|
||||||
|
return changed ? ({ ...payload, pages } as T) : payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function markAgentArchivedInHistoryCache(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
input: ArchiveAgentInput & { archivedAt: string },
|
||||||
|
): void {
|
||||||
|
queryClient.setQueryData<AgentHistoryQueryData | undefined>(
|
||||||
|
agentHistoryQueryKey(input.serverId),
|
||||||
|
(current) => markAgentArchivedInHistoryPayload(current, input),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getStoredAgentSnapshot(input: ArchiveAgentInput) {
|
function getStoredAgentSnapshot(input: ArchiveAgentInput) {
|
||||||
return useSessionStore.getState().sessions[input.serverId]?.agents.get(input.agentId);
|
return useSessionStore.getState().sessions[input.serverId]?.agents.get(input.agentId);
|
||||||
}
|
}
|
||||||
@@ -161,13 +224,16 @@ function getArchivedAgentListCacheSnapshot(
|
|||||||
serverId,
|
serverId,
|
||||||
]),
|
]),
|
||||||
allAgents: queryClient.getQueryData<AgentsListQueryData | undefined>(["allAgents", serverId]),
|
allAgents: queryClient.getQueryData<AgentsListQueryData | undefined>(["allAgents", serverId]),
|
||||||
|
agentHistory: queryClient.getQueryData<AgentHistoryQueryData | undefined>(
|
||||||
|
agentHistoryQueryKey(serverId),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreCachedListSnapshot(
|
function restoreCachedQuerySnapshot<T>(
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
queryKey: readonly [string, string],
|
queryKey: readonly unknown[],
|
||||||
snapshot: AgentsListQueryData | undefined,
|
snapshot: T | undefined,
|
||||||
): void {
|
): void {
|
||||||
if (snapshot === undefined) {
|
if (snapshot === undefined) {
|
||||||
queryClient.removeQueries({ queryKey, exact: true });
|
queryClient.removeQueries({ queryKey, exact: true });
|
||||||
@@ -181,12 +247,13 @@ function restoreArchivedAgentListCacheSnapshot(
|
|||||||
serverId: string,
|
serverId: string,
|
||||||
snapshot: ArchivedAgentListCacheSnapshot,
|
snapshot: ArchivedAgentListCacheSnapshot,
|
||||||
): void {
|
): void {
|
||||||
restoreCachedListSnapshot(
|
restoreCachedQuerySnapshot(
|
||||||
queryClient,
|
queryClient,
|
||||||
["sidebarAgentsList", serverId],
|
["sidebarAgentsList", serverId],
|
||||||
snapshot.sidebarAgentsList,
|
snapshot.sidebarAgentsList,
|
||||||
);
|
);
|
||||||
restoreCachedListSnapshot(queryClient, ["allAgents", serverId], snapshot.allAgents);
|
restoreCachedQuerySnapshot(queryClient, ["allAgents", serverId], snapshot.allAgents);
|
||||||
|
restoreCachedQuerySnapshot(queryClient, agentHistoryQueryKey(serverId), snapshot.agentHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
function markAgentArchivedInStore(input: ArchiveAgentInput & { archivedAt: string }): void {
|
function markAgentArchivedInStore(input: ArchiveAgentInput & { archivedAt: string }): void {
|
||||||
@@ -235,6 +302,11 @@ export function applyArchivedAgentCloseResults(input: ApplyArchivedAgentCloseRes
|
|||||||
serverId: input.serverId,
|
serverId: input.serverId,
|
||||||
agentId: result.agentId,
|
agentId: result.agentId,
|
||||||
});
|
});
|
||||||
|
markAgentArchivedInHistoryCache(input.queryClient, {
|
||||||
|
serverId: input.serverId,
|
||||||
|
agentId: result.agentId,
|
||||||
|
archivedAt: result.archivedAt,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.invalidateQueries ?? true) {
|
if (input.invalidateQueries ?? true) {
|
||||||
@@ -244,6 +316,9 @@ export function applyArchivedAgentCloseResults(input: ApplyArchivedAgentCloseRes
|
|||||||
void input.queryClient.invalidateQueries({
|
void input.queryClient.invalidateQueries({
|
||||||
queryKey: ["allAgents", input.serverId],
|
queryKey: ["allAgents", input.serverId],
|
||||||
});
|
});
|
||||||
|
void input.queryClient.invalidateQueries({
|
||||||
|
queryKey: agentHistoryQueryKey(input.serverId),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,6 +399,9 @@ export function useArchiveAgent() {
|
|||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: ["allAgents", input.serverId],
|
queryKey: ["allAgents", input.serverId],
|
||||||
});
|
});
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: agentHistoryQueryKey(input.serverId),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -359,4 +437,5 @@ export const __private__ = {
|
|||||||
setAgentArchiving,
|
setAgentArchiving,
|
||||||
isAgentArchiving,
|
isAgentArchiving,
|
||||||
removeAgentFromListPayload,
|
removeAgentFromListPayload,
|
||||||
|
markAgentArchivedInHistoryPayload,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user