diff --git a/packages/app/src/git/checkout-status-cache.test.ts b/packages/app/src/git/checkout-status-cache.test.ts index 8ab38178e4..ba4ddd2429 100644 --- a/packages/app/src/git/checkout-status-cache.test.ts +++ b/packages/app/src/git/checkout-status-cache.test.ts @@ -16,6 +16,7 @@ import { resolveWorkingDiffComparison, selectWorkingDiffComparison, } from "@/git/working-diff-comparison"; +import { draftAgentCommandsQueryKey } from "@/hooks/agent-commands-query"; import { applyCheckoutStatusUpdateFromEvent, ensureCheckoutStatus, @@ -197,6 +198,51 @@ describe("applyCheckoutStatusUpdateFromEvent", () => { ).toBe(false); }); + it("drops the checkout's cached draft slash commands when its branch changes", () => { + const queryClient = createQueryClient(); + queryClient.setQueryData( + checkoutStatusQueryKey(serverId, cwd), + checkoutStatus({ currentBranch: "chore/build-paseo" }), + ); + const thisCheckout = draftAgentCommandsQueryKey({ + serverId, + draftConfig: { provider: "claude", cwd, model: "haiku" }, + }); + const otherCheckout = draftAgentCommandsQueryKey({ + serverId, + draftConfig: { provider: "claude", cwd: "/repo2", model: "haiku" }, + }); + queryClient.setQueryData(thisCheckout, [{ name: "build-paseo" }]); + queryClient.setQueryData(otherCheckout, [{ name: "build-paseo" }]); + + applyCheckoutStatusUpdateFromEvent({ + queryClient, + serverId, + message: checkoutStatusUpdate(checkoutStatus({ currentBranch: "main" })), + }); + + expect(queryClient.getQueryData(thisCheckout)).toBeUndefined(); + expect(queryClient.getQueryData(otherCheckout)).toEqual([{ name: "build-paseo" }]); + }); + + it("keeps the checkout's draft slash commands when a push leaves its branch unchanged", () => { + const queryClient = createQueryClient(); + queryClient.setQueryData(checkoutStatusQueryKey(serverId, cwd), checkoutStatus()); + const thisCheckout = draftAgentCommandsQueryKey({ + serverId, + draftConfig: { provider: "claude", cwd, model: "haiku" }, + }); + queryClient.setQueryData(thisCheckout, [{ name: "build-paseo" }]); + + applyCheckoutStatusUpdateFromEvent({ + queryClient, + serverId, + message: checkoutStatusUpdate(checkoutStatus({ isDirty: true })), + }); + + expect(queryClient.getQueryData(thisCheckout)).toEqual([{ name: "build-paseo" }]); + }); + it("writes the PR status cache when prStatus is present, and skips it otherwise", () => { const queryClient = createQueryClient(); const pushedPr = prStatus({ requestId: "pr-1" }); diff --git a/packages/app/src/git/checkout-status-cache.ts b/packages/app/src/git/checkout-status-cache.ts index 97a26e099b..c06499fef5 100644 --- a/packages/app/src/git/checkout-status-cache.ts +++ b/packages/app/src/git/checkout-status-cache.ts @@ -8,6 +8,7 @@ import { invalidatePrPaneTimelineForCheckout, } from "@/git/query-keys"; import { type CheckoutPrStatusPayload, normalizeCheckoutPrStatusPayload } from "@/git/pr-status"; +import { resetDraftAgentCommandsForCheckout } from "@/hooks/agent-commands-query"; import { expireWorkingDiffComparisons } from "@/git/working-diff-comparison"; export type CheckoutStatusPayload = CheckoutStatusResponse["payload"]; @@ -67,7 +68,13 @@ export function applyCheckoutStatusUpdateFromEvent({ ? normalizeCheckoutPrStatusPayload(payload.prStatus) : undefined; const cachePayload = prStatus ? { ...payload, prStatus } : payload; + const previousStatus = queryClient.getQueryData( + checkoutStatusQueryKey(serverId, payload.cwd), + ); queryClient.setQueryData(checkoutStatusQueryKey(serverId, payload.cwd), cachePayload); + if (previousStatus?.currentBranch !== payload.currentBranch) { + void resetDraftAgentCommandsForCheckout(queryClient, { serverId, cwd: payload.cwd }); + } void queryClient.invalidateQueries({ queryKey: checkoutCommitsQueryKey(serverId, payload.cwd), }); diff --git a/packages/app/src/hooks/agent-commands-query.test.ts b/packages/app/src/hooks/agent-commands-query.test.ts index 9d880b8dab..e8d8ee0b94 100644 --- a/packages/app/src/hooks/agent-commands-query.test.ts +++ b/packages/app/src/hooks/agent-commands-query.test.ts @@ -38,9 +38,10 @@ describe("agent command query keys", () => { "agentCommands", "server-1", "draft", - "codex", "cwd", "/repo", + "provider", + "codex", "mode", null, "model", diff --git a/packages/app/src/hooks/agent-commands-query.ts b/packages/app/src/hooks/agent-commands-query.ts index 38cbd97a10..276c0fd9ba 100644 --- a/packages/app/src/hooks/agent-commands-query.ts +++ b/packages/app/src/hooks/agent-commands-query.ts @@ -1,3 +1,4 @@ +import type { QueryClient } from "@tanstack/react-query"; import type { AgentProvider } from "@getpaseo/protocol/agent-types"; import { normalizeWorkspacePath } from "@/utils/workspace-identity"; @@ -24,17 +25,24 @@ export function sessionAgentCommandsQueryKey(input: { serverId: string; agentId: return [...agentCommandsQueryRoot(input.serverId), "session", input.agentId] as const; } +function draftAgentCommandsCheckoutScope(input: { serverId: string; cwd: string }) { + return [ + ...agentCommandsQueryRoot(input.serverId), + "draft", + "cwd", + normalizeAgentCommandsCwd(input.cwd), + ] as const; +} + export function draftAgentCommandsQueryKey(input: { serverId: string; draftConfig: AgentCommandsDraftConfig; }) { const { draftConfig } = input; return [ - ...agentCommandsQueryRoot(input.serverId), - "draft", + ...draftAgentCommandsCheckoutScope({ serverId: input.serverId, cwd: draftConfig.cwd }), + "provider", draftConfig.provider, - "cwd", - normalizeAgentCommandsCwd(draftConfig.cwd), "mode", draftConfig.modeId ?? null, "model", @@ -46,6 +54,16 @@ export function draftAgentCommandsQueryKey(input: { ] as const; } +// Draft commands include project skills read from the checkout. When the checkout moves to +// another branch the cached list describes files that are gone, so it is dropped rather than +// shown while a refetch runs. +export function resetDraftAgentCommandsForCheckout( + queryClient: QueryClient, + input: { serverId: string; cwd: string }, +): Promise { + return queryClient.resetQueries({ queryKey: draftAgentCommandsCheckoutScope(input) }); +} + export function agentCommandsQueryKey(input: { serverId: string; agentId: string;