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
192 lines
11 KiB
Markdown
192 lines
11 KiB
Markdown
# 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** (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 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.
|