Coding agent
A Claude Code and Cursor style coding agent in one block: the thread with reasoning, tool calls and approvals, a workspace with changed files, diffs and the agent's terminal, todos, a message queue, permission mode and a context meter.
On this page
The demo plays a scripted session with no model and no API key. The first edit waits for your approval and allows itself after 3.5 s, so it also runs hands free. A message queued halfway goes out when the run ends. Open /preview/coding-agent-demo to see it full screen, and narrow the window to watch it fold into tabs.
Installation
The block installs to components/chatcn/coding-agent/coding-agent.tsx with every component it composes. It is your code from here: add a file tree, swap the terminal for a live one, change the header.
Usage
Pass messages in the chatcn contract, a status and the actions you support. The workspace builds itself from the tool calls in the thread: edits and writes become changed files and diffs, shell commands become the terminal session, and the latest todo_write becomes the pinned todo list.
"use client"
export default function Page() {
const chat = useChatcnChat({
transport: new DefaultChatTransport({ api: "/api/agent" }),
})
return (
<CodingAgent
fullHeight
messages={chat.chatcnMessages}
status={chat.status}
actions={chat.actions}
branch="fix/runs-list-query"
model="claude-sonnet-4-5"
context={{ limit: 200_000, used: inputTokens }}
/>
)
}Tool names are matched loosely (edit_file, str_replace, apply_patch, write_file, bash, run_command, TodoWrite and their spellings). Edits read a unified diff or old and new text; commands read stdout, stderr and exitCode, and a preliminary output streams into the terminal while it runs. Use codingWorkspaceFromMessages on its own to build the same view elsewhere, or pass workspace to replace any part of it.
import { codingWorkspaceFromMessages } from "@/components/chatcn/coding-agent/coding-agent"
const { files, terminal, todos, pendingPaths } = codingWorkspaceFromMessages(messages)Queue and permission mode
While the agent works, an empty composer offers Stop; typing turns the button back into Send, and sending adds to the queue above the composer. When the run ends, everything queued goes out as one message through actions.send; the rows show Sending until that message appears in the thread. Shift+Tab in the message field cycles the permission mode. The block only shows the mode: read it with onPermissionModeChange and apply it where your tools ask for approval.
Components it composes
| Component | Role |
|---|---|
| Conversation | The scrolling thread that sticks to the bottom while you follow |
| Message and Message parts | Turns and their parts in order |
| Markdown | The agent's prose and code |
| Reasoning | Thinking, folded once done |
| Trace group | Runs of file reads collapsed into Read 4 files. Repeat todo updates stay out of the thread, since the list is pinned |
| Tool call and the tool renderers | Search, read, edit, write and shell calls, approvals included |
| Changed files | The change set in the workspace |
| Diff viewer | The selected file's diff, streaming while the agent writes it |
| Terminal | The agent's shell session with live output |
| Todo list | The plan, pinned above the composer |
| Queued messages | Messages sent while the agent works |
| Prompt input | The composer |
| Permission mode | Ask before edits, edit automatically, plan only or full access |
| Context meter | How full the context window is |
| Run status | Running, needs approval or done, in the header |
| Resizable | The split between thread and workspace |
Responsive
The block measures its own width, not the window, so it behaves the same in a docs column, a dialog or a full page. From 768 px (splitAt) the thread and workspace sit side by side with a draggable divider. Below it, one pane shows at a time under a Chat, Changes and Terminal tab bar, with 44 px targets on touch screens. Inside Changes the file list moves above the diff when the pane is narrow. While the workspace is on screen, edits and commands stay folded in the thread, since their diff and output are one glance away; in the stacked layout they open as usual.
Props
| Prop | Type | Notes |
|---|---|---|
messages | ChatcnMessage[] | AI SDK UIMessage works as is |
status | "ready" | "submitted" | "streaming" | "error" | Same values as useChat().status |
actions | ChatcnActions | send, stop, approve, deny. Missing ones hide their controls |
title | ReactNode | Defaults to the first line of the first user message |
branch, model | string | Quiet meta in the header from 768 px |
headerActions | ReactNode | Extra controls at the end of the header |
workspace | Partial<CodingWorkspace> | Replace any derived part: files, terminal, todos |
toolRenderers | ToolRendererMap | Defaults to every chatcn renderer plus todo_write |
workspaceTab, defaultWorkspaceTab, onWorkspaceTabChange | "changes" | "terminal" | Controlled or uncontrolled |
followActivity | boolean | Move to what the agent touches. Defaults to true; picking a tab pauses it until you send |
permissionModes, permissionMode, defaultPermissionMode, onPermissionModeChange | Controlled or uncontrolled | |
context | { limit, used?, breakdown?, model?, compacted?, onCompact? } | No meter without it |
queue, defaultQueue, onQueueChange | QueuedMessage[] | Controlled or uncontrolled |
placeholder, queuePlaceholder | string | Composer placeholder while idle and while the agent works |
emptyTitle, emptyDescription | ReactNode | Shown before the first message |
splitAt | number | Own width in px for the two pane layout. Defaults to 768 |
fullHeight | boolean | Fill the viewport. Otherwise give the parent a height |
Keyboard
| Key | Action |
|---|---|
Enter | Send, or queue while the agent works |
Shift Enter | New line |
Shift Tab | Next permission mode, in the message field |
Escape | Stop the run, from an empty composer |
Ctrl ` `` | Open the terminal |
ArrowLeft ArrowRight | Move between tabs |
ArrowLeft ArrowRight on the divider | Resize the panes |
Accessibility
- The thread is a labelled
logthat stays silent while tokens stream; finished replies are announced once. - Tabs are a real
tablistwith roving focus, and each panel is labelled by its tab. - Approvals, the queue and the composer are reachable in reading order; the header announces Needs approval as text, not only as an icon.
- The terminal and the diff are focusable scroll regions with their own names.
Motion
The block adds no motion of its own. Messages, tool rows, the todo fold and queue rows use their components' timings, all of them under 300 ms and reduced to fades with prefers-reduced-motion. Following the agent swaps tabs without animation, so nothing moves on its own except the content being written.