- event_bus is now accessible via event_sender() for background threads
- drain_events() processes TuiEvent variants and updates components:
StreamTextDelta, TurnComplete, TurnError, TurnStarted, etc.
- read_line() drains events on each tick before polling for keys
- capture_turn() adds infrastructure for incremental output capture
- Fix flaky capture test (BufferRedirect shares fd across test threads)
- Current turn loop remains synchronous; background threading is next step
- Adds capture_turn() that wraps a closure with stdout/stderr capture
and calls an on_output callback with captured text
- Infrastructure for the streaming event bridge — when turns run on a
background thread, this will feed incremental output to the TUI
- For now, the turn loop continues to use leave-and-return via
TerminalGuard because CliPermissionPrompter needs stdin access
- RAII guard owns alternate screen + raw mode lifecycle
- Drop guarantees cleanup even on panic — replaces panic hook
- Consolidates 6 ad-hoc methods into leave_for_turn/reenter_after_turn
- main.rs uses guard-based lifecycle instead of suspend/resume
The panic hook in tui_update is no longer needed for cleanup —
the guard's Drop fires on panic and restores terminal state.
- mark input bar turn-in-progress on submit
- re-enable input after turn resumes
- clear input area before redraw
- clear completion popup before redraw
- resume re-enters alternate screen and clears cleanly
This prevents typed text from overwriting itself and stops redraws from
spilling word-wrapped text outside the input box.
The TUI is not active during tool execution. The flow becomes:
1. leave_for_turn() — exit alt screen, enable raw mode
2. cli.run_turn_to() — stdout/stderr goes to normal terminal
3. wait_to_return() — 'Press any key to return to TUI...'
4. reenter_after_turn() — re-enter alt screen, redraw
This guarantees zero output bleeding. Replaces suspend/resume approach.
- Add dependency for safe stdout/stderr capture during turns
- Replace unsafe libc::dup/dup2 stdout suppression with capture_output()
- Tool stdout/stderr is now captured and rendered inside conversation pane
- Add compact StatusBar component for focused layout
- Redraw layout: status bar top, large conversation, compact input,
optional dashboard on the right for wide terminals
- Eliminates output bleeding onto the TUI frame
- 321 tests pass
The Component trait's render(&self) couldn't mutate the cache.
Switched to RefCell<RenderCache> for interior mutability so the
word-wrapped line cache rebuilds on dirty during render().
Also adds Dashboard::clear_dirty() and marks components clean
after each draw_screen() cycle.
THE REAL FIX: Before each model turn, save fd 1 (stdout) via dup(),
redirect it to /dev/null via dup2(), run the turn, then restore the
original fd. This catches EVERY possible stdout write during the turn:
runtime streaming, tool executor formatted output, bash subprocess
inheritance, crossterm escape codes, println! — all silently swallowed.
Previous approaches (emit_output=false, Vec<u8> buffer, screen clear)
were insufficient because the runtime's consume_stream writes to the
real io::stdout() through separate code paths that bypass our buffer.
Changed workspace unsafe_code from forbid→warn, and allow it in
rusty-claude-cli specifically. The dup/dup2/close calls are
single-threaded POSIX syscalls with well-understood semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The run_turn_to buffer + emit_output=false was correctly wired,
but runtime internals (child process stderr, tokio tracing, or
unstructured error paths) can still write to the real terminal fd.
Added a crossterm Clear(All) + MoveTo(0,0) after each turn to wipe
any stdout debris that may have landed on the alternate screen,
followed by a full TUI redraw to re-render both panes cleanly.
This is the belt-and-suspenders approach: buffer captures what we can,
screen clear catches what we can't.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fix failing test_filter_by_label: use 'Z' (matches nothing) and 'Help'
(matches specific entries) instead of 'e' which matched everything
- Replace all 36 hardcoded Color:: in tui.rs and markdown.rs with theme
lookups (theme.conversation_user, theme.dashboard_key, etc.)
- MarkdownRenderer now stores a TuiTheme, render_diff() accepts &TuiTheme
- section() and kv() helpers take theme param for key/color lookups
- draw_left_pane/draw_right_pane use theme for all colors
- push_user_input/push_system_message/push_output read from self.theme
- Only remaining Color:: in production code: Color::Reset (unstyled bg)
and Color::Rgb (dynamic syntect output) — both acceptable
283 tests pass, 0 failures. Clippy clean. Workspace builds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
S1-1: Fix ProviderSwap — use suspend()/resume() instead of restore_terminal()
so TUI re-enters cleanly after the setup wizard runs.
S1-2: Fix status message race — remove immediate ds.status_message.clear()
after set_status("Done") so the message is visible until next turn.
S1-4: Bound conversation memory — add MAX_CONVERSATION_LINES (10,000).
auto_scroll() now drains oldest entries and inserts a trim notice.
S1-5: Wire real token tracking — use runtime::UsageTracker::from_session()
to read cumulative TokenUsage (input, output, cache_creation, cache_read)
and pricing_for_model() to estimate cost. No more char÷4 guess.
Sprint 1 of 7. 218 tests pass.
Authored by TheArchitectit
- Create src/tui_update.rs with canonical strip_ansi() (handles CSI, OSC, DCS)
- Add DashboardUpdate struct to decouple from private LiveCli
- Remove duplicate strip_ansi_escapes from tui.rs and strip_ansi from main.rs
- update_dashboard() now delegates to tui_update::update_dashboard_from()
- 12 unit tests for ANSI stripping (CSI, OSC/BEL, OSC/ST, DCS, 256-color, RGB)
Sprint 0, Story S0-1. Pure extraction — no behavioral changes.
Authored by TheArchitectit
Architecture C from expert panel review: add run_turn_to<W: Write>
that writes all output (spinners, markdown, compaction logs) to a
custom writer instead of hardcoding stdout.
In the TUI path, we pass a Vec<u8> buffer. Zero bytes hit the real
terminal during a turn, so nothing bleeds past the conversation pane
boundary into the dashboard. After the turn, the buffer contents are
stripped of ANSI codes and pushed into the conversation pane where
wrap_line() constrains them to the pane width.
Key changes:
- run_turn() is now a thin wrapper: run_turn_to(input, stdout, true)
- run_turn_to<W: Write>(input, out, emit_output) is the real impl
- All println! → writeln!(out, ...), &mut stdout → out
- emit_output=false in TUI mode prevents runtime tool-stream output
- Add strip_ansi() helper for cleaning captured buffer text
- Remove Clear(ClearType::All) from redraw_after_turn (no debris to wipe)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add 'mod tui;' to main.rs (was missing — TUI code was dead)
- Add /tui slash command that switches from the plain REPL to the
split-pane TUI mode at runtime
- Show startup hint: 'Tip: type /tui for the split-pane dashboard view'
- Add run_tui_repl() that manages the TuiApp event loop, captures
assistant output from the session, and feeds it to the conversation pane
- Add update_dashboard() helper that syncs runtime stats (model, session,
turns, provider) into the shared dashboard state
- ProviderSwap and TeamToggle work inside TUI mode
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three fixes for the rendering mess when output goes off-screen:
1. Word-wrap conversation pane lines — long output no longer bleeds
into the dashboard. The wrap_line() function soft-wraps at word
boundaries and hard-breaks long tokens. The viewport math is
updated to count *visual* rows (not logical Line items).
2. Dashboard kv() aligned columns — keys are right-padded to a
fixed 12-char column (KV_KEY_WIDTH) so values line up cleanly
regardless of key length.
3. Context gauge positioned by row tracking, not hard-coded y offset.
The gauge_row counter moves with the section content, so it stays
correct whether git_branch, LSP, or Team sections are shown/hidden.
Also switched dashboard Wrap to trim: false to preserve spacing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Split multi-line user input and system messages on newlines so each
visual line gets its own ConversationLine (same pattern push_output
already used). This fixes text misalignment when Enter is pressed.
- Don't enable Wrap on the conversation Paragraph — the FIFO viewport
counts Line items to fill the pane, so soft-wrapping would throw off
the row count and misalign content.
- Restore non-TUI files from upstream/main to clear conflict artifacts.
- Fix clippy warnings in lsp_client, lsp_process/parse, and trident.
- Add HandlerSwap and TeamToggle match arms in main.rs ReadOutcome.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Removed app.suspend()/app.resume() calls around REPL TUI turns
- Added assistant message capture from session for conversation pane
- Command errors now show as system messages instead of breaking TUI
- Agents post completion/failure to team inbox on termination
(.clawd-agents/mailbox/team/{team_id}/{agent_id}-{ts}.json)
- Team watcher reads from inbox instead of polling .json files
- New TeamStatus action=inbox reads team messages from the inbox
- AgentOutput carries team_id, persisted in manifest
- AgentInput accepts team_id from TeamCreate
- TeamCreate passes team_id to each spawned agent
- Inbox cleaned up when all agents finish
Co-authored-by: GLM 5.1 FP8 via Crush <crush@charm.land>
On REPL start, check for missing provider.apiKey, provider.baseUrl,
and subagentModel. Print a warning with instructions to run `claw setup`
or `/setup` if any are absent.
Co-authored-by: GLM 5.1 FP8 via Crush <crush@charm.land>
- TeamStatus tool with 3 actions:
- status: live snapshot (running/completed/failed counts, agent details)
- summary: final results when agents finish (includes result content)
- events: timeline from append-only event log
- Background team watcher thread spawned by TeamCreate:
- Polls agent .json files every 2s
- Prints [team] progress to stderr on agent completion/failure
- Updates team manifest status when all agents finish
- Writes events to .clawd-agents/teams/{team_id}-events.jsonl
- TeamStatus added to PARALLEL_SAFE_TOOLS and all agent allowed_tools
Co-authored-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Instead of erroring when neither mode nor tasks are specified,
default to "2x" (2 Explore + 2 Plan + 2 Verification = 6 agents).
Co-authored-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Add 'mode' field to TeamCreate that auto-generates agent teams:
- "2x" = 2 Explore + 2 Plan + 2 Verification = 6 agents
- "4x" = 4 Explore + 4 Plan + 4 Verification = 12 agents
- "6x" = 6 Explore + 6 Plan + 6 Verification = 18 agents
When mode is set, 'prompt' provides the shared task description and
'tasks' is ignored. Also add 'subagent_type' and 'model' fields to
manual task items for per-task role and model control.
Co-authored-by: GLM 5.1 FP8 via Crush <crush@charm.land>
- Add missing AgentMessage dispatch entry in execute_tool_with_enforcer
(tool spec existed but couldn't actually be called)
- Add AgentMessage to Explore/Plan/Verification/general-purpose
allowed_tools lists so sub-agents can communicate
- Add debug logging to resolve_agent_model and load_subagent_model_from_config
to diagnose subagentModel config chain
Co-authored-by: GLM 5.1 FP8 via Crush <crush@charm.land>
- Agent tool is now parallel-safe: multiple Agent calls execute concurrently
- AgentMessage tool: send/read/broadcast between agents via shared mailbox
- TeamCreate rewired to spawn real Agent threads instead of TaskRegistry
- Agents set CLAWD_AGENT_ID env var so they can identify themselves for messaging
- AgentMessage added to Explore/Plan/Verification sub-agent tool lists
- Team manifest persisted to .clawd-agents/teams/{team_id}.json
Match Claude Code's architecture: a single Agent tool with subagent_type
(Explore/Plan/Verification) that uses the subagentModel setting when
no explicit model override is provided. The separate SubAgent tool is
removed since it duplicated existing functionality.
Add a SubAgent tool that lets the main model delegate multi-step read
and search tasks to a fast, inexpensive sub-agent. The sub-agent runs
autonomously with its own ConversationRuntime, making multiple tool
calls without round-tripping through the main model.
- SubAgent tool with prompt, task_type (Explore/Plan/Verify), and
optional model override
- Explore: read_file, glob_search, grep_search, WebFetch, WebSearch
- Plan: Explore + TodoWrite
- Verify: Plan + bash
- subagentModel config field in settings.json (falls back to main model)
- Uses ProviderRuntimeClient + SubagentToolExecutor (same as Agent tool)
This dramatically reduces token usage for information-gathering tasks.
Example: "find all Rust files that import X and summarize their usage"
→ one SubAgent call instead of 10 sequential main-model round trips.
When the model returns multiple tool_use blocks in a single response,
read-only tools (read_file, glob_search, grep_search, WebFetch,
WebSearch, LSP, Git*) now execute concurrently via std:🧵:scope
instead of sequentially.
Architecture:
- Add ToolCall/ToolResult types to ToolExecutor trait
- Add execute_batch() with default sequential fallback
- CliToolExecutor overrides execute_batch to classify tools as
parallel-safe or sequential, then runs parallel-safe tools
concurrently via thread scopes
- run_turn refactored into 3 phases:
1. Pre-hooks + permission checks (sequential, hooks may mutate state)
2. Tool execution (batch, parallel for read-only tools)
3. Post-hooks + session updates (sequential, preserves ordering)
Safety guarantees:
- Pre/post hooks always run sequentially
- Permission checks complete before any tool executes
- Tool results pushed to session in original model order
- Fallback to sequential for single-tool batches
Adds a project rules system for loading instruction files into the
system prompt, plus auto-import from other AI coding frameworks.
- `.claw/rules/*.md` / `.claw/rules/*.txt` / `.claw/rules/*.mdc`
- `.claw/rules.local/*.md` — personal/local rules (gitignored)
- Existing: `CLAUDE.md`, `CLAUDE.local.md`, `.claw/CLAUDE.md`,
`.claw/instructions.md`
When users switch to claw-code from another tool, their existing
rules are automatically detected and loaded:
- Cursor: `.cursorrules`, `.cursor/rules/`
- GitHub Copilot: `.github/copilot-instructions.md`
- Windsurf: `.windsurfrules`, `.windsurfrules/`
- Aider: `.aider.conf.yml` instructions block
- Pi (Plandex): `.plandex/instructions.md`, `.plandex/plan.md`
- OpenCode: `opencode.json` instructions field
- Crush: `.crush/CLAUDE.md`, `.crush/rules/`
`rulesImport` in settings.json controls framework auto-import:
- `"auto"` (default) — import from all detected frameworks
- `"none"` — only load .claw/rules/ and CLAUDE.md files
- `["cursor", "copilot"]` — import only from listed frameworks
```json
{
"rulesImport": "auto"
}
```
💘 Generated with Crush
Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
- Startup now shows "Loading LSP servers..." then ✓/✗ per server
- When auto-start is on: shows disable hint (toggle or settings.json)
- When auto-start is off: shows available servers with how to start
💘 Generated with Crush
Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Set NODE_NO_WARNINGS=1 when spawning LSP server processes to suppress
noisy punycode deprecation warnings from bash-language-server,
yaml-language-server, vscode-* servers, etc.
💘 Generated with Crush
Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Godot LSP runs as a TCP server on localhost:6008 when the editor is
open — it doesn't speak LSP over stdio like other servers. Added
connect_tcp() to LspTransport which uses socat (or nc fallback) as
a stdio↔TCP bridge, reusing the existing Content-Length framing.
lsp_process detects tcp:// URIs and routes to TCP transport.
LSP startup now gracefully handles servers that fail to start
(gdscript without a running Godot editor) without blocking other
servers from initializing.
💘 Generated with Crush
Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
- Add distro-aware install prompt system: detects Ubuntu/Debian/Fedora/
Arch/openSUSE/Alpine/Void/NixOS/macOS and suggests the right install
command for each missing LSP server at startup
- Add 6 new language servers: HTML, CSS, JSON, Bash, YAML, GDScript
- Add didClose notifications for proper file lifecycle
- Add code_action support (quick fixes, refactors)
- Add rename support (workspace-wide symbol renaming)
- Add signature_help (function signatures + parameter hints)
- Add code_lens (inline actionable hints)
- Add workspace_symbols (project-wide symbol search)
- Add workspaceFolders support in initialize handshake
- Advertise full capability set (code actions, rename, signatures,
code lens, workspace symbols) to LSP servers
- Fix panic in lsp_discovery test when rust-analyzer is a rustup
proxy stub for an uninstalled component
💘 Generated with Crush
Assisted-by: GLM 5.1 FP8 via Crush <crush@charm.land>
Remove SlashCommand::Setup (provider wizard), PROVIDER_FIELDS
(provider config), and stale imports that leaked in from the
feat/lsp-integration branch which included other PRs. Also fix
pre-existing clippy findings (Duration::from_hours, is_ok_and).
- Add lsp_auto_start field to RuntimeFeatureConfig (default: true)
- Add lspAutoStart bool field validation in config_validate
- Parse lspAutoStart from config JSON
- Auto-start discovered LSP servers on REPL init when enabled
- Add /lsp toggle command to enable/disable auto-start at runtime
- Remove lsp_client.rs, lsp_process.rs, lsp_transport.rs (2831 lines)
— functionality consolidated into discovery-based auto-start
- Show auto-start status in /lsp status output
Wire LSP into the Read/Edit/Write tool flow so the agent automatically
gets diagnostics after file operations:
- lsp_transport: Add LspServerMessage enum, read_message() for handling
both responses and server-initiated notifications, notification queue
with drain_notifications(), send_request now handles interleaved
publishDiagnostics without breaking
- lsp_process: Add did_open(), did_change(), drain_diagnostics(),
open file tracking (HashSet) and version counters for didChange,
language_id_for_path() and severity_name() helpers
- lsp_client: Add notify_file_open(), notify_file_change(),
fetch_diagnostics_for_file() with best-effort graceful fallback,
registry-level open file tracking, diagnostic caching
- tools: Enrich run_read_file with didOpen + diagnostics, run_write_file
and run_edit_file with didChange + diagnostics, format_diagnostic_appendix()
for readable diagnostic output appended to tool results
All enrichment is non-blocking: if no LSP server is available, tools work
exactly as before. No errors propagate from the LSP layer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>