Repository navigation
feat: headless bots with a real chat (M4.1) - #9
Merged
Merged
Conversation
The owner wants to talk to each bot like in a messaging app: bubbles, what the bot did at a glance, approvals with buttons, images and files. A terminal cannot give that. Each bot now runs as `claude -p` with stream-json on stdin and stdout; the daemon turns the events into chat items, messages go in through stdin (the owner's with full authority), approvals go through a permission-prompt tool on our MCP server, and attachments land in the bot's folder. Every behavior the design relies on was checked with Claude Code 2.1.284 (spec 19): input shape, several turns per process, queued input, replay with our uuid, image blocks, the permission-prompt tool, and isolation from the owner's settings. M4.1 is the switch.
Bots now run `claude -p` with stream-json on stdin and stdout instead of the interactive TUI in a PTY (ADR 0001). The daemon turns what each bot prints into chat items (replies, tool calls, turns, notices) and live text deltas, and the courier writes messages into stdin, with read receipts from the replayed user message. - protocol 2: chat.history, approvals.answer, chat.item and chat.delta; terminal.* is gone - messages can carry attachments, saved in the bot's folder; images also go inline - tool permissions go through the permission_prompt MCP tool and wait for the owner in the chat - bot state comes from the stream: busy, needs_approval, rate_limited and auth_error - the PTY runtime, hooks and the named-pipe inbox are removed
attachments.read returns a file the owner sent, as it is now in the bot's folder, so the app can show images after a reload. A file the bot removed is not_found; one that grew past attachment_max_mb is refused.
A link followed inside the webview would replace the app. open_url hands http and https links to the default browser and refuses anything else, since other schemes can start programs.
Each bot opens on its conversation, laid out like a messaging app: - the owner's messages on the right with delivery and read receipts, other bots' and Botloft's on the left - replies in markdown, streamed live while the bot writes them - tool calls as compact lines that open to show input and output - approval cards with Allow, Deny and a note for the bot - a composer where Enter sends, Shift+Enter breaks the line, and files can be picked, pasted or dropped; images show as thumbnails and are read back with attachments.read The sidebar lists bots as conversations with their state and last activity, and bot details moved to a side panel. xterm.js and the terminal feature are gone.
Claude Code loads our MCP tools on demand, so a bot calls ToolSearch before its first send_message. The tool line now reads "load send_message" instead of an empty summary.
Headless bots never show Claude Code's folder-trust dialog, so the welcome screen now explains approvals instead. The empty chat shows the bot's role on its own line rather than running it into the next sentence.
Tested with Claude Code 2.1.284 through the daemon and the app: approvals answered after 95 s, denials with a note, resume after restarting the bot and the daemon, rules in -p, Windows-path deny rules in -p, deferred MCP tools, and where Claude Code keeps inline images.
httpsphl
enabled auto-merge
September 28, 2026 22:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bots now run as headless Claude Code and the app talks to them in a chat, like a messaging app, instead of showing a terminal. The design is in ADR 0001 and spec v0.2 (sections 7 to 10, 15, 17 M4.1 and 19).
Daemon
claude -pwithstream-jsonon stdin and stdout; no PTY, hooks or named-pipe inbox.portable-pty, the hooks and the inbox code are gone.chat.history,approvals.answer,attachments.read,chat.itemandchat.delta;terminal.*is gone.permission_promptMCP tool and wait for the owner (60 min by default). A request expires when its connection drops, when the process ends or when the daemon starts.busy,needs_approval,rate_limited(with the account usage insystem.status) andauth_error. Sessions resume after a restart, and a resume that dies at once starts fresh.App
open_urlcommand (http and https only); images in replies are not fetched.Checks
cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo test --workspaceandpnpm checkpass locally.New tests:
tests/chat.rs(items, deltas, approvals allowed, denied, timed out, unknown and expired on exit),tests/supervisor_turns.rs, andtests/attachments.rs; courier and supervisor tests were rewritten for stdin.chat.test.tsxandrows.test.ts.Tested with real Claude Code (2.1.284)
Done on a dev daemon with a fresh
BOTLOFT_HOMEand the app preview (pnpm dev,?live=<port>). Results are in spec section 19. To repeat:BOTLOFT_HOME, open the app against it, create a crew and a bot.send_messageit. The note shows in the second bot's chat, and its answer reaches the first.claude.exewithmcp__botloft__permission_promptin its command line remains.