fix(app): subagents track display + archive flicker
- Bucket finished subagents as `done` instead of `attention`; drop "needs attention" segment from the section header — subagents are transient and don't carry an unread state worth surfacing. - Gate the subagents selector on the existing pending-archive query so optimistic archives survive authoritative agent snapshots that wipe the local `archivedAt`. Last subagent archived now cleanly hides the whole track via the existing empty-rows guard.
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import React from "react";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { Agent } 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,
|
||||
usePendingArchiveAgentIds,
|
||||
} from "./use-archive-agent";
|
||||
|
||||
function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
||||
return {
|
||||
@@ -92,6 +101,48 @@ describe("useArchiveAgent", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("selects pending archive ids for a single server", () => {
|
||||
const pendingIds = __private__.selectPendingArchiveAgentIds(
|
||||
{
|
||||
"server-a:agent-1": true,
|
||||
"server-a:agent-2": true,
|
||||
"server-b:agent-3": true,
|
||||
},
|
||||
"server-a",
|
||||
);
|
||||
|
||||
expect(Array.from(pendingIds)).toEqual(["agent-1", "agent-2"]);
|
||||
});
|
||||
|
||||
it("subscribes to pending archive ids through react-query", async () => {
|
||||
const queryClient = new QueryClient();
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
const { result } = renderHook(() => usePendingArchiveAgentIds("server-a"), { wrapper });
|
||||
|
||||
expect(Array.from(result.current)).toEqual([]);
|
||||
|
||||
act(() => {
|
||||
__private__.setAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-a",
|
||||
agentId: "agent-1",
|
||||
isArchiving: true,
|
||||
});
|
||||
__private__.setAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-b",
|
||||
agentId: "agent-2",
|
||||
isArchiving: true,
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(Array.from(result.current)).toEqual(["agent-1"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("removes an archived agent from cached list payloads", () => {
|
||||
const payload = {
|
||||
entries: [{ agent: { id: "agent-1" } }, { agent: { id: "agent-2" } }],
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useMutation, useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query";
|
||||
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;
|
||||
const EMPTY_PENDING_ARCHIVE_AGENT_IDS = new Set<string>();
|
||||
|
||||
export interface ArchiveAgentInput {
|
||||
serverId: string;
|
||||
@@ -67,6 +68,35 @@ function readPendingState(queryClient: QueryClient): ArchiveAgentPendingState {
|
||||
return queryClient.getQueryData<ArchiveAgentPendingState>(ARCHIVE_AGENT_PENDING_QUERY_KEY) ?? {};
|
||||
}
|
||||
|
||||
function selectPendingArchiveAgentIds(
|
||||
pendingState: ArchiveAgentPendingState,
|
||||
serverId: string,
|
||||
): ReadonlySet<string> {
|
||||
const normalizedServerId = serverId.trim();
|
||||
if (!normalizedServerId) {
|
||||
return EMPTY_PENDING_ARCHIVE_AGENT_IDS;
|
||||
}
|
||||
|
||||
const prefix = `${normalizedServerId}:`;
|
||||
let agentIds: string[] | null = null;
|
||||
for (const key of Object.keys(pendingState)) {
|
||||
if (!key.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
const agentId = key.slice(prefix.length);
|
||||
if (!agentId) {
|
||||
continue;
|
||||
}
|
||||
agentIds ??= [];
|
||||
agentIds.push(agentId);
|
||||
}
|
||||
|
||||
if (!agentIds || agentIds.length === 0) {
|
||||
return EMPTY_PENDING_ARCHIVE_AGENT_IDS;
|
||||
}
|
||||
return new Set(agentIds);
|
||||
}
|
||||
|
||||
function setAgentArchiving(input: SetAgentArchivingInput): void {
|
||||
const key = toArchiveKey(input);
|
||||
if (!key) {
|
||||
@@ -329,16 +359,28 @@ export function clearArchiveAgentPending(input: IsAgentArchivingInput): void {
|
||||
});
|
||||
}
|
||||
|
||||
export function useArchiveAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const pendingQuery = useQuery({
|
||||
function useArchiveAgentPendingQuery() {
|
||||
return useQuery({
|
||||
queryKey: ARCHIVE_AGENT_PENDING_QUERY_KEY,
|
||||
queryFn: async (): Promise<ArchiveAgentPendingState> => ({}),
|
||||
initialData: {} as ArchiveAgentPendingState,
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
export function usePendingArchiveAgentIds(serverId: string): ReadonlySet<string> {
|
||||
const pendingQuery = useArchiveAgentPendingQuery();
|
||||
return useMemo(
|
||||
() => selectPendingArchiveAgentIds(pendingQuery.data ?? {}, serverId),
|
||||
[pendingQuery.data, serverId],
|
||||
);
|
||||
}
|
||||
|
||||
export function useArchiveAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const pendingQuery = useArchiveAgentPendingQuery();
|
||||
|
||||
const archiveMutation = useMutation({
|
||||
mutationFn: async (input: ArchiveAgentInput): Promise<{ archivedAt: string }> => {
|
||||
@@ -434,6 +476,7 @@ export function useArchiveAgent() {
|
||||
export const __private__ = {
|
||||
toArchiveKey,
|
||||
readPendingState,
|
||||
selectPendingArchiveAgentIds,
|
||||
setAgentArchiving,
|
||||
isAgentArchiving,
|
||||
removeAgentFromListPayload,
|
||||
|
||||
Reference in New Issue
Block a user