* Add WebSocket gateway and control plane endpoint
Adds bidirectional WebSocket transport to the web gateway alongside
the existing SSE stream. Clients can send messages, approvals, and
pings over a single persistent connection at /api/chat/ws.
- Enable axum `ws` feature for built-in WebSocket support
- Add WsClientMessage/WsServerMessage types with tagged JSON protocol
- Add subscribe_raw() to SseManager for non-SSE consumers
- Create ws.rs with connection handler (split sender/receiver tasks)
- Add WsConnectionTracker for active connection counting
- Add /api/gateway/status control plane endpoint (SSE + WS counts)
- 35 new tests covering message types, broadcast, and handler logic
https://claude.ai/code/session_01KEaLN6Xq2j5EeV3SGHQT6b
* Add e2e WebSocket gateway integration tests
- Add tokio-tungstenite dev-dependency for WebSocket client in tests
- Update start_server to return actual bound SocketAddr (enables port 0)
- Add 10 e2e tests covering full HTTP upgrade → WebSocket → message flow:
ping/pong, message routing to agent, broadcast event delivery,
connection tracking, invalid message handling, auth rejection,
gateway status endpoint, and multi-event sequencing
https://claude.ai/code/session_01KEaLN6Xq2j5EeV3SGHQT6b
---------
Co-authored-by: Claude <[email protected]>
- Fix MCP tool schema deserialization: rename input_schema to match
protocol's camelCase inputSchema, so models receive actual parameter
schemas instead of empty defaults
- Fix conversation history: add tool_calls field to ChatMessage and
include assistant message with tool_calls before tool results, as
required by OpenAI-compatible APIs
- Fix approval loop: pass resume_after_tool flag to run_agentic_loop
so the "force tool use" heuristic doesn't re-trigger after approval
- Fix shutdown: add Submission::Quit, Ctrl+C signal handler, and
graceful shutdown flow
- Fix MCP activate button: auto-attempt auth flow when activation
fails due to missing authentication
- Add inline approval cards in chat via SSE ApprovalNeeded events
- Add markdown rendering in chat (marked.js) with proper streaming
- Add structured fields to log entries (key=value pairs from tracing)
- Collapse log entries to single line with click-to-expand
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Auth mode: when a tool requires an API key, the thread enters a special
mode where the next user message is routed directly to the credential
store, bypassing logs, turns, history, and compaction entirely. This
prevents tokens from leaking into debug output or persistent storage.
Fix MCP auth: auth_mcp now actually uses the token parameter (was
ignored as _token) and falls back to manual token entry when OAuth
and DCR are both unsupported.
Parallel loading: WASM tools, WASM channels, and MCP servers now load
concurrently at startup. Within each loader, individual items also
load in parallel (join_all for WASM, JoinSet for MCP servers).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace the dual-server architecture (HttpChannel + WasmChannelServer both
competing for port 8080) with a single WebhookServer that composes route
fragments from all sources. Channels define routes but never spawn servers.
- Add WebhookServer struct that collects Router fragments and binds one listener
- Extract routes() from HttpChannel, remove server-spawning from start/shutdown
- Delete WasmChannelServer (keep WasmChannelRouter and route builder)
- Rewire main.rs to compose all webhook routes into one server
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Drop the full Ratatui TUI in favor of a lighter REPL channel built on
rustyline (line editing, history, tab-completion) and termimad (inline
markdown rendering). Removes ratatui and crossterm event-stream deps,
adds rustyline and termimad. Simplifies main.rs startup to use the REPL
directly instead of the alternate-screen TUI.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Introduces a unified extension abstraction over MCP servers and WASM tools
with six agent-callable tools (tool_search, tool_install, tool_auth,
tool_activate, tool_list, tool_remove) so users can add capabilities
conversationally without CLI commands.
Includes built-in registry of 11 MCP servers, online discovery via URL
probing and GitHub search, OAuth 2.1 flows for MCP servers, and manual
token auth for WASM tools.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Thread message metadata through Channel::send_status so WASM channels
can route status updates (like typing indicators) to the correct chat.
The WasmChannel spawns a background task that repeats on_status every
4 seconds to keep Telegram's typing bubble alive until the response
is sent.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Renamed project from "near-agent" to "ironclaw" throughout the codebase.
Updated documentation to emphasize the core philosophy:
- Your data stays yours (local, encrypted, no telemetry)
- Self-expanding capabilities (build tools on the fly)
- Defense in depth (WASM sandbox, prompt injection defense)
- Always on user's side
Key changes:
- Package name: near-agent -> ironclaw
- Config paths: ~/.near-agent/ -> ~/.ironclaw/
- Database name in docs: near_agent -> ironclaw
- CLI binary: near-agent -> ironclaw
- Log filters: RUST_LOG=near_agent -> RUST_LOG=ironclaw
- All user-facing strings (welcome messages, help text, etc.)
Preserved for compatibility:
- HKDF salt "near-agent-secrets-v1" (changing would break existing secrets)
- WIT interface names (near::agent::*)
- NEAR AI provider config (NEARAI_* env vars)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The WASM channel no longer needs its own polling_enabled/tunnel_url settings.
Instead, the host injects tunnel_url and webhook_secret into the channel config
at runtime before start() is called.
Changes:
- Add update_config() method to WasmChannel for runtime config injection
- Simplify TelegramConfig to only have bot_username, respond_to_all_group_messages
- Host injects tunnel_url (from Settings) and webhook_secret (from secrets store)
- Channel checks if tunnel_url is present to determine webhook vs polling mode
- Add delete_webhook() for clean transition to polling mode when no tunnel
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Enable instant message delivery for Telegram via webhooks instead of polling.
Key changes:
- Add tunnel URL configuration for local development (ngrok, cloudflare)
- Auto-register webhook with Telegram API on startup using setWebhook
- Implement webhook secret validation via X-Telegram-Bot-Api-Secret-Token header
- Add credential injection for bot token via URL placeholder substitution
- Fix metadata preservation in respond() to route replies correctly
- Fix serde flatten with Option<T> issue in capabilities schema parsing
The credential injection pattern replaces {TELEGRAM_BOT_TOKEN} placeholders
in URLs with the actual token from the secrets store, keeping credentials
out of WASM module memory until the HTTP request is made.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add NearAiChatProvider using /v1/chat/completions endpoint with API key auth
- Add NEARAI_API_KEY and NEARAI_API_MODE config options
- Auto-detect API mode from presence of API key
- Keep existing Responses API (NearAiProvider) for session-based auth
- Fix response parsing to accept input_text/output_text/text content types
- Expand REPL with /help, /debug toggle, colored output
- Better tool status display (dots vs verbose based on debug mode)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit addresses three critical issues from code review:
1. Tool approval enforcement: Tools declaring requires_approval() (shell,
http, file write/patch, build_software) now gate execution. Adds
PendingApproval struct, session-scoped auto-approved tools set, and
approval flow with yes/no/always commands.
2. Tool definition refresh: Tool definitions now refresh each iteration
in both chat and job loops, so newly built tools become visible
immediately within the same session.
3. Worker tool call handling: Changed respond() to respond_with_tools()
when select_tools returns empty, properly executing tool calls instead
of formatting them as text.
Also includes prior work from the plan:
- Wire embeddings provider (OpenAI + NEAR AI) to workspace
- Load workspace system prompt (identity files) into LLM context
- Route heartbeat notifications through channel manager
- Enable auto-context compaction when threshold exceeded
- Refactor to config structs (AgentDeps, WorkerDeps, LlmCallRecord)
- Fix clippy warnings (saturating_sub, too_many_arguments)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Switch from List to Paragraph widget for message display
- Split message content by newlines and render each line separately
- Auto-scroll to show most recent messages
- Add empty line between messages for readability
- Wrap long lines properly
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove EnableMouseCapture/DisableMouseCapture from terminal setup
- Users can now select and copy text using normal terminal selection
- We weren't using mouse events anyway
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add ThinkingMessage variant to AppEvent for chat-visible status
- Add set_thinking() and clear_thinking() to AppState
- Thinking messages show as system messages with spinner indicator
- Auto-clear thinking messages when agent response arrives
- Tool started/completed now shown in chat window
- Add debug logging to NEAR AI provider to diagnose empty responses
- Log raw response output when text extraction returns empty
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add StatusUpdate enum with Thinking, ToolStarted, ToolCompleted, StreamChunk, Status variants
- Add send_status method to Channel trait (default no-op)
- Implement send_status in TuiChannel to show status in UI
- Add send_status to ChannelManager for routing to specific channels
- Update handle_message to send "Processing..." status for Chat/CreateJob
- Update handle_chat to send "Generating response..." and show errors
Now when a user sends a message, they see feedback that the agent is working.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
When the TUI quits (Ctrl+D twice), it now:
1. Sends a "/shutdown" message through the channel before closing
2. Explicitly drops msg_tx to ensure channel closure
The agent loop now:
1. Returns Option<String> from handle_message (None = shutdown)
2. Handles /quit, /exit, /shutdown commands by returning None
3. Breaks out of the main loop on shutdown signal
4. Lists /quit in help menu
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove SimpleCliChannel (only TuiChannel remains)
- Add ctrl_d_pending flag to AppState for two-press quit behavior
- Implement Ctrl+D twice to quit (first press shows hint, second quits)
- Add TuiLogWriter with MakeWriter impl for tracing integration
- Create TuiChannel event channel upfront in new() so log_writer() works
- Configure tracing to send logs to TUI status line
- Any key press clears the Ctrl+D pending state
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Store event_tx in Arc<Mutex<>> so respond() can send to TUI
- Fix run_event_loop to take owned receiver
- Switch main.rs from SimpleCliChannel to TuiChannel
- Add proper terminal cleanup on TUI error
Co-Authored-By: Claude Opus 4.5 <[email protected]>