From 8caa2d2eb447e9e989de13f63938d38c712e3db1 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Mon, 23 Mar 2026 23:37:32 +0100 Subject: [PATCH] refactor: group agents and tools into distinct sections in ActivityPanel Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/renderer/components/ActivityPanel.tsx | 281 +++++++++++++--------- 1 file changed, 172 insertions(+), 109 deletions(-) diff --git a/src/renderer/components/ActivityPanel.tsx b/src/renderer/components/ActivityPanel.tsx index eafc39b..0e4b871 100644 --- a/src/renderer/components/ActivityPanel.tsx +++ b/src/renderer/components/ActivityPanel.tsx @@ -1,15 +1,16 @@ import { useMemo, type ReactNode } from 'react'; -import { Activity, Bot, Server, Code, Sparkles } from 'lucide-react'; +import { Activity, Server, Code, Sparkles, Users } from 'lucide-react'; import { buildAgentActivityRows, formatAgentActivityLabel, isAgentActivityActive, isAgentActivityCompleted, + type AgentActivityRow, type SessionActivityState, } from '@renderer/lib/sessionActivity'; import { inferProvider } from '@shared/domain/models'; -import type { PatternDefinition } from '@shared/domain/pattern'; +import type { OrchestrationMode, PatternAgentDefinition, PatternDefinition } from '@shared/domain/pattern'; import { resolveSessionToolingSelection, type SessionRecord, @@ -21,6 +22,19 @@ import type { } from '@shared/domain/tooling'; import { ProviderIcon } from './ProviderIcons'; +/* ── Mode accent colours ───────────────────────────────────── */ + +const modeAccent: Record = { + single: { dot: 'bg-indigo-400', bar: 'bg-indigo-500/60', label: 'text-indigo-400' }, + sequential: { dot: 'bg-amber-400', bar: 'bg-amber-500/60', label: 'text-amber-400' }, + concurrent: { dot: 'bg-emerald-400', bar: 'bg-emerald-500/60', label: 'text-emerald-400' }, + handoff: { dot: 'bg-sky-400', bar: 'bg-sky-500/60', label: 'text-sky-400' }, + 'group-chat': { dot: 'bg-violet-400', bar: 'bg-violet-500/60', label: 'text-violet-400' }, + magentic: { dot: 'bg-zinc-500', bar: 'bg-zinc-600/60', label: 'text-zinc-500' }, +}; + +/* ── Helpers ───────────────────────────────────────────────── */ + function formatModel(model: string): string { return model.replace(/-/g, '\u2011'); } @@ -28,14 +42,118 @@ function formatModel(model: string): string { function formatEffort(effort: string | undefined): string | undefined { if (!effort) return undefined; const labels: Record = { - low: 'Low effort', - medium: 'Medium effort', - high: 'High effort', - xhigh: 'Max effort', + low: 'Low', + medium: 'Medium', + high: 'High', + xhigh: 'Max', }; return labels[effort] ?? effort; } +const modeLabels: Record = { + single: 'Single agent', + sequential: 'Sequential', + concurrent: 'Concurrent', + handoff: 'Handoff', + 'group-chat': 'Group chat', + magentic: 'Magentic', +}; + +/* ── Section header ────────────────────────────────────────── */ + +function SectionHeader({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +/* ── Agent row ─────────────────────────────────────────────── */ + +function AgentRow({ + row, + agent, + accent, + isLast, +}: { + row: AgentActivityRow; + agent?: PatternAgentDefinition; + accent: (typeof modeAccent)[OrchestrationMode]; + isLast: boolean; +}) { + const isActive = isAgentActivityActive(row.activity); + const isCompleted = isAgentActivityCompleted(row.activity); + + return ( +
+ {/* Left accent bar — visible only when this agent is actively working */} + {isActive && ( +
+ )} + + {/* Status dot */} +
+ +
+ + {/* Content */} +
+
+ {row.agentName} +
+ + {/* Model + effort inline */} + {agent && ( +
+ + {(() => { + const prov = inferProvider(agent.model); + return prov ? : null; + })()} + {formatModel(agent.model)} + + {agent.reasoningEffort && ( + <> + · + + + {formatEffort(agent.reasoningEffort)} + + + )} +
+ )} + + {/* Activity label */} +
+ + {formatAgentActivityLabel(row.activity)} + +
+
+
+ ); +} + +/* ── ActivityPanel ─────────────────────────────────────────── */ + interface ActivityPanelProps { activity?: SessionActivityState; lspProfiles: LspProfileDefinition[]; @@ -63,6 +181,8 @@ export function ActivityPanel({ const isBusy = session.status === 'running'; const toolsDisabled = isBusy || projectIsScratchpad; + const accent = modeAccent[pattern.mode] ?? modeAccent.single; + const hasTools = mcpServers.length > 0 || lspProfiles.length > 0; return (
@@ -77,31 +197,60 @@ export function ActivityPanel({
- {/* Agent cards */}
-
-
-
-

- Session tools -

- {toolsDisabled && ( - - {projectIsScratchpad ? 'Scratchpad' : 'Running'} - - )} -
+ {/* ── Agents section ───────────────────────────────── */} +
+ + + Agents + + {activityRows.length} + + + {modeLabels[pattern.mode]} + + + {activityRows.length > 0 ? ( +
+ {activityRows.map((row, index) => ( + + ))} +
+ ) : ( +

No agents configured

+ )} +
+ + {/* ── Tools section ────────────────────────────────── */} +
+ + + Tools + {toolsDisabled && ( + + {projectIsScratchpad ? 'Scratchpad' : 'Running'} + + )} + + +
{projectIsScratchpad ? ( -

+

Start a project-backed session to use MCPs or LSPs.

- ) : mcpServers.length === 0 && lspProfiles.length === 0 ? ( -

+ ) : !hasTools ? ( +

Add MCP servers or LSP profiles in Settings to enable them here.

) : ( -
+
{mcpServers.map((server) => ( )}
- - {activityRows.map((row, index) => { - const agent = pattern.agents[index]; - const isActive = isAgentActivityActive(row.activity); - const isCompleted = isAgentActivityCompleted(row.activity); - - return ( -
- {/* Agent name + status dot */} -
- - - {row.agentName} - -
- - {/* Model + reasoning effort badges */} - {agent && ( -
- - {(() => { - const prov = inferProvider(agent.model); - if (prov) { - return ; - } - return null; - })()} - {formatModel(agent.model)} - - {agent.reasoningEffort && ( - - - {formatEffort(agent.reasoningEffort)} - - )} -
- )} - - {/* Description */} - {agent?.description && ( -

- {agent.description} -

- )} - - {/* Activity status */} -
- - - {formatAgentActivityLabel(row.activity)} - -
-
- ); - })}
- - {activityRows.length === 0 && ( -

- No agents configured -

- )}
);