Repository navigation
Answer with components by default, draw code like Claude Code, and widen answers - #940
Conversation
Every Bot is offered the gallery, but nothing said when to use it, so a plan or the details of one thing came back as prose. One standing instruction, beside the provenance rule on the same paths (built-in prompt and the remote standing role, which background turns also use), names kinds of answer rather than tools: figures as a chart or metrics, steps as a checklist or table, one thing's details as a record card, a choice as a choice card, with a sentence or two of text around it. Plain text stays for short conversational replies.
…, and widen answers Bash, Edit, MultiEdit, Write, Read, Grep, Glob, TodoWrite and Claude Code's task tools get their own renderers: a command with its output folded after six lines, an edit or a new file as a red and green diff, reads and searches as one line with a count, and a plan as a checklist. They are built in the frame of a fenced code block, and read a failure both as the harness streams it and as the history it sends at the end of a turn has it. Tests draw them through the real transcript from calls recorded off the real harness. Fenced code is highlighted through Streamdown's code plugin, in light and dark, in replies, tool answers and group chats. A Bot's answers run to max-w-4xl while the composer stays max-w-2xl; every skin keeps its column as wide as its composer.
Gallery cards kept their 672px reading width inside the new 896px answer column, so a chart sat narrower than the code and tool cards around it. In a conversation they now fill the column; previews elsewhere keep the reading width.
guidovizoso
left a comment
There was a problem hiding this comment.
Nice work, the coding cards and the component guidance are both solid, and the tests draw from real harness recordings, which I like. A few things inline. The main one is the width: I'd go to 768px (max-w-3xl) instead of 896px for now, and we can add a full-width mode soon for people who want more room.
At 896px a line of prose runs long enough to be harder to read, and the width reaches every answer, not only code. 768px (max-w-3xl) is the reading width ChatGPT and Claude.ai give an answer, and it is still wider than the 672px column main draws, so answers still widen. Code blocks and diffs scroll sideways, so they keep working at 768px.
replace_all was parsed but not shown, so an edit that changed every occurrence of its text read as a single replacement. The diff can only show the one replacement, because the arguments carry the text and not the file, so the card's header now says All occurrences.
…ailure in
ag-ui-claude-sdk wraps a failed result as {"error": true, "content": "..."}, but it also parses
any result that is JSON text and sends it on re-serialised. A command that printed an API's
{"error": true, "message": "..."} therefore read as "Didn't work". Only error plus a string
content, and nothing else, now counts; the ambiguity that remains is noted where it is read.
…ents The coding cards are registered by bare name (Bash, Read, Edit) for every Bot, because the browser cannot tell which harness a Bot runs: a Bot is an AG-UI address, and the harness picked in the New Bot dialog is never stored. Another harness with a tool of the same name would have drawn mostly empty cards. A call is now drawn as Claude Code's only when its arguments have the fields the Agent SDK's tool inputs require; anything else under those names is drawn, and folded, like any tool the app has no component for.
… does Registering a renderer for every coding tool stopped groupToolSteps folding them, so a long coding turn drew one row per call. Claude Code itself folds a run of reads and searches (Read, Grep, Glob, and plain ls, cat or grep commands) into one line, "Read 3 files, searched for 2 patterns", and keeps edits, writes, other commands and the plan on their own rows. The transcript now does the same, with each step behind the line saying what it found. A shell command folds only when it is one pipeline of a read, search or list program and filters, read with shell-quote, so anything that could change something keeps its own card.
@streamdown/code imports Shiki's full entry and its JavaScript regex engine at its top, and markdown.tsx imported it statically, so Shiki's core rode in the chunk every chat surface loads (483.5 kB, against 403.8 kB on main). Its grammars and themes were already separate chunks. markdownPlugins now stands in for the plugin with the same interface and imports @streamdown/code the first time a code block asks to be highlighted; Streamdown draws the plain code until then, as it does while a grammar loads. The chat chunk is back to 404.5 kB, and Shiki's core is a 79.4 kB chunk fetched on demand.
…ted ones The replace-all edit and the command that printed JSON with an error field were not made by the recorded run, so they sit in their own fixture that says what each was built from.
ag-ui-claude-sdk marks a failed result in the TOOL_CALL_RESULT it streams, but builds the MESSAGES_SNAPSHOT that ends the turn from the bare result, so the mark is lost from the history a reloaded conversation draws. In a real run, a mkdir and two writes that Claude Code's sandbox refused showed as done once the turn ended, the writes as 30 added lines that were never written. The adapter now gives each failed call's snapshot entry the result it streamed.
bun test walks every file in one module graph, so once another file has highlighted code, Shiki is already loaded and the check cannot ask whether a conversation loads it early. Run beside the highlighting test it hung the run. The check now runs as a probe in a fresh bun test, started by the test file, with its results read from a file rather than a pipe.
A folded run of reads and searches is labelled in full, "Read 1 file, searched for 2 patterns and listed 1 directory", and the label could not shrink, so at phone width it ran 59px past the column and was cut off mid-word. The label now wraps, and the detail beside it, already truncated, gives way first.
guidovizoso
left a comment
There was a problem hiding this comment.
Thanks, all seven are sorted, and the extra digging was worth it: the harness fix for refused writes, and catching that Shiki wasn't lazy. I checked the snapshot fix against ag-ui-claude-sdk 0.1.5. It builds each snapshot from the history the client sends plus the new turn, so a patched failure stays failed in later turns. The app tests and the harness tests pass locally too.
Approving. A few notes, none blocking:
- Worth its own issue: you found that ag-ui-claude-sdk only sends the last message as the prompt, so Claude SDK Bots never get the standing role message. That means they're missing
PROVENANCE_GUIDANCEandUNTRUSTED_GUIDANCEtoo, not just the component guidance. The second one matters for prompt injection. - Minor: while a
Bashcall's arguments stream in, a plaincatorlsshows as a full command card, then jumps into the reads-and-searches line once they arrive. It's brief, so fine to leave. - Cosmetic: in the
groupToolStepscomment intool-activity.tsx, a sentence got appended to an existing line, so it runs past the wrap width.
We'll do the full-width mode as a follow-up.
Main is red: `proactive-connected-reads.integration.test.ts` fails on "a Bot on another framework checks in on OpenBot's own runner". **Cause:** #929 and #940 each passed alone but don't compose. #940 put `COMPONENT_GUIDANCE` between the provenance and untrusted-content guidance in a Bot's standing role. #929's check-in runner (`onOpenBotRunner`) strips that guidance from a remote Bot's role before adding OpenBot's own, but only when the role ends with provenance then untrusted-content, which it no longer did. A harness Bot's check-in carried all three twice. **Fix:** `STANDING_GUIDANCE` now matches what `standingRoleMessage` ends with. The test now checks that each of the three is said once. **Checked:** the test fails on main and passes with the fix. The other server tests that use the standing role (130) pass, along with format, lint, server typecheck and test typecheck.
Bots answer with components by default, code looks like Claude Code, and answers are wider.
Components by default. Every Bot already had the component library: charts, tables, cards, checklists, forms, choices, and UI built on the fly. Nothing told a Bot when to use it. A new standing instruction (
COMPONENT_GUIDANCEinshared/bot-prompt.ts) goes to built-in Bots and in the standing role sent to remote harness Bots, in chat and in background turns. When an answer has structure, it uses a component:It adds a sentence or two around the component, and keeps plain text for short replies. It names kinds of answer, never tools, because the tools vary per Bot. Live with the real model:
A Claude Agent SDK coding Bot does not see it:
ag-ui-claude-sdk0.1.5 builds the prompt from the last message and drops system messages, the standing role included. Run live in the app and asked for a plan, then for a code change, a coding Bot planned in prose, worked with its own Bash and Write, and drew no gallery component.Code that looks like Claude Code.
Syntax highlighting. Bot messages, tool results and group chat use Streamdown's official code plugin (
@streamdown/code, Shiki) with OpenBot's light and dark themes. Shiki loads with the first code block, not with the conversation. In the build, Shiki's core is its own 79.4 kB chunk loaded on demand, not about 80 kB more in the chunk every chat route loads, and each grammar and theme is its own chunk too.Coding harness tool cards. Built from OpenBot's own components:
diff. An edit that replaced every occurrence says so.ls,catorgrepcommands: a run of them folds into one line, as Claude Code folds them ("Read 1 file, searched for 1 pattern and listed 1 directory"), which opens to each step. One on its own keeps its own line. A command folds only whenshell-quotereads it as one pipeline of a read, search or list program and filters.The shapes come from recorded runs of the Claude Agent SDK harness. The browser cannot tell which harness a Bot runs, so a call is drawn as Claude Code's only when it carries the arguments Claude Code sends; another harness's tool with the same name is drawn as any other tool.
Bugs fixed, each with a regression test:
"error": trueshowed "Didn't work". Only the exact shape the harness wraps a failure in counts now;Wider answers. The answer column is now 768px (
max-w-3xl), the reading width ChatGPT and Claude.ai use, up from 672px. The input bar stays 672px, and both stay full width on phones. A Bot's components fill the answer column inside a conversation; previews elsewhere keep their reading width. Skins keep their own widths. A full-width mode for code and tables can come later.Checked: