Replaces the chat right rail's ad-hoc Digest+Graph stack with a single
TaskContextPanel that renders the task's live working state, driven by the
always-on events stream (not the per-turn chat SSE) so it keeps updating
during server-side auto-continuation/resume:
- GoalHeader: goal + status pill (planning/executing/awaiting_input/done/
failed), sourced from the sessions list.
- PlanProgress: ordered steps with live status icons + progress bar, hydrated
via new GET /sessions/{id}/plan; clicking a step with a target opens its
EntitySheet (no fake "jump to transcript" — bits-ui Collapsible content
isn't force-mounted, so a DOM-scroll jump would silently no-op for
collapsed tool groups).
- OperatorQuestion: the pinned structured question card (prompt/why/entity
chips/option buttons/free-text), hydrated via new GET /sessions/{id}/
questions; answering POSTs to the existing answer endpoint.
- SessionGraph upgraded to a live entity panel: entity.touched pulses the
node (animated ring) and shows "Now touching <slug>"; health.changed shows
a transient diff badge for touched entities.
- SessionDigest gains a success/failure/partial outcome banner and now also
refetches when the task's status changes, not just on session switch.
Two bugs found and fixed while wiring this up:
- workspace.ts's status-refresh trigger only covered goal.set/task.status;
question.raised/answered didn't refresh the sessions list, so GoalHeader's
pill went stale after answering via the panel (resumeSession runs entirely
server-side — no client 'done' event to piggyback a refresh on). Now every
status-affecting event triggers the (debounced) refetch.
- Forgot to rebuild the nomos container after adding the /plan and
/questions endpoints, so they silently fell through to the old default GET
handler — caught via a live curl diff against the running container,
not a code read.
Verified end-to-end against the live stack: goal/plan/question all update
without a reload as the agent works; answering a question via the panel
resumes the agent and the header pill correctly flips to Executing;
entity.touched pulses the live graph.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
39 lines
1.7 KiB
Svelte
39 lines
1.7 KiB
Svelte
<script lang="ts">
|
|
import { currentTask } from '$lib/stores/workspace'
|
|
import { Badge } from '$lib/components/ui/badge'
|
|
|
|
type StatusStyle = { label: string; dot: string; pulse: boolean; variant: 'default' | 'secondary' | 'destructive' | 'outline' }
|
|
|
|
function statusStyle(status: string | undefined, outcome: string | undefined): StatusStyle {
|
|
switch (status) {
|
|
case 'awaiting_input':
|
|
return { label: 'Needs your input', dot: 'bg-warning', pulse: true, variant: 'secondary' }
|
|
case 'done':
|
|
return outcome === 'partial'
|
|
? { label: 'Done · partial', dot: 'bg-warning', pulse: false, variant: 'secondary' }
|
|
: { label: 'Done', dot: 'bg-success', pulse: false, variant: 'default' }
|
|
case 'failed':
|
|
return { label: 'Failed', dot: 'bg-destructive', pulse: false, variant: 'destructive' }
|
|
case 'planning':
|
|
return { label: 'Planning', dot: 'bg-primary', pulse: true, variant: 'secondary' }
|
|
case 'executing':
|
|
return { label: 'Executing', dot: 'bg-primary', pulse: true, variant: 'secondary' }
|
|
default:
|
|
return { label: 'Active', dot: 'bg-muted-foreground', pulse: false, variant: 'outline' }
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{#if $currentTask}
|
|
{@const st = statusStyle($currentTask.status, $currentTask.outcome)}
|
|
<div class="flex shrink-0 flex-col gap-1.5 border-b px-3 py-2.5">
|
|
<div class="flex items-center gap-1.5">
|
|
<span class="size-2 rounded-full {st.dot} {st.pulse ? 'animate-pulse' : ''}"></span>
|
|
<Badge variant={st.variant} class="text-[10px]">{st.label}</Badge>
|
|
</div>
|
|
<p class="text-sm font-medium leading-snug">
|
|
{$currentTask.goal || $currentTask.title || 'Untitled task'}
|
|
</p>
|
|
</div>
|
|
{/if}
|