Skip to content

feat(chat): omp threads show thinking, live subagents, diffs, and real tool output - #5

Merged
leonardoxr merged 3 commits into
main-xavierfrom
feat/omp-rich-chat
Aug 19, 2026
Merged

feat(chat): omp threads show thinking, live subagents, diffs, and real tool output#5
leonardoxr merged 3 commits into
main-xavierfrom
feat/omp-rich-chat

Conversation

@leonardoxr

@leonardoxr leonardoxr commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Problem

The omp provider rendered a bare chat timeline compared to the omp CLI: thinking was discarded at ingestion, tool rows flattened to generic "Ran command"/"Tool" labels with one-line 84-char output summaries, omp's inline per-file diffs were dropped by the wire projection, lsp/debug/browser/eval calls all rendered as anonymous hammer rows, and subagents never reached the spawn CTA or Agents panel (omp's subagent frames are RPC-transport-only; ACP carries none).

Fix

All the missing data already crosses the ACP wire — it was being dropped at the parser, the adapter, and the wire-slimming projection. The UI is payload-driven, so most of the work is server-side and applies retroactively to persisted threads.

Server

  • OmpAdapter buffers agent_thought_chunk segments and settles them as reasoning items → reasoning.completed activities.
  • ActivityPayloadProjection keeps a capped multi-line rawOutput.fullText, the ACP {type:"diff"} content blocks, and a bounded toolInfo (xd:// device name, action, scalar args, eval code) on tool.completed rows; tool.updated stays slim.
  • omp task tool calls synthesize the full subagent lifecycle from the TaskToolDetails streamed in rawOutput: task.started per subtask, fingerprint-deduped task.progress (intent, current tool, tokens, duration, model), and task.completed driven by per-subtask progress status. The async-job mode acks the call as completed while agents still run and the ACP runtime evicts completed calls from its merge map, so the roster is cached per toolCallId; agents outliving the turn are marked backgrounded.
  • AcpSessionRuntime gains an opt-in emission predicate so task progress riding in rawOutput survives the title/detail update-suppression gate (other ACP providers unchanged).
  • Informative provider titles (omp per-call intents) win row headings; fixed a pre-existing bug where update frames overwrote titles with the bare "Tool" fallback (also affected Grok/Cursor).

Web

  • Thinking rows render italic/dimmed, expand to markdown, and are exempt from the neutral-row filter.
  • Expanded tool rows show full output, lazy per-file FileDiff blocks (via parseDiffFromFile, no new deps), structured args, syntax-highlighted eval code, and markdown LSP results; icons key on tool identity.

Verification

  • 250 focused tests green (shared toolActivity, projection, ingestion, OmpAdapter incl. new pure parsers, ACP runtime suite, web session-logic); typecheck clean on server/web/shared.
  • Verified live against a real omp acp session end-to-end: thinking rows, intent titles, inline diff cards, spawn CTA transitioning "2 working → ✓ completed · Σ 33.4k", and the Agents panel roster with per-agent status/intent/model/token/tool telemetry.
  • Mobile confirmed crash-safe: unknown reasoning.completed rows fall through to the generic info row (mobile parity is follow-up work).

Built with Claude Fable 5 on Oh My Pi.

Screenshots

All captured live against a real omp acp session (worktree dev server, scratch project). No "before" shots — before this change these turns rendered as a text bubble plus generic collapsed "Tool" rows.

Intent titles mid-run — the tool row heading is omp's per-call intent ("Adding farewell function"), not "Changed files":

Live tool row with intent title

Subagent spawn CTA, live — synthesized task.* lifecycle drives the existing CTA: "Kicked off 2 subagents · 2 working", plus the background-agents banner:

Spawn CTA with two working subagents

Agents panel, live roster — scout badges, elapsed timers, working state, fed entirely from the ACP-streamed TaskToolDetails:

Agents panel with working omp subagents

Settled CTA with usage rollup — "Ran 2 subagents · ✓ completed · Σ 33.4k":

Settled spawn CTA with token rollup

Agents panel, settled — per-agent intent ("Reading greet.js"), model (claude-fable-5:low), token and tool counts, durations; italic thinking row and intent-titled hub row in the timeline:

Settled agents panel with telemetry

Thinking + tool rows in the expanded fold — italic thought rows interleaved with read/command/edit rows and the changed-files card:

Expanded turn fold with thinking and tool rows

Assets live on the orphan branch assets/omp-rich-chat to keep them out of the feature history.

…l tool output

The omp provider rendered a bare timeline: thinking was discarded, tool rows
flattened to generic labels with one-line 84-char output summaries, inline
diffs were dropped on the wire, and subagents never reached the Agents panel
because omp's subagent frames are RPC-mode-only.

Server:
- OmpAdapter buffers agent_thought_chunk segments and settles them as
  reasoning items; ingestion appends them as reasoning.completed activities.
- The wire projection keeps a capped multi-line rawOutput.fullText and the
  ACP diff content blocks on tool.completed rows (tool.updated stays slim),
  plus a bounded toolInfo (device/tool name, action, scalar args, eval code)
  derived from rawInput — retroactive for already-persisted threads.
- omp's task tool calls synthesize the full subagent lifecycle from the
  TaskToolDetails streamed in rawOutput: task.started per subtask, deduped
  task.progress with intent/tool/usage/model, and task.completed driven by
  per-subtask progress status — the async-job mode acks the call as
  completed while agents still run, and the runtime evicts completed calls
  from its merge map, so the roster is cached per toolCallId and agents
  outliving the turn are marked backgrounded instead of spinning forever.
- AcpSessionRuntime gains an opt-in emission predicate so omp task progress
  in rawOutput survives the title/detail update-suppression gate.
- Informative provider titles (omp per-call intents) win the row heading
  over flattened action labels, and update frames no longer overwrite them
  with the bare "Tool" fallback.

Web:
- Thinking rows render italic/dimmed and expand to markdown.
- Expanded tool rows show the full output, per-file FileDiff blocks built
  from the preserved diff contents, structured tool args, syntax-highlighted
  eval code, and markdown LSP results; icons key on the tool identity.

Verified live against a real omp session: thinking, intent titles, inline
diffs, and the Agents panel roster with per-agent status, intent, model,
token and tool counts.

Built with Claude Fable 5 on Oh My Pi.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Aug 19, 2026
@leonardoxr
leonardoxr merged commit 6bc3bfa into main-xavier Aug 19, 2026
5 checks passed
leonardoxr added a commit that referenced this pull request Aug 19, 2026
…every thread

Rich omp chat rows (#5) put a 4000-char output excerpt and inline diffs on
every `tool.completed` wire payload. Those rows are permanent, so a resumed
thread carried the output of every tool it had ever run: the transfer budget
measured a 28.3 KiB thread snapshot against a 7.5 KiB budget and 40.8 KiB of
total thread wire against 15.5 KiB, for every provider, not just omp. It also
made the wire projection *add* fields, which broke the invariant that a
projected payload derives exactly what the raw one does.

Output and diffs now travel on request. `orchestration.getActivityOutput`
takes the threadId plus the activity id clients already hold and reads the
persisted projection row — full payloads were always kept there, only the
wire was slimmed — through a primary-key point read on the read-model
facade. The body is capped at 200k chars with a `truncated` flag, since it
is fetched for the one row a user expanded rather than broadcast. A missing
row (a revert can prune the turn still on screen) reads as an empty body,
which renders as the summary the row already shows.

Web rows fetch on expand through a per-activity cached query, so scrolling a
virtualized row out and back does not refetch, and the one-line detail stays
as the body until the output arrives. ACP diff paths now feed the wire's
`files` list, so a collapsed file-change row still names what it touched
without a fetch. Mobile never rendered inline output and stays summary-only.

Wire cost, codex column of the budget report, before → after:
- thread snapshot HTTP wire: 28.3 KiB → 6.4 KiB (budget 7.3)
- measured turn WS wire: 12.2 KiB → 6.5 KiB (budget 7.8)
- measured turn WS decoded: 76.0 KiB → 51.9 KiB (budget 66.4)
- total thread wire: 39.8 KiB → 12.9 KiB (budget 15.1)

Verified: apps/server 2722 passed (3 were failing), apps/web 2560, apps/mobile
705, typecheck clean.

Built with Claude (opus-5) on Oh My Pi.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant