- New AgentIndicator component: replaces 3 separate indicators (loading dots, ToolCallGroup summary, activity bar) with one - Positioned as last item in message list — scrolls naturally - Shows current tool action: 'Researching lxc:nfs-export…' etc - Spinner during work, check on completion, X on error - Fades out 3s after turn completes - Activity bar, loading dots, statusLabel removed from Chat - Continue button moved to sidebar session panel
4.7 KiB
2026-07-14 — Unified agent activity indicator
Status: Planned
Current state — three separate indicators
| Component | Location | Shows |
|---|---|---|
| Loading dots (Chat.svelte:146) | Inline in assistant bubble | 3 bouncing dots when no text/tools yet |
| ToolCallGroup trigger row | Inline in assistant bubble | "12 tools" with spinner |
| Activity bar | Bottom of message list | "Agent is responding…" / "Working" / Continue button |
All three overlap. The operator sees dots → then a tool count → then the activity bar — three different visual styles for the same thing: "the agent is working."
Target: single indicator appended to conversation
One row, always the last item in the message list, that replaces the loading dots, ToolCallGroup summary, and activity bar. Think of it like a system message appended at the end of the conversation.
Behavior
User: "audit fleet"
Assistant: "I'll check all hosts. Here's the plan..." ← full message bubble
┌─ Agent is auditing… ───────────────────┐
│ ◉ apt update on lxc:jellyfin │ ← spinner + current action
└────────────────────────────────────────┘
... agent finishes ...
Assistant: "Done. 19 LXCs have pending updates." ← next message bubble
The indicator:
- Appears when the agent starts working (first
tool_useevent orstreaming=true) - Updates its description with the current tool name in flight
- Collapses/disappears when the turn ends (
doneevent orstreaming=false) - If there were tools, shows a brief completion summary for 3 seconds then fades
- During auto-continuation (polling picks up new messages), reappears if the agent did tool calls
States
| State | Icon | Description |
|---|---|---|
| Thinking | ◉ pulse | "Agent is thinking…" |
| Planning | ◉ pulse | "Building plan…" |
| Researching | ◉ pulse | "Researching …" |
| Executing | ◉ spinner | "<tool_name> …" |
| Done | ✓ | Fades out after 3s |
Data source
The description comes from the most recent tool_use event's name + args. If no tools yet, show generic "thinking" message. The derived toolTimeline store already has this data.
Implementation
1. New component: AgentIndicator.svelte
Props: active: boolean, lastTool: ToolCallResult | null, toolCount: number
Renders a single compact row:
<div class="activity-indicator">
<LoaderCircle class="animate-spin" /> <!-- or CheckIcon when done -->
<span>{label}</span>
</div>
label is derived:
const label = $derived.by(() => {
if (!active) return ''
if (!lastTool) return 'Agent is thinking…'
const args = lastTool.args ?? {}
switch (lastTool.name) {
case 'set_goal': return 'Setting goal…'
case 'propose_plan': return 'Building plan…'
case 'search_knowledge': return `Researching: ${args.query ?? ''}`
case 'get_entity': return `Looking up ${args.slug_or_id ?? ''}`
case 'run': return `${args.purpose ?? 'Running command…'}`
case 'list_lxcs': return 'Listing containers…'
case 'update_plan_step': return 'Updating progress…'
case 'upsert_knowledge': return 'Recording knowledge…'
case 'complete_task': return 'Wrapping up…'
default: return `${lastTool.name}…`
}
})
2. Chat.svelte changes
- Remove activity bar from bottom of messages
- Replace the 3 bouncing dots
{#if msg.tools.length === 0}with nothing (the indicator covers this) - Add
<AgentIndicator>after the{#each}loop, beforemessagesEnd - The indicator shows when
$streaming || liveStatus === 'executing' - Pass
lastToolfrom$toolTimeline— the last tool_use entry
3. Remove activity bar code
Delete the {#if $currentSession && $messages.length > 0} block at the bottom (already moved once, now deleted entirely — replaced by AgentIndicator).
4. Remove loading dots
In Chat.svelte, remove the 3 bouncing dots block:
{:else if msg.tools.length === 0}
<div class="flex items-center gap-1.5 py-1 text-sm text-muted-foreground">
<span class="size-1.5 animate-bounce rounded-full bg-current ...">...</span>
</div>
Verification
- Send "status" → indicator appears "Agent is thinking…" → agent responds → indicator fades
- Send "check updates on jellyfin" → indicator shows "Researching…" → "Listing containers…" → "Running apt list…" → fades
- Auto-continuation fires → indicator reappears with current tool → fades when done
- Scroll up during agent work → indicator stays at bottom of message list (it's just a message)
- Error during agent work → indicator shows "Error: …" with X icon