v0.20.0: thinking blocks, chat windows overhaul, scroll fix
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

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
This commit is contained in:
2026-08-04 22:42:53 +02:00
parent 20adb89650
commit 1aaedf498a
25 changed files with 1852 additions and 1211 deletions

View File

@@ -0,0 +1,191 @@
# 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.
- `UnifiedTimeline` is imported **only** by `TaskContextPanel` (grep confirms),
so removing the Activity pane is self-contained.
- The `activityLog` **store** stays required: it feeds inline labels
(`toolActivityLabel`), live `run` output (`toolsWithLive`), and the mascot
(`mascot/stimuli.ts`). Only the timeline *view* is removed.
- Tool events already arrive separately (`tool_use` then `tool_result` in
`chat.ts`), and the activity log already carries humanized labels + per-tool
`stepSeq` attribution. So progressive per-tool lines + step grouping are a
**presentation** change, not a data/model change.
- `run` results 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:
1. **Live plan checklist** (only on the most-recent/running turn — see D3).
2. **Tool lines grouped by plan step** (D2), then orphan tools (no step).
3. **Streamed text answer** (existing `markdown-body prose-chat`), with the
blinking cursor while streaming (existing).
4. A compact **"Thinking" line** while `working` and before any output: reuses
the existing `indicatorLabel` (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)`
in `activity.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 Z`
- `list_entities`/`list_lxcs` → `N entities`; `get_relations` → `N relations`
- `search_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); `done` if empty
- Live `run` output: while streaming, the line auto-expands a pinned-tail mini
pane (reuse the `liveOutput` path from `toolsWithLive`).
- 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`/`NOMOS`
role-tags, hairline `divide-y` separators; no bubbles, no soft shadow).
Delete `.user-msg { box-shadow }`.
- Tool lines + expanded `<pre>`: border-driven, square, opaque.
- Composer: opaque `bg-background`, square (remove `rounded-2xl`/`bg-card/50`).
- Rewrite the stale "Art Nouveau" `<style>` comments → "cyberspace/terminal".
- Per `central_css_override`: drive surface styling centrally in `app.css`
where it's a primitive concern; no ad-hoc `rounded-*`/`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) into `cw`/`ch`; cap `s ∈ [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).
A `userPanned` flag 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 setting `fx`/`fy`. Dot-grid stays in screen space.
- Keep: open-on-click, `touched` pulse, health-diff label, selection ring.
Respect `scrollIntoView` pitfall (transform, not scroll).
## Phased task list (each independently shippable; all frontend)
- **P1 — Inline progressive stream.** `TurnTrace.svelte` + `ToolLine.svelte`;
wire into `ChatThread` per turn; "Thinking" line; tool→step grouping via
activity-log `stepSeq` matched by tool id; keep `toolsWithLive` for `run`.
- **P2 — Live plan checklist.** Inline current-gen `planSteps` on the running
turn; collapse-to-summary at terminal state.
- **P3 — Rail → graph only.** Strip Activity pane + `UnifiedTimeline` from
`TaskContextPanel`; 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`, never `scrollIntoView`).
## Plumbing specifics (grounded, no backend)
- New `toolResultSummary(t: ToolCallResult): string` in `activity.ts`, beside
`toolActivityLabel`. Per-name switch (D2 list), graceful fallback.
- Tool→step grouping: build `id → stepSeq` from the activity log once per turn;
tools with no step render as orphans.
- Reuse: `planSteps` (generation-aware), `indicatorLabel`, `toolsWithLive`,
`toolActivityLabel`, `liveOutput` streaming 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.scrollTo` for 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** (1527 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 in
`ui/*`, `oidc.ts`, `windows.ts`, `workspace.ts`), `vite build`, `vitest`
(add a `toolResultSummary` unit 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) `run` tool → 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** — `truncateFrom` store method + `POST /sessions/{id}/edit`
(extract `streamTurn` from `handleChat`); reuse `reopenSession` (already
exists, store.go:838 — marks prior `session_plan_steps` `replaced`, 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 inserting `type:name` slugs the agent/graph already parse.
- **Attachments** — multipart upload + `agent_attachments` table + configured
`OIKOS_ATTACHMENTS_DIR` (explicit volume, not relative) + capped text inlining.
- **Continue button** — needs `/resume` to `reopenSession` first for terminal
sessions (today `/resume` does not reopen `done`/`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_attachment` MCP tool (lazy full-content fetch, lower context than inlining).
- Oldest-first timeline toggle / per-tool `tool.*` events for background turns.