Files
oikos/plans/done/2026-08-04-chat-window-overhaul.md
dtoro 1aaedf498a
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
v0.20.0: thinking blocks, chat windows overhaul, scroll fix
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
2026-08-04 22:42:53 +02:00

192 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.