Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,15 @@ vi.mock("@/hooks/queries/thread-queries", () => ({
error: null,
status: "success",
}),
useThreadQueuedMessages: () => ({ data: [] }),
useThreadTimeline: () => ({
data: { activeThinking: null, rows: mocks.threadTimelineRows },
isError: false,
isPending: false,
}),
}));

vi.mock("@/hooks/queries/thread-default-execution-options-query", () => ({
useThreadDefaultExecutionOptions: () => ({
data: {
model: "gpt-5",
Expand All @@ -303,12 +312,6 @@ vi.mock("@/hooks/queries/thread-queries", () => ({
},
isLoading: false,
}),
useThreadQueuedMessages: () => ({ data: [] }),
useThreadTimeline: () => ({
data: { activeThinking: null, rows: mocks.threadTimelineRows },
isError: false,
isPending: false,
}),
}));

vi.mock("@/hooks/mutations/thread-runtime-mutations", () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ import {
import { useHostDaemon } from "@/hooks/useHostDaemon";
import {
useThread,
useThreadDefaultExecutionOptions,
useThreadQueuedMessages,
} from "@/hooks/queries/thread-queries";
import { useThreadDefaultExecutionOptions } from "@/hooks/queries/thread-default-execution-options-query";
import {
useCreateThreadQueuedMessage,
useCreateThread,
Expand Down
5 changes: 5 additions & 0 deletions apps/app/src/hooks/cache-owners/cache-owner-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ const CACHE_OWNER_QUERY_KEY_IMPORTS: CacheOwnerQueryKeyImportRegistry = {
"threadsQueryKey",
],
"hooks/cache-owners/system-cache-effects.ts": [
"allAutomationDetailQueryKeyPrefix",
"allAutomationRunsQueryKeyPrefix",
"allEnvironmentDiffFilesQueryKeyPrefix",
"allEnvironmentDiffPatchQueryKeyPrefix",
"allEnvironmentFilePreviewQueryKeyPrefix",
Expand All @@ -170,6 +172,8 @@ const CACHE_OWNER_QUERY_KEY_IMPORTS: CacheOwnerQueryKeyImportRegistry = {
"allHostQueryKeyPrefix",
"allProjectPathsQueryKeyPrefix",
"allSystemExecutionOptionsQueryKeyPrefix",
"allTerminalsQueryKeyPrefix",
"allThreadHostFilePreviewQueryKeyPrefix",
"allThreadPendingInteractionsQueryKeyPrefix",
"allThreadQueryKeyPrefix",
"allThreadQueuedMessagesQueryKeyPrefix",
Expand All @@ -178,6 +182,7 @@ const CACHE_OWNER_QUERY_KEY_IMPORTS: CacheOwnerQueryKeyImportRegistry = {
"allThreadStoragePathsQueryKeyPrefix",
"allThreadTimelineQueryKeyPrefix",
"allThreadTimelineTurnSummaryDetailsQueryKeyPrefix",
"automationsQueryKey",
"hostsQueryKey",
"hostPathExistenceQueryKeyPrefix",
"projectsQueryKey",
Expand Down
12 changes: 11 additions & 1 deletion apps/app/src/hooks/cache-owners/system-cache-effects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { QueryKey } from "@tanstack/react-query";
import type { SystemExecutionOptionsResponse } from "@bb/server-contract";
import {
allAutomationDetailQueryKeyPrefix,
allAutomationRunsQueryKeyPrefix,
allEnvironmentDiffFilesQueryKeyPrefix,
allEnvironmentDiffPatchQueryKeyPrefix,
allEnvironmentFilePreviewQueryKeyPrefix,
Expand All @@ -10,14 +12,17 @@ import {
allHostQueryKeyPrefix,
allProjectPathsQueryKeyPrefix,
allSystemExecutionOptionsQueryKeyPrefix,
allThreadQueuedMessagesQueryKeyPrefix,
allTerminalsQueryKeyPrefix,
allThreadHostFilePreviewQueryKeyPrefix,
allThreadPendingInteractionsQueryKeyPrefix,
allThreadQueuedMessagesQueryKeyPrefix,
allThreadQueryKeyPrefix,
allThreadStorageFilePreviewQueryKeyPrefix,
allThreadStorageFilesQueryKeyPrefix,
allThreadStoragePathsQueryKeyPrefix,
allThreadTimelineQueryKeyPrefix,
allThreadTimelineTurnSummaryDetailsQueryKeyPrefix,
automationsQueryKey,
hostPathExistenceQueryKeyPrefix,
hostsQueryKey,
projectsQueryKey,
Expand Down Expand Up @@ -112,6 +117,8 @@ function getServerReconnectInvalidationQueryKeys(): QueryKey[] {
allThreadStorageFilesQueryKeyPrefix(),
allThreadStoragePathsQueryKeyPrefix(),
allThreadStorageFilePreviewQueryKeyPrefix(),
allThreadHostFilePreviewQueryKeyPrefix(),
allTerminalsQueryKeyPrefix(),
allEnvironmentQueryKeyPrefix(),
allEnvironmentWorkStatusQueryKeyPrefix(),
allEnvironmentMergeBaseBranchesQueryKeyPrefix(),
Expand All @@ -122,6 +129,9 @@ function getServerReconnectInvalidationQueryKeys(): QueryKey[] {
allEnvironmentDiffFilesQueryKeyPrefix(),
allEnvironmentFilePreviewQueryKeyPrefix(),
hostPathExistenceQueryKeyPrefix(),
automationsQueryKey(),
allAutomationDetailQueryKeyPrefix(),
allAutomationRunsQueryKeyPrefix(),
systemProvidersQueryKey(),
allSystemExecutionOptionsQueryKeyPrefix(),
];
Expand Down
17 changes: 17 additions & 0 deletions apps/app/src/hooks/queries/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@ export type ThreadHostFilePreviewQueryKey = readonly [
string | null | undefined,
string | null,
];
export type AllThreadHostFilePreviewQueryKeyPrefix = readonly [
typeof THREAD_HOST_FILE_PREVIEW_QUERY_KEY,
];
export type ThreadHostFilePreviewQueryKeyPrefix = readonly [
typeof THREAD_HOST_FILE_PREVIEW_QUERY_KEY,
string,
];
export type EnvironmentQueryKeyPrefix = readonly [typeof ENVIRONMENT_QUERY_KEY];
export type EnvironmentQueryKey = readonly [
typeof ENVIRONMENT_QUERY_KEY,
Expand Down Expand Up @@ -784,6 +791,16 @@ export function threadHostFilePreviewQueryKey(
return [THREAD_HOST_FILE_PREVIEW_QUERY_KEY, threadId, environmentId, path];
}

export function allThreadHostFilePreviewQueryKeyPrefix(): AllThreadHostFilePreviewQueryKeyPrefix {
return [THREAD_HOST_FILE_PREVIEW_QUERY_KEY];
}

export function threadHostFilePreviewQueryKeyPrefix(
threadId: string,
): ThreadHostFilePreviewQueryKeyPrefix {
return [THREAD_HOST_FILE_PREVIEW_QUERY_KEY, threadId];
}

export function allEnvironmentQueryKeyPrefix(): EnvironmentQueryKeyPrefix {
return [ENVIRONMENT_QUERY_KEY];
}
Expand Down
80 changes: 79 additions & 1 deletion apps/app/src/hooks/queries/system-queries.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@

import { cleanup, renderHook, waitFor } from "@testing-library/react";
import type { SystemExecutionOptionsResponse } from "@bb/server-contract";
import type {
ProviderCliStatusResponse,
ProviderUsageResponse,
} from "@bb/host-daemon-contract";
import { afterEach, describe, expect, it, vi } from "vitest";
import * as api from "@/lib/api";
import { createQueryClientTestHarness } from "@/test/queryClientTestHarness";
import { useSystemExecutionOptions } from "./system-queries";
import {
hostProviderCliStatusQueryKey,
systemUsageLimitsQueryKey,
} from "./query-keys";
import {
useHostProviderCliStatus,
useSystemExecutionOptions,
useSystemUsageLimits,
} from "./system-queries";

vi.mock("@/lib/api", async (importOriginal) => {
const actual = await importOriginal<typeof import("@/lib/api")>();
return {
...actual,
fetchHostProviderCliStatus: vi.fn(),
getSystemExecutionOptions: vi.fn(),
getSystemUsageLimits: vi.fn(),
};
});

Expand All @@ -22,6 +36,13 @@ const EXECUTION_OPTIONS_RESPONSE: SystemExecutionOptionsResponse = {
modelLoadError: null,
};

const PROVIDER_CLI_STATUS_RESPONSE = {} as ProviderCliStatusResponse;

const PROVIDER_USAGE_RESPONSE: ProviderUsageResponse = {
codex: { status: "unauthenticated" },
claudeCode: { status: "unauthenticated" },
};

afterEach(() => {
cleanup();
vi.clearAllMocks();
Expand Down Expand Up @@ -64,3 +85,60 @@ describe("useSystemExecutionOptions", () => {
});
});
});

describe("useHostProviderCliStatus", () => {
it("refreshes stale host CLI status on focus and reconnect", async () => {
vi.mocked(api.fetchHostProviderCliStatus).mockResolvedValue(
PROVIDER_CLI_STATUS_RESPONSE,
);
const { queryClient, wrapper } = createQueryClientTestHarness();

renderHook(
() => useHostProviderCliStatus({ hostId: "host-1", enabled: true }),
{ wrapper },
);

await waitFor(() => {
expect(api.fetchHostProviderCliStatus).toHaveBeenCalledTimes(1);
});

const query = queryClient.getQueryCache().find({
queryKey: hostProviderCliStatusQueryKey("host-1"),
});

expect(query?.options).toEqual(
expect.objectContaining({
refetchOnReconnect: true,
refetchOnWindowFocus: true,
staleTime: 60_000,
}),
);
});
});

describe("useSystemUsageLimits", () => {
it("refreshes stale usage data on focus and reconnect", async () => {
vi.mocked(api.getSystemUsageLimits).mockResolvedValue(
PROVIDER_USAGE_RESPONSE,
);
const { queryClient, wrapper } = createQueryClientTestHarness();

renderHook(() => useSystemUsageLimits(), { wrapper });

await waitFor(() => {
expect(api.getSystemUsageLimits).toHaveBeenCalledTimes(1);
});

const query = queryClient.getQueryCache().find({
queryKey: systemUsageLimitsQueryKey(),
});

expect(query?.options).toEqual(
expect.objectContaining({
refetchOnReconnect: true,
refetchOnWindowFocus: true,
staleTime: 30_000,
}),
);
});
});
12 changes: 6 additions & 6 deletions apps/app/src/hooks/queries/system-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function useSystemConfig(options?: QueryOptions) {
}

const SYSTEM_VERSION_STALE_TIME_MS = 60 * 60 * 1000;
const HOST_PROVIDER_CLI_STATUS_STALE_TIME_MS = 60_000;

export function useSystemVersion(options?: QueryOptions) {
return useQuery<SystemVersionResponse>({
Expand Down Expand Up @@ -125,10 +126,9 @@ export function useHostProviderCliStatus({
signal,
),
enabled: (enabled ?? true) && hostId !== null,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
staleTime: Infinity,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
staleTime: HOST_PROVIDER_CLI_STATUS_STALE_TIME_MS,
});
}

Expand All @@ -139,8 +139,8 @@ export function useSystemUsageLimits(options?: QueryOptions) {
queryKey: systemUsageLimitsQueryKey(),
queryFn: ({ signal }) => api.getSystemUsageLimits(signal),
enabled: options?.enabled ?? true,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
staleTime: PROVIDER_USAGE_STALE_TIME_MS,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import { apiClient } from "@/lib/api-server";
import { request, requestOptions } from "@/lib/api";
import { useThreadDetailRealtimeSubscription } from "@/hooks/useRealtimeSubscription";
import { requireEnabledQueryArg } from "./query-helpers";
import { threadDefaultExecutionOptionsQueryKey } from "./query-keys";

export const THREAD_DEFAULT_EXECUTION_OPTIONS_QUERY_KEY =
"threadDefaultExecutionOptions";

export type ThreadDefaultExecutionOptionsQueryKeyPrefix = readonly [
typeof THREAD_DEFAULT_EXECUTION_OPTIONS_QUERY_KEY,
];
export type ThreadDefaultExecutionOptionsQueryKey = readonly [
typeof THREAD_DEFAULT_EXECUTION_OPTIONS_QUERY_KEY,
string,
];
export {
allThreadDefaultExecutionOptionsQueryKeyPrefix,
threadDefaultExecutionOptionsQueryKey,
} from "./query-keys";
export type {
ThreadDefaultExecutionOptionsQueryKey,
ThreadDefaultExecutionOptionsQueryKeyPrefix,
} from "./query-keys";

interface ThreadDefaultExecutionOptionsQueryOptions {
enabled?: boolean;
Expand All @@ -26,16 +25,6 @@ function requireThreadId(id: string, hookName: string): string {
return requireEnabledQueryArg({ value: id, hookName, argName: "thread id" });
}

export function threadDefaultExecutionOptionsQueryKey(
threadId: string,
): ThreadDefaultExecutionOptionsQueryKey {
return [THREAD_DEFAULT_EXECUTION_OPTIONS_QUERY_KEY, threadId];
}

export function allThreadDefaultExecutionOptionsQueryKeyPrefix(): ThreadDefaultExecutionOptionsQueryKeyPrefix {
return [THREAD_DEFAULT_EXECUTION_OPTIONS_QUERY_KEY];
}

export function fetchThreadDefaultExecutionOptions(
threadId: string,
signal?: AbortSignal,
Expand Down
Loading
Loading