From ca0f3ecedfdce95cdecdfb60709f8555284a883c Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 14 Jan 2026 21:02:24 +0100 Subject: [PATCH 1/4] fix: adjust task progress panel height and improve effective todos handling in agent info panel - Reduced the maximum height of the task progress panel from 300px to 200px for better UI consistency. - Introduced a new `effectiveTodos` calculation in the agent info panel to correctly display tasks from `planSpec` when available, ensuring accurate task counts and statuses. - Updated references to use `effectiveTodos` instead of the original `agentInfo.todos` for task display logic in the agent info panel. - Adjusted the height of various modal components to align with the new task progress panel height. --- .../src/components/ui/task-progress-panel.tsx | 2 +- .../kanban-card/agent-info-panel.tsx | 36 +++++++++++++------ .../board-view/dialogs/agent-output-modal.tsx | 8 ++--- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/apps/ui/src/components/ui/task-progress-panel.tsx b/apps/ui/src/components/ui/task-progress-panel.tsx index 5cb5826f..414be1e7 100644 --- a/apps/ui/src/components/ui/task-progress-panel.tsx +++ b/apps/ui/src/components/ui/task-progress-panel.tsx @@ -230,7 +230,7 @@ export function TaskProgressPanel({ )} >
-
+
{/* Vertical Connector Line */}
diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx index 87268652..60eba50f 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx @@ -1,6 +1,6 @@ // @ts-nocheck -import { useEffect, useState } from 'react'; -import { Feature, ThinkingLevel } from '@/store/app-store'; +import { useEffect, useState, useMemo } from 'react'; +import { Feature, ThinkingLevel, ParsedTask } from '@/store/app-store'; import type { ReasoningEffort } from '@automaker/types'; import { getProviderFromModel } from '@/lib/utils'; import { @@ -72,6 +72,22 @@ export function AgentInfoPanel({ const [isSummaryDialogOpen, setIsSummaryDialogOpen] = useState(false); const [isTodosExpanded, setIsTodosExpanded] = useState(false); + // Derive effective todos from planSpec.tasks when available, fallback to agentInfo.todos + // This fixes the issue where Kanban cards show "0/0 tasks" when the agent uses planSpec + // instead of emitting TodoWrite tool calls in the output log + const effectiveTodos = useMemo(() => { + // First priority: use planSpec.tasks if available (modern approach) + if (feature.planSpec?.tasks && feature.planSpec.tasks.length > 0) { + return feature.planSpec.tasks.map((task: ParsedTask) => ({ + content: task.description, + // Map 'failed' status to 'pending' since todo display doesn't support 'failed' + status: task.status === 'failed' ? 'pending' : task.status, + })); + } + // Fallback: use parsed agentInfo.todos from agent-output.md + return agentInfo?.todos || []; + }, [feature.planSpec?.tasks, agentInfo?.todos]); + useEffect(() => { const loadContext = async () => { if (contextContent) { @@ -189,13 +205,13 @@ export function AgentInfoPanel({
{/* Task List Progress */} - {agentInfo.todos.length > 0 && ( + {effectiveTodos.length > 0 && (
- {agentInfo.todos.filter((t) => t.status === 'completed').length}/ - {agentInfo.todos.length} tasks + {effectiveTodos.filter((t) => t.status === 'completed').length}/ + {effectiveTodos.length} tasks
- {(isTodosExpanded ? agentInfo.todos : agentInfo.todos.slice(0, 3)).map( + {(isTodosExpanded ? effectiveTodos : effectiveTodos.slice(0, 3)).map( (todo, idx) => (
{todo.status === 'completed' ? ( @@ -227,7 +243,7 @@ export function AgentInfoPanel({
) )} - {agentInfo.todos.length > 3 && ( + {effectiveTodos.length > 3 && ( )}
@@ -286,10 +302,10 @@ export function AgentInfoPanel({ {agentInfo.toolCallCount} tool calls - {agentInfo.todos.length > 0 && ( + {effectiveTodos.length > 0 && ( - {agentInfo.todos.filter((t) => t.status === 'completed').length} tasks done + {effectiveTodos.filter((t) => t.status === 'completed').length} tasks done )}
diff --git a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx index 7b900409..f96c5807 100644 --- a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx @@ -380,11 +380,11 @@ export function AgentOutputModal({ {effectiveViewMode === 'changes' ? ( -
+
{projectPath ? ( ) : effectiveViewMode === 'summary' && summary ? ( -
+
{summary}
) : ( @@ -409,7 +409,7 @@ export function AgentOutputModal({
{isLoading && !output ? (
From 2b93afbd438f9c63a45dd46f3d61cfc05d259824 Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 14 Jan 2026 21:03:54 +0100 Subject: [PATCH 2/4] Changes from feature/v0.11.0rc-1768413909856-a0al --- package-lock.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d26140a..00e0d253 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11595,7 +11595,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11617,7 +11616,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11660,7 +11658,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11682,7 +11679,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11704,7 +11700,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11726,7 +11721,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11748,7 +11742,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11770,7 +11763,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11792,7 +11784,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -17015,7 +17006,6 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, From 724858d215d906d108ccc8fdede7219fd6bf3cb9 Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 14 Jan 2026 21:12:48 +0100 Subject: [PATCH 3/4] fix: adjust more --- .../components/views/board-view/dialogs/agent-output-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx index f96c5807..ab2b0732 100644 --- a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx @@ -380,7 +380,7 @@ export function AgentOutputModal({ {effectiveViewMode === 'changes' ? ( From f6bda66ed4ebf5c055ed1cb4bfe5789a9ec3b21e Mon Sep 17 00:00:00 2001 From: Shirone Date: Wed, 14 Jan 2026 22:39:47 +0100 Subject: [PATCH 4/4] feat: enhance agent info panel with real-time task status updates and fresh planSpec integration - Added support for real-time task status updates using WebSocket events, allowing the Kanban card to reflect current task progress accurately. - Introduced a new state for fresh planSpec data fetched from the API to ensure the agent info panel displays up-to-date task information. - Updated the effectiveTodos calculation to prioritize fresh planSpec data and incorporate real-time status, improving task display accuracy. - Enhanced the logic to listen for relevant WebSocket events and update task statuses accordingly, ensuring synchronization with the agent output modal. --- .../kanban-card/agent-info-panel.tsx | 146 ++++++++++++++++-- 1 file changed, 129 insertions(+), 17 deletions(-) diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx index 60eba50f..6916222e 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx @@ -10,6 +10,7 @@ import { DEFAULT_MODEL, } from '@/lib/agent-context-parser'; import { cn } from '@/lib/utils'; +import type { AutoModeEvent } from '@/types/electron'; import { Brain, ListTodo, @@ -71,22 +72,66 @@ export function AgentInfoPanel({ const [agentInfo, setAgentInfo] = useState(null); const [isSummaryDialogOpen, setIsSummaryDialogOpen] = useState(false); const [isTodosExpanded, setIsTodosExpanded] = useState(false); + // Track real-time task status updates from WebSocket events + const [taskStatusMap, setTaskStatusMap] = useState< + Map + >(new Map()); + // Fresh planSpec data fetched from API (store data is stale for task progress) + const [freshPlanSpec, setFreshPlanSpec] = useState<{ + tasks?: ParsedTask[]; + tasksCompleted?: number; + currentTaskId?: string; + } | null>(null); // Derive effective todos from planSpec.tasks when available, fallback to agentInfo.todos - // This fixes the issue where Kanban cards show "0/0 tasks" when the agent uses planSpec - // instead of emitting TodoWrite tool calls in the output log + // Uses freshPlanSpec (from API) for accurate progress, with taskStatusMap for real-time updates const effectiveTodos = useMemo(() => { + // Use freshPlanSpec if available (fetched from API), fallback to store's feature.planSpec + const planSpec = freshPlanSpec?.tasks?.length ? freshPlanSpec : feature.planSpec; + // First priority: use planSpec.tasks if available (modern approach) - if (feature.planSpec?.tasks && feature.planSpec.tasks.length > 0) { - return feature.planSpec.tasks.map((task: ParsedTask) => ({ - content: task.description, - // Map 'failed' status to 'pending' since todo display doesn't support 'failed' - status: task.status === 'failed' ? 'pending' : task.status, - })); + if (planSpec?.tasks && planSpec.tasks.length > 0) { + const completedCount = planSpec.tasksCompleted || 0; + const currentTaskId = planSpec.currentTaskId; + + return planSpec.tasks.map((task: ParsedTask, index: number) => { + // Use real-time status from WebSocket events if available + const realtimeStatus = taskStatusMap.get(task.id); + + // Calculate status: WebSocket status > index-based status > task.status + let effectiveStatus: 'pending' | 'in_progress' | 'completed'; + if (realtimeStatus) { + effectiveStatus = realtimeStatus; + } else if (index < completedCount) { + effectiveStatus = 'completed'; + } else if (task.id === currentTaskId) { + effectiveStatus = 'in_progress'; + } else { + // Fallback to task.status if available, otherwise pending + effectiveStatus = + task.status === 'completed' + ? 'completed' + : task.status === 'in_progress' + ? 'in_progress' + : 'pending'; + } + + return { + content: task.description, + status: effectiveStatus, + }; + }); } // Fallback: use parsed agentInfo.todos from agent-output.md return agentInfo?.todos || []; - }, [feature.planSpec?.tasks, agentInfo?.todos]); + }, [ + freshPlanSpec, + feature.planSpec?.tasks, + feature.planSpec?.tasksCompleted, + feature.planSpec?.currentTaskId, + agentInfo?.todos, + taskStatusMap, + ]); useEffect(() => { const loadContext = async () => { @@ -98,6 +143,7 @@ export function AgentInfoPanel({ if (feature.status === 'backlog') { setAgentInfo(null); + setFreshPlanSpec(null); return; } @@ -107,6 +153,21 @@ export function AgentInfoPanel({ if (!currentProject?.path) return; if (api.features) { + // Fetch fresh feature data to get up-to-date planSpec (store data is stale) + try { + const featureResult = await api.features.get(currentProject.path, feature.id); + const freshFeature: any = (featureResult as any).feature; + if (featureResult.success && freshFeature?.planSpec) { + setFreshPlanSpec({ + tasks: freshFeature.planSpec.tasks, + tasksCompleted: freshFeature.planSpec.tasksCompleted || 0, + currentTaskId: freshFeature.planSpec.currentTaskId, + }); + } + } catch { + // Ignore errors fetching fresh planSpec + } + const result = await api.features.getAgentOutput(currentProject.path, feature.id); if (result.success && result.content) { @@ -129,13 +190,62 @@ export function AgentInfoPanel({ loadContext(); - if (isCurrentAutoTask) { + // Poll for updates when feature is in_progress (not just isCurrentAutoTask) + // This ensures planSpec progress stays in sync + if (isCurrentAutoTask || feature.status === 'in_progress') { const interval = setInterval(loadContext, 3000); return () => { clearInterval(interval); }; } }, [feature.id, feature.status, contextContent, isCurrentAutoTask]); + + // Listen to WebSocket events for real-time task status updates + // This ensures the Kanban card shows the same progress as the Agent Output modal + // Listen for ANY in-progress feature with planSpec tasks, not just isCurrentAutoTask + const hasPlanSpecTasks = + (freshPlanSpec?.tasks?.length ?? 0) > 0 || (feature.planSpec?.tasks?.length ?? 0) > 0; + const shouldListenToEvents = feature.status === 'in_progress' && hasPlanSpecTasks; + + useEffect(() => { + if (!shouldListenToEvents) return; + + const api = getElectronAPI(); + if (!api?.autoMode) return; + + const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => { + // Only handle events for this feature + if (!('featureId' in event) || event.featureId !== feature.id) return; + + switch (event.type) { + case 'auto_mode_task_started': + if ('taskId' in event) { + const taskEvent = event as Extract; + setTaskStatusMap((prev) => { + const newMap = new Map(prev); + // Mark current task as in_progress + newMap.set(taskEvent.taskId, 'in_progress'); + return newMap; + }); + } + break; + + case 'auto_mode_task_complete': + if ('taskId' in event) { + const taskEvent = event as Extract; + setTaskStatusMap((prev) => { + const newMap = new Map(prev); + newMap.set(taskEvent.taskId, 'completed'); + return newMap; + }); + } + break; + } + }); + + return unsubscribe; + }, [feature.id, shouldListenToEvents]); + // Model/Preset Info for Backlog Cards if (feature.status === 'backlog') { const provider = getProviderFromModel(feature.model); @@ -174,7 +284,9 @@ export function AgentInfoPanel({ } // Agent Info Panel for non-backlog cards - if (feature.status !== 'backlog' && agentInfo) { + // Show panel if we have agentInfo OR planSpec.tasks (for spec/full mode) + // Note: hasPlanSpecTasks is already defined above and includes freshPlanSpec + if (feature.status !== 'backlog' && (agentInfo || hasPlanSpecTasks)) { return ( <>
@@ -187,7 +299,7 @@ export function AgentInfoPanel({ })()} {formatModelName(feature.model ?? DEFAULT_MODEL)}
- {agentInfo.currentPhase && ( + {agentInfo?.currentPhase && (
- {(feature.summary || summary || agentInfo.summary) && ( + {(feature.summary || summary || agentInfo?.summary) && (
@@ -289,18 +401,18 @@ export function AgentInfoPanel({ onPointerDown={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} > - {feature.summary || summary || agentInfo.summary} + {feature.summary || summary || agentInfo?.summary}

)} {!feature.summary && !summary && - !agentInfo.summary && - agentInfo.toolCallCount > 0 && ( + !agentInfo?.summary && + (agentInfo?.toolCallCount ?? 0) > 0 && (
- {agentInfo.toolCallCount} tool calls + {agentInfo?.toolCallCount ?? 0} tool calls {effectiveTodos.length > 0 && (