refactor: Rename agent modes breakdown→plan, decompose→detail
Full rename across the codebase for clarity: - breakdown (initiative→phases) is now "plan" - decompose (phase→tasks) is now "detail" Updates schema enums, TypeScript types, events, prompts, output handler, tRPC procedures, CLI commands, frontend components, tests, and docs. Also fixes 0022 migration multi-statement issue (adds statement-breakpoint markers).
This commit is contained in:
@@ -10,8 +10,8 @@ interface ChangeSetBannerProps {
|
||||
}
|
||||
|
||||
const MODE_LABELS: Record<string, string> = {
|
||||
breakdown: "phases",
|
||||
decompose: "tasks",
|
||||
plan: "phases",
|
||||
detail: "tasks",
|
||||
refine: "pages",
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { topologicalSortPhases, type DependencyEdge } from "@codewalk-district/s
|
||||
import {
|
||||
ExecutionProvider,
|
||||
PhaseActions,
|
||||
BreakdownSection,
|
||||
PlanSection,
|
||||
TaskModal,
|
||||
type PhaseData,
|
||||
} from "@/components/execution";
|
||||
@@ -22,7 +22,7 @@ interface ExecutionTabProps {
|
||||
phasesLoading: boolean;
|
||||
phasesLoaded: boolean;
|
||||
dependencyEdges: DependencyEdge[];
|
||||
mergeTarget?: string | null;
|
||||
branch?: string | null;
|
||||
}
|
||||
|
||||
export function ExecutionTab({
|
||||
@@ -31,7 +31,7 @@ export function ExecutionTab({
|
||||
phasesLoading,
|
||||
phasesLoaded,
|
||||
dependencyEdges,
|
||||
mergeTarget,
|
||||
branch,
|
||||
}: ExecutionTabProps) {
|
||||
// Topological sort
|
||||
const sortedPhases = useMemo(
|
||||
@@ -53,7 +53,7 @@ export function ExecutionTab({
|
||||
return map;
|
||||
}, [dependencyEdges, sortedPhases]);
|
||||
|
||||
// Decompose agent tracking: map phaseId → most recent active decompose agent
|
||||
// Detail agent tracking: map phaseId → most recent active detail agent
|
||||
const agentsQuery = trpc.listAgents.useQuery();
|
||||
const allAgents = agentsQuery.data ?? [];
|
||||
|
||||
@@ -133,8 +133,8 @@ export function ExecutionTab({
|
||||
return { taskCountsByPhase: counts, tasksByPhase: grouped };
|
||||
}, [allTasks]);
|
||||
|
||||
// Map phaseId → most recent active decompose agent
|
||||
const decomposeAgentByPhase = useMemo(() => {
|
||||
// Map phaseId → most recent active detail agent
|
||||
const detailAgentByPhase = useMemo(() => {
|
||||
const map = new Map<string, (typeof allAgents)[number]>();
|
||||
// Build taskId → phaseId lookup from allTasks
|
||||
const taskPhaseMap = new Map<string, string>();
|
||||
@@ -143,7 +143,7 @@ export function ExecutionTab({
|
||||
}
|
||||
const candidates = allAgents.filter(
|
||||
(a) =>
|
||||
a.mode === "decompose" &&
|
||||
a.mode === "detail" &&
|
||||
a.initiativeId === initiativeId &&
|
||||
["running", "waiting_for_input", "idle"].includes(a.status) &&
|
||||
!a.userDismissedAt,
|
||||
@@ -163,7 +163,7 @@ export function ExecutionTab({
|
||||
return map;
|
||||
}, [allAgents, allTasks, initiativeId]);
|
||||
|
||||
// Phase IDs that have zero tasks (eligible for breakdown)
|
||||
// Phase IDs that have zero tasks (eligible for detailing)
|
||||
const phasesWithoutTasks = useMemo(
|
||||
() =>
|
||||
sortedPhases
|
||||
@@ -178,11 +178,11 @@ export function ExecutionTab({
|
||||
[sortedPhases],
|
||||
);
|
||||
|
||||
// No phases yet and not adding — show breakdown section
|
||||
// No phases yet and not adding — show plan section
|
||||
if (phasesLoaded && sortedPhases.length === 0 && !isAddingPhase) {
|
||||
return (
|
||||
<ExecutionProvider>
|
||||
<BreakdownSection
|
||||
<PlanSection
|
||||
initiativeId={initiativeId}
|
||||
phasesLoaded={phasesLoaded}
|
||||
phases={sortedPhases}
|
||||
@@ -209,7 +209,7 @@ export function ExecutionTab({
|
||||
phases={sortedPhases}
|
||||
onAddPhase={handleStartAdd}
|
||||
phasesWithoutTasks={phasesWithoutTasks}
|
||||
decomposeAgentByPhase={decomposeAgentByPhase}
|
||||
detailAgentByPhase={detailAgentByPhase}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -258,8 +258,8 @@ export function ExecutionTab({
|
||||
tasks={tasksByPhase[activePhase.id] ?? []}
|
||||
tasksLoading={allTasksQuery.isLoading}
|
||||
onDelete={() => deletePhase.mutate({ id: activePhase.id })}
|
||||
decomposeAgent={decomposeAgentByPhase.get(activePhase.id) ?? null}
|
||||
mergeTarget={mergeTarget}
|
||||
detailAgent={detailAgentByPhase.get(activePhase.id) ?? null}
|
||||
branch={branch}
|
||||
/>
|
||||
) : (
|
||||
<PhaseDetailEmpty />
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface SerializedInitiative {
|
||||
name: string;
|
||||
status: "active" | "completed" | "archived";
|
||||
mergeRequiresApproval: boolean;
|
||||
mergeTarget: string | null;
|
||||
branch: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export interface SerializedInitiative {
|
||||
interface InitiativeCardProps {
|
||||
initiative: SerializedInitiative;
|
||||
onView: () => void;
|
||||
onSpawnArchitect: (mode: "discuss" | "breakdown") => void;
|
||||
onSpawnArchitect: (mode: "discuss" | "plan") => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
@@ -94,9 +94,9 @@ export function InitiativeCard({
|
||||
Discuss
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onSpawnArchitect("breakdown")}
|
||||
onClick={() => onSpawnArchitect("plan")}
|
||||
>
|
||||
Breakdown
|
||||
Plan
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -11,7 +11,7 @@ interface InitiativeListProps {
|
||||
onViewInitiative: (id: string) => void;
|
||||
onSpawnArchitect: (
|
||||
initiativeId: string,
|
||||
mode: "discuss" | "breakdown",
|
||||
mode: "discuss" | "plan",
|
||||
) => void;
|
||||
onDeleteInitiative: (id: string) => void;
|
||||
}
|
||||
|
||||
@@ -31,18 +31,18 @@ export function SpawnArchitectDropdown({
|
||||
onSuccess: handleSuccess,
|
||||
});
|
||||
|
||||
const breakdownSpawn = useSpawnMutation(trpc.spawnArchitectBreakdown.useMutation, {
|
||||
const planSpawn = useSpawnMutation(trpc.spawnArchitectPlan.useMutation, {
|
||||
onSuccess: handleSuccess,
|
||||
});
|
||||
|
||||
const isPending = discussSpawn.isSpawning || breakdownSpawn.isSpawning;
|
||||
const isPending = discussSpawn.isSpawning || planSpawn.isSpawning;
|
||||
|
||||
function handleDiscuss() {
|
||||
discussSpawn.spawn({ initiativeId });
|
||||
}
|
||||
|
||||
function handleBreakdown() {
|
||||
breakdownSpawn.spawn({ initiativeId });
|
||||
function handlePlan() {
|
||||
planSpawn.spawn({ initiativeId });
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -57,8 +57,8 @@ export function SpawnArchitectDropdown({
|
||||
<DropdownMenuItem onClick={handleDiscuss} disabled={isPending}>
|
||||
Discuss
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleBreakdown} disabled={isPending}>
|
||||
Breakdown
|
||||
<DropdownMenuItem onClick={handlePlan} disabled={isPending}>
|
||||
Plan
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Loader2, Plus, Sparkles } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
@@ -8,45 +8,55 @@ interface PhaseActionsProps {
|
||||
phases: Array<{ id: string; status: string }>;
|
||||
onAddPhase: () => void;
|
||||
phasesWithoutTasks: string[];
|
||||
decomposeAgentByPhase: Map<string, { id: string; status: string }>;
|
||||
detailAgentByPhase: Map<string, { id: string; status: string }>;
|
||||
}
|
||||
|
||||
export function PhaseActions({
|
||||
onAddPhase,
|
||||
phasesWithoutTasks,
|
||||
decomposeAgentByPhase,
|
||||
detailAgentByPhase,
|
||||
}: PhaseActionsProps) {
|
||||
const decomposeMutation = trpc.spawnArchitectDecompose.useMutation();
|
||||
const detailMutation = trpc.spawnArchitectDetail.useMutation();
|
||||
const [isDetailingAll, setIsDetailingAll] = useState(false);
|
||||
|
||||
// Phases eligible for breakdown: no tasks AND no active decompose agent
|
||||
// Phases eligible for detailing: no tasks AND no active detail agent
|
||||
const eligiblePhaseIds = useMemo(
|
||||
() => phasesWithoutTasks.filter((id) => !decomposeAgentByPhase.has(id)),
|
||||
[phasesWithoutTasks, decomposeAgentByPhase],
|
||||
() => phasesWithoutTasks.filter((id) => !detailAgentByPhase.has(id)),
|
||||
[phasesWithoutTasks, detailAgentByPhase],
|
||||
);
|
||||
|
||||
// Count of phases currently being decomposed
|
||||
const activeDecomposeCount = useMemo(() => {
|
||||
// Count of phases currently being detailed
|
||||
const activeDetailCount = useMemo(() => {
|
||||
let count = 0;
|
||||
for (const [, agent] of decomposeAgentByPhase) {
|
||||
for (const [, agent] of detailAgentByPhase) {
|
||||
if (agent.status === "running" || agent.status === "waiting_for_input") {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}, [decomposeAgentByPhase]);
|
||||
}, [detailAgentByPhase]);
|
||||
|
||||
const handleBreakdownAll = useCallback(() => {
|
||||
for (const phaseId of eligiblePhaseIds) {
|
||||
decomposeMutation.mutate({ phaseId });
|
||||
const handleDetailAll = useCallback(async () => {
|
||||
setIsDetailingAll(true);
|
||||
try {
|
||||
for (const phaseId of eligiblePhaseIds) {
|
||||
try {
|
||||
await detailMutation.mutateAsync({ phaseId });
|
||||
} catch {
|
||||
// CONFLICT errors expected if agent already exists — continue
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setIsDetailingAll(false);
|
||||
}
|
||||
}, [eligiblePhaseIds, decomposeMutation]);
|
||||
}, [eligiblePhaseIds, detailMutation]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{activeDecomposeCount > 0 && (
|
||||
{activeDetailCount > 0 && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
Decomposing ({activeDecomposeCount})
|
||||
Detailing ({activeDetailCount})
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
@@ -61,12 +71,16 @@ export function PhaseActions({
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={eligiblePhaseIds.length === 0}
|
||||
onClick={handleBreakdownAll}
|
||||
disabled={eligiblePhaseIds.length === 0 || isDetailingAll}
|
||||
onClick={handleDetailAll}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
Breakdown All
|
||||
{isDetailingAll ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
)}
|
||||
Detail All
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -36,8 +36,8 @@ interface PhaseDetailPanelProps {
|
||||
tasks: SerializedTask[];
|
||||
tasksLoading: boolean;
|
||||
onDelete?: () => void;
|
||||
mergeTarget?: string | null;
|
||||
decomposeAgent: {
|
||||
branch?: string | null;
|
||||
detailAgent: {
|
||||
id: string;
|
||||
status: string;
|
||||
createdAt: string | Date;
|
||||
@@ -53,8 +53,8 @@ export function PhaseDetailPanel({
|
||||
tasks,
|
||||
tasksLoading,
|
||||
onDelete,
|
||||
mergeTarget,
|
||||
decomposeAgent,
|
||||
branch,
|
||||
detailAgent,
|
||||
}: PhaseDetailPanelProps) {
|
||||
const { setSelectedTaskId, handleTaskCounts, handleRegisterTasks } =
|
||||
useExecutionContext();
|
||||
@@ -137,46 +137,46 @@ export function PhaseDetailPanel({
|
||||
handleRegisterTasks(phase.id, entries);
|
||||
}, [tasks, phase.id, displayIndex, phase.name, handleTaskCounts, handleRegisterTasks]);
|
||||
|
||||
// --- Change sets for decompose agent ---
|
||||
// --- Change sets for detail agent ---
|
||||
const changeSetsQuery = trpc.listChangeSets.useQuery(
|
||||
{ agentId: decomposeAgent?.id ?? "" },
|
||||
{ enabled: !!decomposeAgent && decomposeAgent.status === "idle" },
|
||||
{ agentId: detailAgent?.id ?? "" },
|
||||
{ enabled: !!detailAgent && detailAgent.status === "idle" },
|
||||
);
|
||||
const latestChangeSet = useMemo(
|
||||
() => (changeSetsQuery.data ?? []).find((cs) => cs.status === "applied") ?? null,
|
||||
[changeSetsQuery.data],
|
||||
);
|
||||
|
||||
// --- Decompose spawn ---
|
||||
const decomposeMutation = trpc.spawnArchitectDecompose.useMutation();
|
||||
// --- Detail spawn ---
|
||||
const detailMutation = trpc.spawnArchitectDetail.useMutation();
|
||||
|
||||
const handleDecompose = useCallback(() => {
|
||||
decomposeMutation.mutate({ phaseId: phase.id });
|
||||
}, [phase.id, decomposeMutation]);
|
||||
const handleDetail = useCallback(() => {
|
||||
detailMutation.mutate({ phaseId: phase.id });
|
||||
}, [phase.id, detailMutation]);
|
||||
|
||||
// --- Dismiss handler for decompose agent ---
|
||||
// --- Dismiss handler for detail agent ---
|
||||
const dismissMutation = trpc.dismissAgent.useMutation();
|
||||
const handleDismissDecompose = useCallback(() => {
|
||||
if (!decomposeAgent) return;
|
||||
dismissMutation.mutate({ id: decomposeAgent.id });
|
||||
}, [decomposeAgent, dismissMutation]);
|
||||
const handleDismissDetail = useCallback(() => {
|
||||
if (!detailAgent) return;
|
||||
dismissMutation.mutate({ id: detailAgent.id });
|
||||
}, [detailAgent, dismissMutation]);
|
||||
|
||||
// Compute phase branch name if initiative has a merge target
|
||||
const phaseBranch = mergeTarget
|
||||
? `${mergeTarget}-phase-${phase.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`
|
||||
const phaseBranch = branch
|
||||
? `${branch}-phase-${phase.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`
|
||||
: null;
|
||||
|
||||
const isPendingReview = phase.status === "pending_review";
|
||||
|
||||
const sortedTasks = sortByPriorityAndQueueTime(tasks);
|
||||
const hasTasks = tasks.length > 0;
|
||||
const isDecomposeRunning =
|
||||
decomposeAgent?.status === "running" ||
|
||||
decomposeAgent?.status === "waiting_for_input";
|
||||
const showBreakdownButton =
|
||||
!decomposeAgent && !hasTasks;
|
||||
const isDetailRunning =
|
||||
detailAgent?.status === "running" ||
|
||||
detailAgent?.status === "waiting_for_input";
|
||||
const showDetailButton =
|
||||
!detailAgent && !hasTasks;
|
||||
const showChangeSet =
|
||||
decomposeAgent?.status === "idle" && !!latestChangeSet;
|
||||
detailAgent?.status === "idle" && !!latestChangeSet;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -214,25 +214,25 @@ export function PhaseDetailPanel({
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Breakdown button in header */}
|
||||
{showBreakdownButton && (
|
||||
{/* Detail button in header */}
|
||||
{showDetailButton && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleDecompose}
|
||||
disabled={decomposeMutation.isPending}
|
||||
onClick={handleDetail}
|
||||
disabled={detailMutation.isPending}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
{decomposeMutation.isPending ? "Starting..." : "Breakdown"}
|
||||
{detailMutation.isPending ? "Starting..." : "Detail Tasks"}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Running indicator in header */}
|
||||
{isDecomposeRunning && (
|
||||
{isDetailRunning && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
Breaking down...
|
||||
Detailing...
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -342,11 +342,11 @@ export function PhaseDetailPanel({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Decompose change set */}
|
||||
{/* Detail change set */}
|
||||
{showChangeSet && (
|
||||
<ChangeSetBanner
|
||||
changeSet={latestChangeSet!}
|
||||
onDismiss={handleDismissDecompose}
|
||||
onDismiss={handleDismissDetail}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Skeleton } from "@/components/Skeleton";
|
||||
import { useExecutionContext, type PhaseData } from "./ExecutionContext";
|
||||
import { PhaseWithTasks } from "./PhaseWithTasks";
|
||||
import { BreakdownSection } from "./BreakdownSection";
|
||||
import { PlanSection } from "./PlanSection";
|
||||
|
||||
interface PhasesListProps {
|
||||
initiativeId: string;
|
||||
@@ -35,7 +35,7 @@ export function PhasesList({
|
||||
|
||||
if (phasesLoaded && phases.length === 0) {
|
||||
return (
|
||||
<BreakdownSection
|
||||
<PlanSection
|
||||
initiativeId={initiativeId}
|
||||
phasesLoaded={phasesLoaded}
|
||||
phases={phases}
|
||||
|
||||
@@ -5,27 +5,27 @@ import { trpc } from "@/lib/trpc";
|
||||
import { useSpawnMutation } from "@/hooks/useSpawnMutation";
|
||||
import { ChangeSetBanner } from "@/components/ChangeSetBanner";
|
||||
|
||||
interface BreakdownSectionProps {
|
||||
interface PlanSectionProps {
|
||||
initiativeId: string;
|
||||
phasesLoaded: boolean;
|
||||
phases: Array<{ status: string }>;
|
||||
onAddPhase?: () => void;
|
||||
}
|
||||
|
||||
export function BreakdownSection({
|
||||
export function PlanSection({
|
||||
initiativeId,
|
||||
phasesLoaded,
|
||||
phases,
|
||||
onAddPhase,
|
||||
}: BreakdownSectionProps) {
|
||||
// Breakdown agent tracking
|
||||
}: PlanSectionProps) {
|
||||
// Plan agent tracking
|
||||
const agentsQuery = trpc.listAgents.useQuery();
|
||||
const allAgents = agentsQuery.data ?? [];
|
||||
const breakdownAgent = useMemo(() => {
|
||||
const planAgent = useMemo(() => {
|
||||
const candidates = allAgents
|
||||
.filter(
|
||||
(a) =>
|
||||
a.mode === "breakdown" &&
|
||||
a.mode === "plan" &&
|
||||
a.initiativeId === initiativeId &&
|
||||
["running", "waiting_for_input", "idle"].includes(a.status),
|
||||
)
|
||||
@@ -36,12 +36,12 @@ export function BreakdownSection({
|
||||
return candidates[0] ?? null;
|
||||
}, [allAgents, initiativeId]);
|
||||
|
||||
const isBreakdownRunning = breakdownAgent?.status === "running";
|
||||
const isPlanRunning = planAgent?.status === "running";
|
||||
|
||||
// Query change sets when we have a completed breakdown agent
|
||||
// Query change sets when we have a completed plan agent
|
||||
const changeSetsQuery = trpc.listChangeSets.useQuery(
|
||||
{ agentId: breakdownAgent?.id ?? "" },
|
||||
{ enabled: !!breakdownAgent && breakdownAgent.status === "idle" },
|
||||
{ agentId: planAgent?.id ?? "" },
|
||||
{ enabled: !!planAgent && planAgent.status === "idle" },
|
||||
);
|
||||
const latestChangeSet = useMemo(
|
||||
() => (changeSetsQuery.data ?? []).find((cs) => cs.status === "applied") ?? null,
|
||||
@@ -50,18 +50,18 @@ export function BreakdownSection({
|
||||
|
||||
const dismissMutation = trpc.dismissAgent.useMutation();
|
||||
|
||||
const breakdownSpawn = useSpawnMutation(trpc.spawnArchitectBreakdown.useMutation, {
|
||||
const planSpawn = useSpawnMutation(trpc.spawnArchitectPlan.useMutation, {
|
||||
showToast: false,
|
||||
});
|
||||
|
||||
const handleBreakdown = useCallback(() => {
|
||||
breakdownSpawn.spawn({ initiativeId });
|
||||
}, [initiativeId, breakdownSpawn]);
|
||||
const handlePlan = useCallback(() => {
|
||||
planSpawn.spawn({ initiativeId });
|
||||
}, [initiativeId, planSpawn]);
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
if (!breakdownAgent) return;
|
||||
dismissMutation.mutate({ id: breakdownAgent.id });
|
||||
}, [breakdownAgent, dismissMutation]);
|
||||
if (!planAgent) return;
|
||||
dismissMutation.mutate({ id: planAgent.id });
|
||||
}, [planAgent, dismissMutation]);
|
||||
|
||||
// Don't render during loading
|
||||
if (!phasesLoaded) {
|
||||
@@ -73,8 +73,8 @@ export function BreakdownSection({
|
||||
return null;
|
||||
}
|
||||
|
||||
// Show change set banner when breakdown agent completed
|
||||
if (breakdownAgent?.status === "idle" && latestChangeSet) {
|
||||
// Show change set banner when plan agent completed
|
||||
if (planAgent?.status === "idle" && latestChangeSet) {
|
||||
return (
|
||||
<div className="py-4">
|
||||
<ChangeSetBanner
|
||||
@@ -88,24 +88,24 @@ export function BreakdownSection({
|
||||
return (
|
||||
<div className="py-8 text-center space-y-3">
|
||||
<p className="text-muted-foreground">No phases yet</p>
|
||||
{isBreakdownRunning ? (
|
||||
{isPlanRunning ? (
|
||||
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Breaking down initiative...
|
||||
Planning phases...
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleBreakdown}
|
||||
disabled={breakdownSpawn.isSpawning}
|
||||
onClick={handlePlan}
|
||||
disabled={planSpawn.isSpawning}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
{breakdownSpawn.isSpawning
|
||||
{planSpawn.isSpawning
|
||||
? "Starting..."
|
||||
: "Break Down Initiative"}
|
||||
: "Plan Phases"}
|
||||
</Button>
|
||||
{onAddPhase && (
|
||||
<>
|
||||
@@ -123,9 +123,9 @@ export function BreakdownSection({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{breakdownSpawn.isError && (
|
||||
{planSpawn.isError && (
|
||||
<p className="text-xs text-destructive">
|
||||
{breakdownSpawn.error}
|
||||
{planSpawn.error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
export { ExecutionProvider, useExecutionContext } from "./ExecutionContext";
|
||||
export { BreakdownSection } from "./BreakdownSection";
|
||||
export { PlanSection } from "./PlanSection";
|
||||
export { PhaseActions } from "./PhaseActions";
|
||||
export { PhaseSidebarItem } from "./PhaseSidebarItem";
|
||||
export { PhaseDetailPanel, PhaseDetailEmpty } from "./PhaseDetailPanel";
|
||||
|
||||
@@ -42,8 +42,8 @@ const INVALIDATION_MAP: Partial<Record<MutationName, QueryName[]>> = {
|
||||
// --- Architect spawns ---
|
||||
spawnArchitectRefine: ["listAgents"],
|
||||
spawnArchitectDiscuss: ["listAgents"],
|
||||
spawnArchitectBreakdown: ["listAgents"],
|
||||
spawnArchitectDecompose: ["listAgents", "listInitiativeTasks"],
|
||||
spawnArchitectPlan: ["listAgents"],
|
||||
spawnArchitectDetail: ["listAgents", "listInitiativeTasks"],
|
||||
|
||||
// --- Initiatives ---
|
||||
createInitiative: ["listInitiatives"],
|
||||
|
||||
11
packages/web/src/lib/labels.ts
Normal file
11
packages/web/src/lib/labels.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const MODE_LABELS: Record<string, string> = {
|
||||
plan: 'Plan',
|
||||
detail: 'Detail',
|
||||
discuss: 'Discuss',
|
||||
refine: 'Refine',
|
||||
execute: 'Execute',
|
||||
};
|
||||
|
||||
export function modeLabel(mode: string): string {
|
||||
return MODE_LABELS[mode] ?? mode;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { AgentOutputViewer } from "@/components/AgentOutputViewer";
|
||||
import { AgentActions } from "@/components/AgentActions";
|
||||
import { formatRelativeTime } from "@/lib/utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { modeLabel } from "@/lib/labels";
|
||||
import { StatusDot } from "@/components/StatusDot";
|
||||
import { useLiveUpdates } from "@/hooks";
|
||||
|
||||
@@ -247,7 +248,7 @@ function AgentsPage() {
|
||||
{agent.provider}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{agent.mode}
|
||||
{modeLabel(agent.mode)}
|
||||
</Badge>
|
||||
{/* Action dropdown */}
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
|
||||
@@ -10,8 +10,8 @@ import { ReviewTab } from "@/components/review";
|
||||
import { PipelineTab } from "@/components/pipeline";
|
||||
import { useLiveUpdates } from "@/hooks";
|
||||
|
||||
type Tab = "content" | "breakdown" | "execution" | "review";
|
||||
const TABS: Tab[] = ["content", "breakdown", "execution", "review"];
|
||||
type Tab = "content" | "plan" | "execution" | "review";
|
||||
const TABS: Tab[] = ["content", "plan", "execution", "review"];
|
||||
|
||||
export const Route = createFileRoute("/initiatives/$id")({
|
||||
component: InitiativeDetailPage,
|
||||
@@ -90,7 +90,7 @@ function InitiativeDetailPage() {
|
||||
name: initiative.name,
|
||||
status: initiative.status,
|
||||
executionMode: (initiative as any).executionMode as string | undefined,
|
||||
mergeTarget: (initiative as any).mergeTarget as string | null | undefined,
|
||||
branch: (initiative as any).branch as string | null | undefined,
|
||||
};
|
||||
|
||||
const projects = (initiative as { projects?: Array<{ id: string; name: string; url: string }> }).projects;
|
||||
@@ -130,14 +130,14 @@ function InitiativeDetailPage() {
|
||||
|
||||
{/* Tab content */}
|
||||
{activeTab === "content" && <ContentTab initiativeId={id} initiativeName={initiative.name} />}
|
||||
{activeTab === "breakdown" && (
|
||||
{activeTab === "plan" && (
|
||||
<ExecutionTab
|
||||
initiativeId={id}
|
||||
phases={phases}
|
||||
phasesLoading={phasesQuery.isLoading}
|
||||
phasesLoaded={phasesQuery.isSuccess}
|
||||
dependencyEdges={depsQuery.data ?? []}
|
||||
mergeTarget={serializedInitiative.mergeTarget}
|
||||
branch={serializedInitiative.branch}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "execution" && (
|
||||
|
||||
Reference in New Issue
Block a user