Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 5d91bf8

Browse files
committed
feat: add task coordination dashboard for multi-agent delegation visibility
Adds a collapsible Task Delegation dashboard section inside the existing Roo Code webview panel, displayed below the TaskHeader when the current task is part of a multi-task delegation hierarchy. Features: - Tree view showing parent/child delegation relationships - Mode name + status badge (Active/Delegated/Completed) per task - Active task highlighting with visual indicator - Click-to-navigate to any task in the hierarchy - Collapsible panel header - Only visible during active delegation sessions (2+ tasks) - Supports custom modes Closes #12329
1 parent 22d845c commit 5d91bf8

6 files changed

Lines changed: 787 additions & 0 deletions

File tree

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { CheckpointWarning } from "./CheckpointWarning"
4444
import { QueuedMessages } from "./QueuedMessages"
4545
import { WorktreeSelector } from "./WorktreeSelector"
4646
import FileChangesPanel from "./FileChangesPanel"
47+
import { TaskDashboard } from "./task-dashboard"
4748
import DismissibleUpsell from "../common/DismissibleUpsell"
4849
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
4950
import { useScrollLifecycle } from "@src/hooks/useScrollLifecycle"
@@ -1615,6 +1616,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
16151616
todos={latestTodos}
16161617
/>
16171618

1619+
<TaskDashboard />
1620+
16181621
{checkpointWarning && (
16191622
<div className="px-3">
16201623
<CheckpointWarning warning={checkpointWarning} />
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { memo, useState, useCallback, useMemo } from "react"
2+
import { ChevronDown, ChevronRight, GitBranch } from "lucide-react"
3+
4+
import type { ModeConfig } from "@roo-code/types"
5+
6+
import { getAllModes } from "@roo/modes"
7+
8+
import { cn } from "@src/lib/utils"
9+
import { vscode } from "@src/utils/vscode"
10+
import { useExtensionState } from "@src/context/ExtensionStateContext"
11+
12+
import type { TaskTreeNode } from "./useTaskTree"
13+
import { useTaskTree } from "./useTaskTree"
14+
15+
/**
16+
* Status badge colors for task states.
17+
*/
18+
const statusConfig: Record<string, { label: string; className: string }> = {
19+
active: { label: "Active", className: "bg-vscode-charts-green text-white" },
20+
delegated: { label: "Delegated", className: "bg-vscode-charts-blue text-white" },
21+
completed: {
22+
label: "Completed",
23+
className: "bg-vscode-descriptionForeground/30 text-vscode-descriptionForeground",
24+
},
25+
}
26+
27+
interface TaskNodeRowProps {
28+
node: TaskTreeNode
29+
depth: number
30+
currentTaskId?: string
31+
modeMap: Map<string, ModeConfig>
32+
}
33+
34+
/**
35+
* A single row in the task tree, showing mode name, status badge,
36+
* and active indicator. Supports click-to-navigate.
37+
*/
38+
const TaskNodeRow = memo(({ node, depth, currentTaskId, modeMap }: TaskNodeRowProps) => {
39+
const { item, children } = node
40+
const isCurrentTask = item.id === currentTaskId
41+
const modeConfig = item.mode ? modeMap.get(item.mode) : undefined
42+
const modeName = modeConfig?.name ?? item.mode ?? "Unknown"
43+
const status = item.status ?? "active"
44+
const statusInfo = statusConfig[status] ?? statusConfig.active
45+
46+
const handleClick = useCallback(() => {
47+
vscode.postMessage({ type: "showTaskWithId", text: item.id })
48+
}, [item.id])
49+
50+
const handleKeyDown = useCallback(
51+
(e: React.KeyboardEvent) => {
52+
if (e.key === "Enter" || e.key === " ") {
53+
e.preventDefault()
54+
handleClick()
55+
}
56+
},
57+
[handleClick],
58+
)
59+
60+
// Truncate task description for display
61+
const taskSummary = item.task.length > 60 ? item.task.slice(0, 57) + "..." : item.task
62+
63+
return (
64+
<div data-testid={`task-node-${item.id}`}>
65+
<div
66+
className={cn(
67+
"group flex items-center gap-2 py-1.5 px-2 cursor-pointer rounded-sm transition-colors",
68+
"hover:bg-vscode-list-hoverBackground",
69+
isCurrentTask &&
70+
"bg-vscode-list-activeSelectionBackground/20 border-l-2 border-vscode-charts-green",
71+
!isCurrentTask && "border-l-2 border-transparent",
72+
)}
73+
style={{ paddingLeft: `${depth * 16 + 8}px` }}
74+
onClick={handleClick}
75+
role="button"
76+
tabIndex={0}
77+
onKeyDown={handleKeyDown}>
78+
{/* Mode icon/indicator */}
79+
<span
80+
className={cn(
81+
"shrink-0 size-2 rounded-full",
82+
status === "active" && "bg-vscode-charts-green",
83+
status === "delegated" && "bg-vscode-charts-blue",
84+
status === "completed" && "bg-vscode-descriptionForeground/50",
85+
)}
86+
/>
87+
88+
{/* Mode name */}
89+
<span
90+
className={cn(
91+
"text-xs font-medium shrink-0",
92+
isCurrentTask ? "text-vscode-foreground" : "text-vscode-descriptionForeground",
93+
)}>
94+
{modeName}
95+
</span>
96+
97+
{/* Status badge */}
98+
<span
99+
className={cn(
100+
"text-[10px] px-1.5 py-0.5 rounded-full leading-none shrink-0",
101+
statusInfo.className,
102+
)}>
103+
{statusInfo.label}
104+
</span>
105+
106+
{/* Task summary (truncated) */}
107+
<span className="text-xs text-vscode-descriptionForeground truncate min-w-0" title={item.task}>
108+
{taskSummary}
109+
</span>
110+
</div>
111+
112+
{/* Render children */}
113+
{children.map((child) => (
114+
<TaskNodeRow
115+
key={child.item.id}
116+
node={child}
117+
depth={depth + 1}
118+
currentTaskId={currentTaskId}
119+
modeMap={modeMap}
120+
/>
121+
))}
122+
</div>
123+
)
124+
})
125+
126+
TaskNodeRow.displayName = "TaskNodeRow"
127+
128+
/**
129+
* The Task Coordination Dashboard component.
130+
*
131+
* Displays a collapsible tree view of the current delegation session,
132+
* showing each task's mode, status, and delegation relationships.
133+
* Only visible when the current task is part of a multi-task delegation hierarchy.
134+
*/
135+
const TaskDashboard = () => {
136+
const { taskHistory, currentTaskItem, currentTaskId, customModes } = useExtensionState()
137+
const { rootNode, hasDelegationHierarchy } = useTaskTree(taskHistory, currentTaskItem)
138+
const [isExpanded, setIsExpanded] = useState(true)
139+
140+
// Build a mode lookup map
141+
const modeMap = useMemo(() => {
142+
const allModes = getAllModes(customModes)
143+
const map = new Map<string, ModeConfig>()
144+
for (const mode of allModes) {
145+
map.set(mode.slug, mode)
146+
}
147+
return map
148+
}, [customModes])
149+
150+
const toggleExpanded = useCallback(() => {
151+
setIsExpanded((prev) => !prev)
152+
}, [])
153+
154+
// Don't render if there's no delegation hierarchy
155+
if (!hasDelegationHierarchy || !rootNode) {
156+
return null
157+
}
158+
159+
return (
160+
<div
161+
data-testid="task-dashboard"
162+
className="border-b border-vscode-panel-border bg-vscode-sideBar-background/50">
163+
{/* Header */}
164+
<button
165+
className={cn(
166+
"w-full flex items-center gap-2 px-3 py-2 text-xs font-medium",
167+
"text-vscode-descriptionForeground hover:text-vscode-foreground",
168+
"transition-colors cursor-pointer select-none",
169+
)}
170+
onClick={toggleExpanded}
171+
data-testid="task-dashboard-toggle">
172+
{isExpanded ? <ChevronDown className="size-3.5" /> : <ChevronRight className="size-3.5" />}
173+
<GitBranch className="size-3.5" />
174+
<span>Task Delegation</span>
175+
</button>
176+
177+
{/* Tree content */}
178+
{isExpanded && (
179+
<div className="pb-2" data-testid="task-dashboard-content">
180+
<TaskNodeRow node={rootNode} depth={0} currentTaskId={currentTaskId} modeMap={modeMap} />
181+
</div>
182+
)}
183+
</div>
184+
)
185+
}
186+
187+
export default memo(TaskDashboard)

0 commit comments

Comments
 (0)