feat(desktop): add review check debug harness

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Taylor Ho
2026-08-27 11:50:13 -07:00
parent 9e50b93eb7
commit 2dda0eafbb
3 changed files with 157 additions and 105 deletions

View File

@@ -1,7 +1,6 @@
import {
Bot,
CheckCircle2,
ChevronDown,
CircleAlert,
LoaderCircle,
Play,
@@ -49,6 +48,10 @@ import type { ProjectsConversationOpener } from "@/features/projects/lib/project
import { pullRequestShareLink } from "@/features/projects/lib/projectShareLinks";
import type { ProjectPullRequest, Repository } from "@/features/projects/hooks";
import { ProjectReviewCheckResultCard } from "@/features/projects/ui/ProjectReviewCheckResultCard";
import {
ProjectReviewDebugHarness,
type ReviewCheckAgent,
} from "@/features/projects/ui/ProjectReviewDebugHarness";
import { useIdentityQuery } from "@/shared/api/hooks";
import { listManagedAgents } from "@/shared/api/tauri";
import { revalidateRelayAgents } from "@/shared/api/tauriRelayAgents";
@@ -63,13 +66,6 @@ import {
import { normalizePubkey } from "@/shared/lib/pubkey";
import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/shared/ui/dropdown-menu";
type ReviewCheckStatus = "idle" | "running" | "completed" | "failed";
@@ -89,13 +85,6 @@ type ReviewCheckRun = {
type ReviewCheckRuns = Record<string, ReviewCheckRun>;
type ReviewCheckAgent = {
pubkey: string;
name: string;
isManaged: boolean;
isActive: boolean;
};
function isReviewCheckStatus(value: unknown): value is ReviewCheckStatus {
return (
value === "idle" ||
@@ -457,66 +446,6 @@ function CheckStatus({
return null;
}
function AgentPicker({
candidates,
disabled,
onSelect,
selected,
}: {
candidates: ReviewCheckAgent[];
disabled: boolean;
onSelect: (pubkey: string) => void;
selected: ReviewCheckAgent | null;
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="min-w-44 justify-between"
data-testid="project-review-check-agent-picker"
disabled={disabled}
size="sm"
type="button"
variant="outline"
>
<span className="flex min-w-0 items-center gap-1.5">
<Bot className="h-3.5 w-3.5" />
<span className="truncate">
{selected?.name ?? "Assign an agent"}
</span>
</span>
<ChevronDown className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-64">
<DropdownMenuRadioGroup
onValueChange={onSelect}
value={selected?.pubkey ?? ""}
>
{candidates.map((candidate) => (
<DropdownMenuRadioItem
className="flex items-center justify-between gap-3"
key={candidate.pubkey}
value={candidate.pubkey}
>
<span className="truncate">{candidate.name}</span>
<span className="shrink-0 text-xs text-muted-foreground">
{candidate.isManaged
? candidate.isActive
? "Running here"
: "Can start here"
: candidate.isActive
? "Online on relay"
: "Offline"}
</span>
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}
export function ProjectReviewChecks({
project,
pullRequest,
@@ -548,6 +477,9 @@ export function ProjectReviewChecks({
const [pendingCheckIds, setPendingCheckIds] = React.useState<Set<string>>(
() => new Set(),
);
const [selectedAgentPubkey, setSelectedAgentPubkey] = React.useState<
string | null
>(null);
const targetCommit = pullRequest.commit ?? pullRequest.initialCommit;
const diffRepoUrl = React.useMemo(
() =>
@@ -566,6 +498,7 @@ export function ProjectReviewChecks({
);
setRuns(normalizeStoredRuns(stored));
setPendingCheckIds(new Set());
setSelectedAgentPubkey(null);
}, [storageKey]);
const updateRun = React.useCallback(
@@ -593,17 +526,6 @@ export function ProjectReviewChecks({
[],
);
const assignAgent = React.useCallback(
(checkId: string, pubkey: string) => {
if (!storageKey) return;
updateRun(storageKey, checkId, () => ({
agentPubkey: normalizePubkey(pubkey),
status: "idle",
}));
},
[storageKey, updateRun],
);
const recordResult = React.useCallback(
(
checkId: string,
@@ -806,13 +728,19 @@ export function ProjectReviewChecks({
const defaultAgent =
reviewCheckAgents.candidates.find((candidate) => candidate.isActive) ??
null;
const selectedAgent = selectedAgentPubkey
? (reviewCheckAgents.candidates.find(
(candidate) =>
candidate.isActive &&
normalizePubkey(candidate.pubkey) ===
normalizePubkey(selectedAgentPubkey),
) ?? defaultAgent)
: defaultAgent;
return (
<div className="-mx-6" data-testid="project-review-checks">
<div className="border-b border-border/55 px-6 pb-3 text-xs text-muted-foreground">
The first running agent is selected automatically; you can choose a
different local managed agent or relay agent authorized for your
signed-in identity. Definitions come from the target branch
Definitions come from the target branch
{checkDefinitions.source === "project"
? ` (${PROJECT_REVIEW_CHECKS_CONFIG_PATH})`
: checkDefinitions.source === "starter"
@@ -826,13 +754,13 @@ export function ProjectReviewChecks({
agentPubkey: null,
status: "idle" as const,
};
const selectedAgent = run.agentPubkey
const runAgent = run.agentPubkey
? (reviewCheckAgents.candidates.find(
(candidate) =>
normalizePubkey(candidate.pubkey) ===
normalizePubkey(run.agentPubkey ?? ""),
) ?? null)
: defaultAgent;
: null;
const isPending = pendingCheckIds.has(check.id);
const isStale =
run.status === "completed" && run.targetCommit !== targetCommit;
@@ -870,12 +798,6 @@ export function ProjectReviewChecks({
</p>
</div>
<div className="flex flex-wrap items-center justify-end gap-2">
<AgentPicker
candidates={reviewCheckAgents.candidates}
disabled={isPending || run.status === "running"}
onSelect={(pubkey) => assignAgent(check.id, pubkey)}
selected={selectedAgent}
/>
<Button
disabled={
!selectedAgent ||
@@ -905,8 +827,8 @@ export function ProjectReviewChecks({
</div>
{run.status === "running" ? (
<p className="text-xs text-muted-foreground">
Waiting for {selectedAgent?.name ?? "the assigned agent"} to
return a structured conclusion…
Waiting for {runAgent?.name ?? "the assigned agent"} to return
a structured conclusion…
</p>
) : null}
{run.error ? (
@@ -960,6 +882,13 @@ export function ProjectReviewChecks({
: "No local managed or authorized relay agents are available."}
</div>
) : null}
<ProjectReviewDebugHarness
candidates={reviewCheckAgents.candidates}
hasError={reviewCheckAgents.isError}
isLoading={reviewCheckAgents.isLoading}
onSelect={setSelectedAgentPubkey}
selected={selectedAgent}
/>
</div>
);
}

View File

@@ -0,0 +1,98 @@
import { Bug } from "lucide-react";
import { Button } from "@/shared/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
export type ReviewCheckAgent = {
pubkey: string;
name: string;
isManaged: boolean;
isActive: boolean;
};
function agentAvailabilityLabel(agent: ReviewCheckAgent) {
return agent.isManaged ? "Running here" : "Online on relay";
}
export function ProjectReviewDebugHarness({
candidates,
hasError,
isLoading,
onSelect,
selected,
}: {
candidates: ReviewCheckAgent[];
hasError: boolean;
isLoading: boolean;
onSelect: (pubkey: string) => void;
selected: ReviewCheckAgent | null;
}) {
const activeCandidates = candidates.filter((candidate) => candidate.isActive);
const offlineAgentCount = candidates.length - activeCandidates.length;
return (
<div className="fixed bottom-5 right-5 z-40">
<Popover>
<PopoverTrigger asChild>
<Button
aria-label={`Review check debug harness${selected ? `, agent ${selected.name}` : ""}`}
className="h-12 w-12 rounded-full p-0 shadow-lg ring-1 ring-border/60"
data-testid="project-review-debug-harness-trigger"
title="Review check debug harness"
type="button"
>
<Bug className="h-5 w-5" />
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
className="w-72 space-y-3"
data-testid="project-review-debug-harness-menu"
side="top"
sideOffset={10}
>
<div className="space-y-0.5">
<p className="text-sm font-semibold">Review debug harness</p>
<p className="text-xs text-muted-foreground">
Choose the agent used for new checks.
</p>
</div>
<label className="block space-y-1.5 text-xs font-medium">
<span>Agent</span>
<select
className="h-9 w-full rounded-lg border border-input bg-background px-3 text-sm font-normal outline-hidden focus:ring-1 focus:ring-ring disabled:opacity-50"
data-testid="project-review-debug-agent-select"
disabled={activeCandidates.length === 0}
onChange={(event) => onSelect(event.target.value)}
value={selected?.pubkey ?? ""}
>
{activeCandidates.length === 0 ? (
<option value="">
{isLoading
? "Loading available agents…"
: hasError
? "Agents could not be loaded"
: "No running agents are available"}
</option>
) : null}
{activeCandidates.map((candidate) => (
<option key={candidate.pubkey} value={candidate.pubkey}>
{candidate.name} · {agentAvailabilityLabel(candidate)}
</option>
))}
</select>
{offlineAgentCount > 0 ? (
<span
className="block font-normal text-muted-foreground"
data-testid="project-review-debug-offline-agent-count"
>
{offlineAgentCount} offline{" "}
{offlineAgentCount === 1 ? "agent" : "agents"}
</span>
) : null}
</label>
</PopoverContent>
</Popover>
</div>
);
}

View File

@@ -169,8 +169,10 @@ test("review checks dispatch to an agent authorized for the relay identity", asy
await expect(checks).toBeVisible();
await expect(checks.getByTestId("project-review-check")).toHaveCount(3);
const interfaceCheck = checks.getByTestId("project-review-check").first();
await interfaceCheck.getByTestId("project-review-check-agent-picker").click();
await page.getByRole("menuitemradio", { name: /charlie/i }).click();
await page.getByTestId("project-review-debug-harness-trigger").click();
await page
.getByTestId("project-review-debug-agent-select")
.selectOption(RELAY_REVIEW_AGENT_PUBKEY);
await interfaceCheck
.getByRole("button", { name: "Run", exact: true })
.click();
@@ -350,6 +352,13 @@ test("review checks dispatch to a running local managed agent", async ({
respondTo: "allowlist",
respondToAllowlist: [],
},
...Array.from({ length: 16 }, (_, index) => ({
pubkey: (index + 1).toString(16).padStart(64, "0"),
name: `Stopped prototype ${index + 1}`,
status: "stopped" as const,
respondTo: "allowlist" as const,
respondToAllowlist: [],
})),
],
});
await openBuzzProject(page);
@@ -362,12 +371,28 @@ test("review checks dispatch to a running local managed agent", async ({
const checks = page.getByTestId("project-review-checks");
const interfaceCheck = checks.getByTestId("project-review-check").first();
const debugHarness = page.getByTestId("project-review-debug-harness-trigger");
await expect(debugHarness).toHaveCount(1);
await expect(debugHarness).toHaveAccessibleName(
"Review check debug harness, agent Roof",
);
await debugHarness.click();
await expect(
checks.getByTestId("project-review-check-agent-picker"),
).toHaveCount(3);
page.getByTestId("project-review-debug-agent-select"),
).toHaveValue(LOCAL_REVIEW_AGENT_PUBKEY);
await expect(
interfaceCheck.getByTestId("project-review-check-agent-picker"),
).toContainText("Roof");
page
.getByTestId("project-review-debug-agent-select")
.locator("option", { hasText: "Stopped prototype" }),
).toHaveCount(0);
await expect(
page.getByTestId("project-review-debug-offline-agent-count"),
).toHaveText("17 offline agents");
expect(
(await page.getByTestId("project-review-debug-harness-menu").boundingBox())
?.height,
).toBeLessThan(220);
await page.keyboard.press("Escape");
const revalidationCountBefore = await page.evaluate(
() =>
window.__BUZZ_E2E_COMMANDS__?.filter(