Live run progress: watch the agent work in real time

Clicking Run on a task now keeps the drawer open and streams the agent's
progress instead of just closing. A new RunProgress panel polls the run
(GET /api/assembler/runs/{id}) and shows a live timeline — Queued -> Thinking
-> Delivered — with elapsed time, any MCP tool calls, the action produced and
its risk tag, and an expandable output. When the run settles it refreshes the
board and pulses the review badge. The panel resets when the drawer switches
tasks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-17 00:42:36 +03:30
parent 861efa4e20
commit 1e33d57b4e
2 changed files with 224 additions and 8 deletions
+28 -8
View File
@@ -12,6 +12,7 @@ import { Bot, Play, Plus, Trash2 } from 'lucide-react'
import { toast } from 'sonner'
import { AppShell, REVIEWS_CHANGED } from '@/components/AppShell'
import { LivePreview } from '@/components/LivePreview'
import { RunProgress } from '@/components/RunProgress'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
@@ -407,8 +408,15 @@ function TaskDrawer({
const [busy, setBusy] = useState(false)
const [seatId, setSeatId] = useState<string>('')
const [preview, setPreview] = useState(false)
const [runId, setRunId] = useState<string | null>(null)
const aiSeats = seats.filter((s) => s.state === 'Ai')
// Switching to a different task clears the live run panel so it never shows a stale run.
const taskId = task?.id
useEffect(() => {
setRunId(null)
}, [taskId])
if (!task) {
return null
}
@@ -513,26 +521,38 @@ function TaskDrawer({
</Select>
<Button
disabled={busy || !seatId}
onClick={() =>
onClick={() => {
let started: string | null = null
act(async () => {
await api.post('/api/assembler/runs', { seatId, workItemId: task.id })
const run = await api.post<{ id: string }>('/api/assembler/runs', { seatId, workItemId: task.id })
started = run?.id ?? null
// Visible feedback: the task leaves Backlog for In Progress while the agent works.
if (task.status === 'Backlog') {
await api.patch(`/api/orgboard/tasks/${task.id}/move`, { status: 'InProgress' })
}
}, 'Sent to the agent — moved to In Progress; its result will land in the review inbox.').then(() => {
// The proposal lands a few seconds later; nudge the review badge, then close.
}, 'Agent started — watch it work below.').then(() => {
if (started) setRunId(started)
window.dispatchEvent(new Event(REVIEWS_CHANGED))
setTimeout(() => window.dispatchEvent(new Event(REVIEWS_CHANGED)), 4000)
setTimeout(() => window.dispatchEvent(new Event(REVIEWS_CHANGED)), 9000)
onClose()
})
}
}}
>
<Bot data-icon="inline-start" />
Run
</Button>
</div>
{/* Live process: watch the agent move through Queued → Thinking → Delivered in real time. */}
{runId && (
<RunProgress
key={runId}
runId={runId}
onSettled={() => {
// Result is in the review inbox and the board has moved — refresh both.
window.dispatchEvent(new Event(REVIEWS_CHANGED))
onChanged()
}}
/>
)}
</div>
)}