Repository navigation
M4 (2/3): bot terminals in xterm.js - #6
Merged
Merged
Conversation
The app's forms need the daemon's length limits and color palette; the generated file now carries them so the UI never restates them.
Commands for what the page cannot do (spec 15.2): daemon_status checks /health on the port from <home>\config.toml, daemon_start launches the botloftd.exe next to the app detached so it outlives the window, read_owner_token reads the owner token, and open_path opens folders only. The window has no decorations (the app draws its own title bar), and the CSP allows ws://127.0.0.1 on any port so a dev daemon with its own port is reachable.
rpc.ts speaks spec 11 over WebSocket: session.hello first, requests queued until it is accepted, answers matched by id, typed RpcError codes, and reconnection with backoff (0.5 s doubling to 5 s). Requests already sent when the socket drops fail instead of being replayed, since they may have run. A refused hello (token or protocol) stops for good. BotloftApi is the contract components use; client.ts implements it over rpc.ts and FakeBotloft in memory, typed from the generated RpcMethods so every method is covered. Host is the same idea for the Tauri commands and the window.
- Onboarding: finds the daemon, offers to start it, reads the owner token and connects; a taken port, another protocol version or a refused hello each get their own message. The first run lists what is ready (daemon, Claude Code) and explains the one-time folder trust prompt per bot. - Store: crews, bots and system.status load on every (re)connection, then follow crew.changed, bot.changed and bot.state; archived records leave. - Screens: crews and their bots in the sidebar, a crew overview with bot cards, and a bot view with pause, restart (fresh or not), edit, open folder and archive. State reads as color, icon and label; paused bots read as paused. Daemon validation errors stay in the form. - Own title bar with Windows controls, light and dark themes following Windows, IBM Plex type, bot avatars drawn from the mascot. - `pnpm dev` in a plain browser runs the UI against FakeBotloft, for UI work without Tauri or the daemon. The preview is not in release builds.
The docs say a parent folder's trust does not cover a project's permissions.allow rules, and the generated settings allow mcp__botloft, so every new bot asks once; trusting the workspaces root would not help. The app explains the prompt instead, and Botloft leaves ~/.claude.json alone. Spec 15 gains daemon_start (M4, replaced by the service in M5) and how the app finds the daemon's port. The dev config now lives inside the dev BOTLOFT_HOME so the daemon and the app share it.
The replay carries the queries a terminal answers (cursor position, XTVERSION, keyboard flags). A client that answers them again sends stale replies into the bot as keystrokes; with Claude Code 2.1.284 a stray character showed up in the prompt after attaching. The attach result now has liveOffset, where live output starts, so the client can leave the queries before it unanswered (spec 8).
- TerminalSession follows spec 8 without depending on xterm: attach and replay, a newer generation is a new screen, a gap attaches again from the last byte, duplicates and old generations are ignored, and a new connection resumes where the old one stopped. Replayed bytes are marked as history; the view drops what xterm "types" while parsing them. Keys typed while disconnected are dropped instead of sent late. - TerminalView: xterm.js with fit and WebGL, a dark screen in both themes, Ctrl+C copies a selection, and size changes reach the PTY. - The bot view has Terminal and Details tabs; the terminal stays mounted across tabs. A start that takes more than 4 s explains the one-time folder trust prompt; a bot that never started says how to start it. - FakeBotloft gains terminals: buffers, replay after the attach answer, live output, input and sizes. The dev preview shows sample screens, and `?live=<port>` connects it to a real dev daemon. Checked with Claude Code 2.1.284 through the preview connected to a dev daemon: the trust prompt showed in the terminal and was accepted with Down and Enter, the bot went idle, a typed prompt made it busy and back to idle with the answer on screen, and a reload replayed the screen without stray input.
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.
Second of three slices of M4: bot terminals in the app. This PR is built on #5. Merge #5 first (merge commit); this diff then shrinks to the two commits below.
What changes
terminal.attachreturnsliveOffset(spec 8, 11.2): the offset where the replay ends and live output starts.ESC[6n, threeESC[>0q(XTVERSION) andESC[?u(keyboard flags).TerminalSession(spec 8, independent of xterm):TerminalView:windowsPty: conpty;FakeBotloftgains terminals: per-bot buffers, a replay sent after the attach answer (as the daemon does), live output, recorded input and sizes.?live=<port>connects it to a real dev daemon. The owner token comes fromsessionStorage, never from the URL.dist).Tests
session.test.ts:App.test.tsx:liveOffsetin the terminal buffer unit tests and inrpc_terminal.pnpm check,pnpm build, clippy and fmt pass. Each commit passes on its own.Checked with real Claude Code 2.1.284
The test used the dev preview in a browser, connected with
?live=to a fresh dev daemon.SessionStarthook set the bot to Idle, and the notice cleared.Manual test (Tauri window)
With
BOTLOFT_HOMEset to the dev folder, runpnpm tauri dev, then: