Backend: - Add isThinking flag to agentEvent for text before tool calls - Separate thinking from response text in runChatTurn and continue.go - Persist thinking in a dedicated field in message content Frontend: - Add thinking field to MessageContent, ChatMessage, ChatTextEvent types - Create ThinkingBlock.svelte — collapsible block with brain icon - SSE handler moves text_delta content to thinking on isThinking flag - Render thinking block between tools and response in ChatThread - Fix chat window scroll reset on focus change (stable windowKeys order) - Remove redundant #key id wrapper in WindowLayer - Enlarge sidebar rail (24→32 default, 40→60 max) - Remove glyph from sidebar, square graph at top - Replace AgentTrace/ToolCallCard/UnifiedTimeline with TurnTrace/ToolLine
11 KiB
2026-08-04 — Chat interaction overhaul: inline progressive stream (Claude Code style)
Status: Planned — not started. (Refocused from the earlier feature-heavy draft; backend features deferred — see "Deferred".)
Goal
Streamline agent interactions — thinking, plan, tool usage, responses — into one linear progressive inline stream per turn (the Claude Code / Cline / Roo pattern), instead of the current split where the transcript shows a collapsed trace and the real live activity lives in a separate rail timeline. The right rail becomes graph-only (and auto-zooms to fit all entities).
Locked decisions (operator interview)
| Decision | Choice |
|---|---|
| Live activity layout | Inline stream (Claude Code) — one progressive column per turn; rail keeps ONLY the Scope graph; Activity timeline tab removed |
| Tool-call detail | Per-tool progressive lines — each tool its own compact live line (spinner → one-line result summary), expandable to raw |
| Feature phases | Defer — edit/resubmit, @mentions, attachments are later phases; this plan is interaction-focused + graph auto-zoom |
Diagnosis (grounded in current code)
- The transcript (
ChatThread→AgentTrace) collapses a whole turn's tool calls into one line ("Proposed plan" / "N tool calls"), raw-JSON detail on expand. Not progressive; you can't see what's happening without expanding. - The actual live plan + tool timeline lives in the right rail
(
TaskContextPanel→UnifiedTimeline): newest-first backbone + tool stubs. So "what is the agent doing" is in a second place — a cognitive split. UnifiedTimelineis imported only byTaskContextPanel(grep confirms), so removing the Activity pane is self-contained.- The
activityLogstore stays required: it feeds inline labels (toolActivityLabel), liverunoutput (toolsWithLive), and the mascot (mascot/stimuli.ts). Only the timeline view is removed. - Tool events already arrive separately (
tool_usethentool_resultinchat.ts), and the activity log already carries humanized labels + per-toolstepSeqattribution. So progressive per-tool lines + step grouping are a presentation change, not a data/model change. runresults are free-form text (e.g."run on lxc:caddy: ERROR exit status 1") → one-line result summaries are best-effort text parsing, no backend.
Design
D1 — One progressive inline stream per turn
Replace AgentTrace (one collapsed blob per turn) with a new
TurnTrace.svelte rendered inline for each assistant turn, top-to-bottom:
- Live plan checklist (only on the most-recent/running turn — see D3).
- Tool lines grouped by plan step (D2), then orphan tools (no step).
- Streamed text answer (existing
markdown-body prose-chat), with the blinking cursor while streaming (existing). - A compact "Thinking" line while
workingand before any output: reuses the existingindicatorLabel(running step → tool → "Agent is thinking…"). Fades once text/tools arrive; reappears between steps.
D2 — Per-tool progressive lines (the Claude-Code signature)
One ToolLine.svelte per tool call (replaces ToolCallCard's row style):
- Left: state icon — spinner while
tool_use-only, ✓ on result, ✗ on error. - Label: existing
toolActivityLabel(tool)(humanized action). - One-line result summary on completion — new
toolResultSummary(tool)inactivity.ts(see plumbing). E.g.:run→exit 0 · <first line>(parse "exit status N" / "ERROR")get_entity→host:hubris (healthy);get_health_summary→healthy X · degraded Y · down Zlist_entities/list_lxcs→N entities;get_relations→N relationssearch_knowledge→N results;upsert_knowledge→recorded document:…update_plan_step→step <seq> → <status>;propose_plan→N steps- default → first non-empty line of stringified result (≤80ch);
doneif empty
- Live
runoutput: while streaming, the line auto-expands a pinned-tail mini pane (reuse theliveOutputpath fromtoolsWithLive). - Click → expand raw args/result (border-driven
<pre>, cyberspace-square). - Border-driven, no rounded/shadow (per
border_driven_language).
D3 — Live plan checklist (TodoWrite-style)
On the running/last turn, render the current-generation planSteps
(already generation-aware via workspace.ts) as a checklist: pending = hollow,
running = spinner + highlight, done = ✓, failed = ✗, blocked = pause. Steps
check off live as plan.step.* events land. This is the unified timeline's
plan view, moved inline and scoped to the active turn. Past turns render only
their tool lines + text (the plan is session-level; the running turn carries
its current state, mirroring how TodoWrite re-displays state each turn). On a
terminal task state (done/failed), the checklist collapses to one line:
Plan complete — N steps / Plan failed — step K.
D4 — Rail → graph only
TaskContextPanel: remove the Activity pane and the UnifiedTimeline import;
the panel becomes the Scope graph full-height (keep the collapsible "Scope"
header + the nowTouching strip). The graph is now the rail's entire job, so
auto-fit (D6) matters more. activityLog* stores remain imported only where
the inline stream/mascot need them.
D5 — Cyberspace cohesion of the stream
Apply alongside the rewrite so the new inline view is on-system from day one:
- Transcript → terminal log rows (square, full-width,
YOU/NOMOSrole-tags, hairlinedivide-yseparators; no bubbles, no soft shadow). Delete.user-msg { box-shadow }. - Tool lines + expanded
<pre>: border-driven, square, opaque. - Composer: opaque
bg-background, square (removerounded-2xl/bg-card/50). - Rewrite the stale "Art Nouveau"
<style>comments → "cyberspace/terminal". - Per
central_css_override: drive surface styling centrally inapp.csswhere it's a primitive concern; no ad-hocrounded-*/shadow-*/backdrop-blur.
D6 — Graph auto-fit + drag-pan (SessionGraph.svelte) (carried over)
- Wrap nodes+links in
<g transform="translate(tx,ty) scale(s)">; fit the bbox of all nodes (radius + label + padding) intocw/ch; caps ∈ [0.2, 2.5]. - Re-fit on: mount, node-set change, container resize, sim-settle
(
alpha > 0.05), background double-click. Not every tick (fights pan). AuserPannedflag pauses auto-follow after a manual pan until next membership/resize/double-click. - Background drag = pan (
tx/ty); node drag converts screen→graph via the inverse transform before settingfx/fy. Dot-grid stays in screen space. - Keep: open-on-click,
touchedpulse, health-diff label, selection ring. RespectscrollIntoViewpitfall (transform, not scroll).
Phased task list (each independently shippable; all frontend)
- P1 — Inline progressive stream.
TurnTrace.svelte+ToolLine.svelte; wire intoChatThreadper turn; "Thinking" line; tool→step grouping via activity-logstepSeqmatched by tool id; keeptoolsWithLiveforrun. - P2 — Live plan checklist. Inline current-gen
planStepson the running turn; collapse-to-summary at terminal state. - P3 — Rail → graph only. Strip Activity pane +
UnifiedTimelinefromTaskContextPanel; verify no other importers (grep: only TaskContextPanel). - P4 — Cyberspace cohesion. Terminal log rows; remove rounded/shadow/
translucency; square composer; centralize in
app.css; fix stale comments. - P5 — Graph auto-fit + drag-pan. D6.
- Polish (small, frontend-only): per-message/tool copy; scroll-to-
bottom button (uses
container.scrollTo, neverscrollIntoView).
Plumbing specifics (grounded, no backend)
- New
toolResultSummary(t: ToolCallResult): stringinactivity.ts, besidetoolActivityLabel. Per-name switch (D2 list), graceful fallback. - Tool→step grouping: build
id → stepSeqfrom the activity log once per turn; tools with no step render as orphans. - Reuse:
planSteps(generation-aware),indicatorLabel,toolsWithLive,toolActivityLabel,liveOutputstreaming path.
Constraints honored (saved decisions)
design_system.central_css_override,border_driven_language: square, hairline, opaque, focus-by-color, no soft shadows/glows.chat_thread.pane_layout: dynamic status (Thinking line, live checklist) lives in the message Pane, never the input Pane.wmkit.scrollintoview_reflow_pitfall:container.scrollTofor scroll-to- bottom; transform (not scroll) for graph pan.
Risks
- Removing the rail timeline loses the "overview" view. Mitigation: the inline checklist + per-turn tool lines carry the same info progressively; the graph still shows fleet scope. If operators miss the overview, a collapsed "full timeline" can return as a toggle (follow-up).
- Auto-fit vs manual pan — handled by
userPanned+ settle-alpha gate. - Inline stream length on long turns (15–27 min, many tools) — progressive lines can get long; mitigate by auto-collapsing finished steps (keep the running step + its tools expanded, prior steps as one-line summaries).
- Best-effort result summaries may misformat unusual payloads — fallback is always a truncated raw line + expandable raw detail, never a blank.
Validation
npm run lint,tsc --noEmit(no NEW errors beyond the known baseline inui/*,oidc.ts,windows.ts,workspace.ts),vite build,vitest(add atoolResultSummaryunit test per tool name + fallback).- Manual matrix: (a) start a long task → Thinking line → plan checklist
appears and checks off live → each tool streams as its own line with a
one-line summary → text streams; (b) reload mid-turn → working still shows;
(c)
runtool → live output pins to tail then collapses to summary; (d) graph auto-fits at settle + on new entity + drag-pan + double-click reset; (e) no rounded/soft-shadow remains on chat surfaces; (f) rail shows graph only.
Deferred (later phases, after this lands + validates)
- Edit-and-resubmit —
truncateFromstore method +POST /sessions/{id}/edit(extractstreamTurnfromhandleChat); reusereopenSession(already exists, store.go:838 — marks priorsession_plan_stepsreplaced, clears outcome) for the reset. Reject edit while the gate is busy (HTTP 409); edit cannot queue (truncation must be atomic). Regenerate = no-op-edit case. - @entity mentions — small
GET /api/v1/entities/search?q=+ composer autocomplete insertingtype:nameslugs the agent/graph already parse. - Attachments — multipart upload +
agent_attachmentstable + configuredOIKOS_ATTACHMENTS_DIR(explicit volume, not relative) + capped text inlining. - Continue button — needs
/resumetoreopenSessionfirst for terminal sessions (today/resumedoes not reopendone/failed;handleChat's follow-up path does). Small backend tweak. - Image vision pending provider confirmation.
Out of scope / follow-ups
- A collapsible "full timeline" overview toggle if the rail removal is missed.
read_attachmentMCP tool (lazy full-content fetch, lower context than inlining).- Oldest-first timeline toggle / per-tool
tool.*events for background turns.