diff --git a/VERSION b/VERSION index d15723f..1c09c74 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.2 +0.3.3 diff --git a/plans/2026-07-14-unified-agent-indicator.md b/plans/2026-07-14-unified-agent-indicator.md new file mode 100644 index 0000000..c4d4f1d --- /dev/null +++ b/plans/2026-07-14-unified-agent-indicator.md @@ -0,0 +1,118 @@ +# 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_use` event or `streaming=true`) +- **Updates** its description with the current tool name in flight +- **Collapses/disappears** when the turn ends (`done` event or `streaming=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 | " …" | +| 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: +```html +
+ + {label} +
+``` + +`label` is derived: +```ts +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** `` after the `{#each}` loop, before `messagesEnd` +- The indicator shows when `$streaming || liveStatus === 'executing'` +- Pass `lastTool` from `$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: +```svelte +{:else if msg.tools.length === 0} +
+ ... +
+``` + +## 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 diff --git a/plans/index.md b/plans/index.md index a3ab9b0..108745a 100644 --- a/plans/index.md +++ b/plans/index.md @@ -14,7 +14,9 @@ went sideways, open an investigation. | 2026-07-08 | [Liveness, drift, and UX cohesion](2026-07-08-liveness-drift-and-ux-cohesion.md) | In Progress — Phase 5 deferred | | 2026-07-10 | [General gated execution: unlimited actions, gated by risk](2026-07-10-general-gated-execution.md) | In Progress — enum retirement + auto-act revival still open | | 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred | -| 2026-07-14 | [Session reliability & UX audit](2026-07-14-session-reliability-and-ux-audit.md) | Planned — just audited, not started | +| 2026-07-14 | [Session reliability & UX audit](2026-07-14-session-reliability-and-ux-audit.md) | Done — all 21 fixes deployed | +| 2026-07-14 | [Tool timeline in sidebar](2026-07-14-tool-timeline-sidebar.md) | Done — deployed v0.3.2 | +| 2026-07-14 | [Unified agent activity indicator](2026-07-14-unified-agent-indicator.md) | Done — deployed v0.3.3 | ## Done diff --git a/web/src/lib/components/AgentIndicator.svelte b/web/src/lib/components/AgentIndicator.svelte new file mode 100644 index 0000000..f4a8f69 --- /dev/null +++ b/web/src/lib/components/AgentIndicator.svelte @@ -0,0 +1,68 @@ + + +{#if active || done || error} +
+ + {#if error} + + {:else if done} + + {:else} + + {/if} + + {label} +
+{/if} diff --git a/web/src/pages/Chat.svelte b/web/src/pages/Chat.svelte index 96cfba6..84c8394 100644 --- a/web/src/pages/Chat.svelte +++ b/web/src/pages/Chat.svelte @@ -1,18 +1,17 @@ @@ -173,12 +168,6 @@ {@html render(msg.text)} - {:else if msg.tools.length === 0} -
- - - -
{/if} {#if msg.pendingApprovals.length > 0} @@ -187,27 +176,11 @@ {/if} {/each} - {#if $currentSession && $messages.length > 0} -
- {#if $streaming} - - Agent is responding… - {:else if liveStatus === 'executing'} - - Working — {statusLabel(liveStatus)} - - {:else if liveStatus === 'awaiting_input'} - Waiting for your answer - {:else if liveStatus} - Status: {statusLabel(liveStatus)} - {/if} - {#if $currentTask?.goal} - · {$currentTask.goal.slice(0, 60)}{$currentTask.goal.length > 60 ? '…' : ''} - {/if} -
- {/if} + t.type === 'tool_use').at(-1) ?? null} + error={$error} + />