* feat: add libSQL/Turso database backend with full feature parity
Introduce a Database trait abstraction (~60 async methods) enabling
compile-time backend selection between PostgreSQL and libSQL/Turso.
Convert all modules from concrete Store to Arc<dyn Database>, add
LibSqlSecretsStore and LibSqlWasmToolStore implementations, wire
libsql stores throughout CLI and main entry points, and make the
setup wizard backend-agnostic.
Key changes:
- src/db/: Database trait, PostgresDatabase adapter, LibSqlBackend
with native SQLite-dialect SQL, and idempotent migration system
- src/secrets/store.rs: LibSqlSecretsStore (all 8 trait methods)
- src/tools/wasm/storage.rs: LibSqlWasmToolStore (all 7 trait methods)
- src/main.rs, cli/tool.rs, cli/mcp.rs: backend-conditional wiring
- src/setup/channels.rs: SecretsContext uses Arc<dyn SecretsStore>
- Feature-gate postgres-only tests and examples
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: enable onboarding wizard for libSQL builds
Refactor the setup wizard to work with both postgres and libsql feature
flags. Previously the wizard was gated behind #[cfg(feature = "postgres")]
only, so libsql-only builds would print an error on `ironclaw onboard`.
- Add libsql fields to Settings (database_backend, libsql_path, libsql_url)
- Split wizard database/migration/secrets methods into feature-gated variants
- Add step_database_libsql() with local path and Turso remote replica prompts
- Update setup/mod.rs and main.rs feature gates to any(postgres, libsql)
- Extend check_onboard_needed() to detect libsql database presence
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review feedback for libSQL backend
- P0: Switch libsql_backend to connection-per-operation pattern to fix
shared Connection concurrency issue across tokio tasks
- P0: Wrap secrets store INSERT+SELECT in transaction to fix TOCTOU race
- P0: Document encryption-at-rest limitations and json_patch divergence
- P1: Fix get_opt_text removing .filter(|s| !s.is_empty()) that conflated
empty strings with NULL
- P1: Replace datetime('now') with fmt_ts(&Utc::now()) for consistent
RFC 3339 timestamps across all queries
- P2: Use explicit _rowid column in FTS5 triggers and joins for stability
across VACUUM operations
- P2: Add tracing::warn when embedding provided but vector search disabled
in hybrid_search
- Extract shared connect_from_config() helper to deduplicate DB connection
logic across main.rs, cli/config.rs, and cli/mcp.rs
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: add missing JobContext fields and resolve fmt/clippy warnings
Add total_tokens_used and max_tokens fields to JobContext in
libsql_backend.rs, apply cargo fmt, and fix clippy warnings.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: review fixes for libSQL backend (shared connections, panics, indexes)
- Replace .expect() with proper error propagation in 3 call sites
- Share Arc<Database> between backend and stores instead of single Connection
- Add connect-per-operation pattern to LibSqlSecretsStore and LibSqlWasmToolStore
- Wrap store() INSERT + SELECT-back in a transaction
- Add ~22 missing indexes for parity with PostgreSQL schema
- Add 18 leak_detection_patterns seed rows matching PostgreSQL V2 migration
- Fix super:: import to use crate:: style
- Gate mask_password_in_url behind #[cfg(feature = "postgres")]
- Rewrite secrets store init with or_else chain for runtime backend selection
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Resolve clippy lints (collapsible_if, too_many_arguments)
Collapse nested if blocks into let_chains to satisfy clippy's
collapsible_if lint (CI uses -D warnings). Suppress too_many_arguments
on libsql_row_to_tool_at since refactoring the positional index
pattern would be a larger change.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Illia Polosukhin <[email protected]>
* fix: comprehensive security hardening across all layers
Critical:
- Replace --dangerously-skip-permissions with explicit tool allowlist
via settings.json (Claude Code bridge)
- Constant-time token comparison (subtle crate) in web auth and
orchestrator auth to prevent timing attacks
High:
- Revoke tokens and clean up handles on container creation failure
- Drop SETUID/SETGID capabilities from containers (keep only CHOWN)
- Disable redirect following in HTTP tool and WASM wrapper (SSRF)
- Reject URL userinfo (@) in WASM allowlist parser (host confusion)
- Fix binary body bypassing leak detection (from_utf8 -> from_utf8_lossy)
- Protect identity files from LLM overwrites (prompt injection defense)
- Prevent tool shadowing: built-in tools cannot be replaced dynamically
- User-scoped job APIs: list/detail/cancel/restart/prompt/events/files
- CORS restricted to localhost origins, WebSocket origin validation
- Sandbox shell fail-closed: no silent fallback to unsandboxed execution
- Scrub secrets from log broadcaster before SSE broadcast
- XSS sanitization on rendered markdown in web UI
- WASM epoch ticker thread so timeout deadlines actually fire
Medium:
- Cap state transition history at 200 entries
- SSE/WebSocket connection limit (100 max)
- Request body size limit (1MB)
- Response body size limit enforcement in WASM HTTP
- UTF-8 safe string truncation (routine engine, shell tool)
- Fix PolicyAction::Sanitize to actually run the sanitizer
- TOCTOU fix in scheduler and context manager (hold write lock)
- Project file serving moved behind auth
- Path traversal guard on project_id
- Session file permissions set to 0600 on unix
- AtomicUsize for routine running_count (panic-safe)
- Completion detection hardened against false positives and tool injection
- Tool output no longer drives job completion (only LLM response)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address security review findings across all layers
- Fix path traversal sandbox bypass via lexical normalization (file.rs)
- Fix SSRF via DNS rebinding with pre-request hostname resolution (http.rs)
- Add token budget enforcement on LLM calls (reasoning.rs, state.rs)
- Fix cross-user chat history leak with ownership verification (store.rs, server.rs)
- Add sliding-window rate limiter on gateway chat endpoint (server.rs)
- Harden extension install: HTTPS-only, 50MB cap, WASM magic validation (manager.rs)
- Add destructive command blocklist that overrides shell auto-approval (shell.rs)
- Add 5MB response body size cap to HTTP tool (http.rs)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: deduplicate shared helpers and remove dead code
Extract floor_char_boundary and llm_signals_completion into src/util.rs,
unifying diverging phrase lists from agent/worker.rs and worker/runtime.rs.
Remove dead RespondResult::usage(), duplicate PROTECTED_IDENTITY_FILES
constant, double LeakDetector scanning in WebLogLayer, and invalid
0.0.0.0 origin from WebSocket allow list.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review findings and CI test failures
- Fix record_failed_approve: .truncate(true) wiped the attempts file
before reading, so failed pairing attempts never accumulated and
rate limiting never triggered.
- Guard wizard WASM test: skip gracefully when channel build artifacts
are absent (CI doesn't compile wasm32-wasip2 targets).
- Fix DNS rebinding check: use port 0 instead of hardcoded 443, since
the port is irrelevant for hostname resolution.
- Remove hardcoded CORS port 3001: the dynamic addr.port() entries
already cover the actual server port.
- Require WebSocket Origin header: reject connections that omit it
entirely, since browsers always send Origin for WS upgrades and a
missing header indicates a non-browser client bypassing the check.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address second round of PR review findings
- store.rs: reintroduce file locking around read-modify-write in
record_failed_approve (concurrent callers could clobber each other).
- sse.rs: replace load+check+fetch_add with atomic fetch_update in both
subscribe_raw() and subscribe() to prevent overshooting max_connections.
- ws.rs: decrement WS tracker before early return when subscribe_raw()
returns None (connection limit reached), fixing a counter leak.
- server.rs: parse WS Origin host exactly instead of prefix matching,
preventing bypass via crafted origins like http://localhost.evil.com.
- workspace_integration.rs: skip tests gracefully when Postgres is
unreachable instead of panicking (fixes 10 CI failures).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: add Origin header to WS integration tests
The Origin header requirement added in a3b0190 broke the WS gateway
integration tests. Test clients now send Origin: http://127.0.0.1:{port}
to match the server's localhost validation.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat: Implement DM pairing for channels
- Introduced a new pairing system to manage direct messages from unknown senders.
- Added `PairingStore` to handle pending requests and allowlist management.
- Implemented CLI commands for listing and approving pairing requests.
- Updated Telegram channel to utilize the new pairing logic, including workspace paths for storing pairing data.
- Enhanced WASM channel integration to support pairing functionality.
This feature enhances security by requiring approval for unknown senders before they can interact with the agent.
* Enhance Telegram channel support with media captioning and DM pairing features
- Added support for media captions in Telegram messages, allowing for richer content handling.
- Updated message processing to utilize either text or caption, improving message flexibility.
- Enhanced DM pairing functionality to include approval and listing capabilities for direct messages.
- Updated feature parity documentation to reflect new capabilities and improvements in Telegram integration.
* Update README and BUILDING_CHANNELS documentation for Telegram channel integration
- Enhanced README with instructions for building and running the Telegram channel, including a note on running `./scripts/build-all.sh` for full releases.
- Added detailed steps in BUILDING_CHANNELS.md for building and deploying the Telegram channel, emphasizing the need to run `./channels-src/telegram/build.sh` before building the main crate to ensure updated WASM is included.
- Updated CLI module to expose a new command for pairing with store functionality.
* Implement build script for Telegram channel WASM and enhance pairing error handling
- Added a new `build.rs` script to automate the compilation of the Telegram channel's WASM binary from source, ensuring reproducible builds and emphasizing supply chain security by preventing committed binaries.
- Updated `BUILDING_CHANNELS.md` to reflect the new build process and the importance of not committing compiled binaries.
- Enhanced error handling in the pairing approval process to include rate limiting for failed attempts, improving security and user feedback.
* Remove Telegram channel WASM binary file as part of the build process cleanup, ensuring no committed binaries are present in the repository.
Add support for OpenAI, Anthropic, Ollama, and OpenAI-compatible
endpoints alongside the existing NEAR AI backend. Users can now
bring their own API keys via environment variables (LLM_BACKEND,
OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) while NEAR AI remains
the default.
Co-authored-by: Claude Opus 4.6 <[email protected]>
* Orchestrating jobs and running them in sandboxes
* Fix heartbeat: dynamic max_tokens, empty content guard, notification fallback
- Query /v1/models API for context_length and set max_tokens to half
(floor 4096) instead of hardcoded 1024; reasoning models like GLM-4.7
need much larger budgets
- Guard against empty LLM content (reasoning models can burn all tokens
on chain-of-thought and return content: null)
- Simplify notification routing: try configured channel first, fall back
to broadcast_all so heartbeat alerts always reach someone
- Add ModelMetadata struct and model_metadata() to LlmProvider trait
- Refactor NearAiChatProvider::list_models into shared fetch_models()
- Add standalone test_heartbeat example for isolated debugging
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Add job detail view with drill-down from jobs list
Click a job row to see full details across four sub-tabs:
Overview (metadata grid, description, state transitions timeline),
Actions (expandable tool call cards with input/output JSON),
Thinking (conversation messages styled by role), and
Files (embedded workspace tree browser).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Strip model-internal XML tags from LLM responses, fix Telegram parse_mode 400
Some models (GLM-4.7, etc.) emit <tool_call>tool_list</tool_call> in the
content field instead of using the OpenAI tool_calls array. This XML leaks
through to channels as text, and Telegram's Markdown parser chokes on the
underscores, returning 400 "can't parse entities".
Two fixes:
- Generalize clean_response() to strip <tool_call>, <function_call>,
<tool_calls>, and pipe-delimited variants (<|tool_call|>) alongside
the existing <thinking> tag stripping
- Add Telegram send_message helper with parse_mode fallback: try Markdown
first, retry as plain text on "can't parse entities" 400 errors
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Add SystemCommand submission type for thread-state-independent commands
System commands (/help, /model, /version, /tools, /ping, /debug) now
bypass thread-state checks and safety validation via a dedicated
Submission::SystemCommand variant. Previously these flowed through
process_user_input() which blocked them during Processing/AwaitingApproval
/Completed states.
- Add /model [name] for runtime model switching with provider validation
- Add active_model_name()/set_model() to LlmProvider trait with RwLock
hot-swap in both NEAR AI providers
- Rewrite /help with aligned columns grouped by category
- Expand REPL tab-completion from 10 to 23 slash commands
- Remove REPL-local /help interception (now handled by agent)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Add per-tool execution timeouts, auto-create sandbox project dirs, serve built files
The sandbox e2e pipeline (agent -> container -> built website -> browsable URL)
was broken by three gaps: hardcoded 60s timeouts killed sandbox jobs that need
minutes, no auto-created project directory meant container output vanished, and
no HTTP route to browse the built files.
- Add `execution_timeout()` to the `Tool` trait (default 60s), replace all four
hardcoded `Duration::from_secs(60)` call sites (agent_loop, worker, scheduler,
worker/runtime) with the per-tool value
- Override to 660s in `RunInSandboxTool` (10 min polling + 60s buffer)
- Auto-create `~/.ironclaw/projects/{uuid}/` when no `project_dir` is specified,
so every sandbox job gets a persistent bind mount
- Include `project_dir` and `browse_url` in sandbox tool output JSON
- Add `/projects/{id}` and `/projects/{id}/{path}` static file serving routes
to the web gateway with path traversal protection and MIME type detection
- Add `mime_guess` dependency for content-type detection
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Apply cargo fmt to wizard.rs after merge
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Persist sandbox jobs in DB, fix web UI, unify job model
Sandbox container jobs were invisible to the web UI because they lived
only in ContainerJobManager's in-memory HashMap while the API queried
ContextManager. This persists them to the agent_jobs table and fixes
all six front-end bugs (empty job list, broken back button, empty
actions/thinking tabs, wrong files tab, stuck status, no persistence).
Key changes:
- V4 migration adds project_dir and user_id columns to agent_jobs
- Embedded migrations via refinery (no external CLI needed)
- SandboxJobRecord CRUD in Store with fire-and-forget DB writes
- Unified job_id: sandbox tool generates UUID, passes to ContainerJobManager
- Web API queries DB for sandbox jobs, merges with ContextManager direct jobs
- New endpoints: restart, project file list/read with path traversal protection
- Front-end: rebuild DOM on back navigation, sandbox-aware tabs, job cards in
chat stream, source badges, restart button for failed/interrupted jobs
- Gateway defaults to enabled, prints Web UI URL on startup
- Stale jobs marked "interrupted" on restart for visibility and restartability
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Secure in-chat auth: tokens never touch the LLM or chat history
Remove the token parameter from tool_auth so the LLM cannot pass raw
API keys. Add dedicated REST (POST /api/chat/auth-token) and WebSocket
(auth_token) endpoints that route tokens directly to ext_mgr.auth(),
completely bypassing the message pipeline, turns, history, and compaction.
Web UI shows an auth card (password input + OAuth button) when the agent
enters auth mode, submitted via the dedicated endpoint. CLI auth mode
interception is unchanged (already secure).
New StatusUpdate::AuthRequired/AuthCompleted variants propagate through
all channels (SSE, WebSocket, REPL, WASM).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Add Claude Code mode for sandbox jobs
Run Claude Code CLI inside Docker containers as an alternative to the
standard worker mode. The bridge spawns `claude -p` with stream-json
output, posts events to the orchestrator, and supports follow-up
prompts via `--resume`.
Key additions:
- `claude-bridge` CLI subcommand and ClaudeBridgeRuntime
- JobMode enum (Worker vs ClaudeCode) with per-mode container config
- Orchestrator endpoints for Claude events and prompt polling
- SSE event variants for real-time Claude Code streaming to frontend
- Claude Code sub-tab in web UI with terminal-style output and input bar
- Database migration for job_mode column and claude_code_events table
- ClaudeCodeConfig with env var support (CLAUDE_CODE_ENABLED, etc.)
- Mode parameter on run_in_sandbox tool schema
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Skip create_job tool when sandbox is enabled to prevent duplicate jobs
When sandbox mode is on, the LLM would call create_job (creating a
pending "direct" entry) then run_in_sandbox (creating a second "sandbox"
entry), producing two jobs in the list for a single user request.
Now register_job_tools() skips create_job when sandbox is enabled since
run_in_sandbox already creates tracked jobs. Also improved the
run_in_sandbox description to guide the LLM to use it directly and to
mention wait=false for async execution.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Web gateway UI quality-of-life improvements
Phase 1: Send button disabled state to prevent double-sends, copy button
on code blocks, confirm() guards on destructive actions, SSE-driven job
list auto-refresh, log filters re-applied on tab switch, jobEvents memory
leak fix (cap at 500, cleanup after 60s).
Phase 2: Toast notification system replacing chat-based system messages,
memory search highlighting with centered snippets, keyboard shortcuts
(Ctrl+1-5 tabs, Ctrl+K focus, Ctrl+N new thread, Escape close/blur),
activity tab toolbar with event type filter and auto-scroll toggle.
Phase 3: Thread sidebar with load/switch/create, thread_id passed with
messages, collapsible to hamburger. Memory inline editing with textarea,
Save/Cancel, POST to /api/memory/write.
Phase 4: Gateway status popover on hover (polls every 30s), extension
install form (name/URL/kind), markdown rendering in memory viewer for
.md files, mobile responsive layout at 768px breakpoint.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Add routines system, remove non-sandbox job mode from web UI
Routines: scheduled & reactive job system with cron and event triggers,
lightweight (single LLM call) and full-job execution modes, guardrails
(cooldown, max concurrent, dedup), and LLM-facing tools for CRUD.
Web UI: remove ContextManager-backed "direct" job mode entirely. Jobs
are now exclusively sandbox-backed (DB + container). Simplify job detail
response, drop dead types (ActionInfo, MessageInfo, MessageToolCallInfo),
fix Browse Files CSS loading (trailing-slash redirect), fix Activity tab
event rendering.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Re-enable chat input on agent completion, auto-auth on tool_activate, recover tool calls from content XML
Three fixes:
1. Chat input stays disabled after agent finishes: the "Done" status
SSE event now calls enableChatInput() as a safety net when the
response event is empty or lost. Same for auth_completed and
cancelAuth().
2. tool_activate never triggers auth: when activation fails due to
missing authentication, it now auto-initiates the auth flow
(same pattern as the web API handler). detect_auth_awaiting()
also matches tool_activate results now.
3. Models like GLM-4.7 emit tool calls as XML tags in content
(<tool_call>tool_list</tool_call>) instead of using the structured
tool_calls array. recover_tool_calls_from_content() extracts and
validates these before falling back to plain text.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Add routines web UI tab, update docs for sandbox-jobs branch
Add full routines management to the web gateway (list, detail, trigger,
toggle, delete) with 7 new API endpoints, response types, and frontend
(HTML, JS, CSS). Update FEATURE_PARITY.md (~23 rows), CLAUDE.md (new
subsystems, config, TODOs), and README.md (architecture diagram,
features, components, fix onboard command).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Bind Telegram bot to owner account during setup
Without owner binding, anyone who discovers the bot can send it messages.
The setup wizard now prompts the user to message their bot, captures their
Telegram user ID via getUpdates, and persists it as telegram_owner_id in
settings. On startup, the owner_id is injected into the WASM channel config
so the existing owner restriction logic drops messages from non-owners.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Move settings from disk to PostgreSQL database
Settings previously lived in three JSON files on disk (settings.json,
mcp-servers.json, session.json). This made them inaccessible from the
web UI and caused redundant disk reads (Settings::load() called 8+
times during startup).
Now all settings live in a `settings` table (user_id + key -> JSONB)
with only 4 bootstrap fields remaining on disk (database_url, pool
size, secrets key source, onboard_completed) since they're needed
before the DB connection exists.
- Add V8 migration for settings table
- Add BootstrapConfig (thin disk file) and Settings DB round-trip
- Add Store CRUD methods for settings (get/set/delete/list/bulk)
- Refactor Config to load from DB (env > DB > default cascade)
- Add SessionManager DB persistence for session tokens
- Add DB-backed MCP server config load/save functions
- Add 6 settings web API endpoints (list/get/set/delete/export/import)
- Add one-time disk-to-DB migration on first boot
- Make CLI config commands async with DB access (disk fallback)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Seed workspace on boot, fix gateway duplicate logs and URL auto-auth
- Add Workspace::seed_if_empty() to create core identity files (README,
MEMORY, IDENTITY, SOUL, AGENTS, USER, HEARTBEAT) when missing, called
on every boot without overwriting existing user edits
- Remove duplicate gateway log lines from web/mod.rs (main.rs has the
useful clickable ?token= URL)
- Auto-authenticate from ?token= URL parameter in the web UI and strip
the token from the address bar after successful auth
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Harden sandbox security (path traversal + orchestrator auth)
Two vulnerabilities fixed:
1. project_dir path traversal: The create_job tool let the LLM specify
arbitrary host paths for Docker bind mounts. Removed project_dir from
the tool schema entirely, and added canonicalization + prefix validation
at both resolve_project_dir() and the job_manager bind mount point.
2. Orchestrator API auth bypass: worker_auth_middleware was defined but
never applied. Each handler manually called validate_token(), so any
new endpoint that forgot would be publicly accessible. Applied the
middleware as route_layer on all /worker/ routes, removed manual auth
from all 7 handlers. Bind to 127.0.0.1 on macOS/Windows (Linux keeps
0.0.0.0 since containers reach host via docker bridge, not loopback).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Rework gateway chat with pinned assistant, pagination, and NEAR AI response chaining
Implements the 4-phase plan for overhauling the web gateway chat:
- Phase 1: Pinned "Assistant" thread at top of sidebar, regular threads below
- Phase 2: Cursor-based history pagination with infinite scroll
- Phase 3: NEAR AI previous_response_id chaining (delta-only messages),
with fallback to full history on chain errors, and DB persistence of
chain state across restarts
- Phase 4: SSE thread isolation (events filtered by thread_id)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Add per-request HTTP timeout to WASM host, redact credentials in errors
Three fixes for WASM channel reliability:
1. Per-request timeout: Add optional timeout-ms parameter to http-request
in both channel and tool WIT interfaces. Telegram long-poll now specifies
35s (outliving the 30s server-side hold), while regular API calls use
the 30s default. Fixes the triple-30s timeout race that caused polling
failures.
2. Credential redaction: reqwest::Error includes the full URL (with injected
bot tokens) in its Display output. Scrub credential values from error
messages before logging or returning to WASM.
3. Webhook route registration: Remove tunnel URL gate so webhook routes are
always available when webhook channels exist, not only when TUNNEL_URL
is configured.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore: Fix clippy warnings in WASM tools and channels
- slack channel: allow dead_code on signing_secret_name (forward compat field)
- gmail tool: use div_ceil() instead of manual (n+2)/3
- google-calendar tool: extract CreateEventParams/UpdateEventParams structs
to fix too-many-arguments warnings
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Fix approval flow
* fix: Rebuild bundled telegram.wasm with updated WIT interface
The bundled WASM binary must match the host's WIT definition.
Previous binary was compiled against the old 4-arg http-request;
this rebuild includes the new timeout-ms parameter.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: Load WASM channels from disk instead of bundling in binary
Remove include_bytes! embedding of telegram.wasm. Channels are now
loaded from their build output directories (channels-src/<name>/target/)
during onboarding, then from ~/.ironclaw/channels/ at runtime.
- bundled.rs: locate_channel_artifacts() finds WASM + capabilities from
build output; IRONCLAW_CHANNELS_SRC env var overrides the default path
- available_channel_names(): only lists channels with build artifacts
- bundled_channel_names(): lists all known channels (manifest)
- Setup wizard uses available_channel_names() to offer installable channels
- Add *.wasm to .gitignore, remove tracked telegram.wasm
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Persist gateway auth token, fix thread hydration race, polish auth screen
Three web gateway UX fixes:
1. Token persistence: Store auth token in sessionStorage so refreshing
the page doesn't force re-authentication. Hide the auth screen
immediately when a saved token exists to prevent flash.
2. Thread hydration: Remove the !msgs.is_empty() bail-out in
maybe_hydrate_thread so that even brand-new (empty) assistant threads
get hydrated with their correct DB UUID. Previously resolve_thread
would mint a fresh UUID, causing messages to land in the wrong
conversation and duplicate threads to appear.
3. Auth screen: Redesign as a centered card with brand, tagline, labeled
input, and hint text.
Also adds 34 new tests covering session/thread lifecycle, thread
resolution isolation (user, channel, external ID), hydration edge cases,
serialization round-trips, approval flows, and stale mapping recovery.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: Use bindgen! for WASM tool wrapper, add dev tool loading
Three changes:
1. Rewrite src/tools/wasm/wrapper.rs to use wasmtime::component::bindgen!
instead of manual linker.root().func_wrap(). This fixes the
"component imports instance 'near:agent/host', but a matching
implementation was not found in the linker" error. All 6 host functions
(log, now-millis, workspace-read, http-request, secret-exists,
tool-invoke) are now properly registered under the near:agent/host
namespace. Also adds WASI support, credential injection, and leak
detection for HTTP requests made by WASM tools.
2. Add dev tool loading to src/tools/wasm/loader.rs. During startup, the
loader now also scans tools-src/*/target/wasm32-wasip2/release/ for
build artifacts that are newer than installed copies. This means during
development you just rebuild the WASM and restart the host; no manual
copy step needed. Set IRONCLAW_TOOLS_SRC to override the source dir.
3. Wire up load_dev_tools() in main.rs alongside the existing
load_from_dir() call.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: Wire main startup and CLI to use DB-backed settings
main.rs now reloads Config from the database after connecting,
attaches the store to the session manager for dual-write tokens,
and loads MCP servers from DB instead of disk. ExtensionManager
and MCP CLI commands use DB when available with disk fallback.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* 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]>
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]>
Enables connecting to official MCP servers (like Notion) instead of
building custom WASM tools. Uses OAuth 2.1 with PKCE and supports
Dynamic Client Registration for zero-config authentication.
Key features:
- OAuth 2.1 flow with PKCE for secure browser-based auth
- Dynamic Client Registration (DCR) for servers without pre-configured clients
- Automatic token refresh on 401 responses
- Session management with Mcp-Session-Id headers
- SSE streaming response handling
New CLI commands:
- `mcp add <name> <url>` - Add an MCP server
- `mcp remove <name>` - Remove an MCP server
- `mcp list` - List configured servers
- `mcp auth <name>` - Authenticate with a server
- `mcp test <name>` - Test connection
Also removes the Notion WASM tool example since it's superseded by the
Notion MCP server which provides 13 official tools.
Co-Authored-By: Claude Opus 4.5 <[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]>
Implements a general-purpose Docker sandbox (inspired by Codex) that provides:
- Container isolation for shell commands with ephemeral containers
- HTTP proxy for network access control with domain allowlist
- Credential injection by proxy (secrets never enter containers)
- Three security policies: ReadOnly, WorkspaceWrite, FullAccess
- Resource limits (memory, CPU, timeout enforcement)
Key components:
- SandboxManager: Main entry point coordinating proxy and containers
- NetworkProxy: HTTP proxy validating requests and injecting credentials
- ContainerRunner: Docker lifecycle management via bollard
- DomainAllowlist: Pattern matching for allowed network destinations
The ShellTool now routes commands through the sandbox when enabled,
with automatic fallback to direct execution if Docker is unavailable.
Configuration via SANDBOX_ENABLED, SANDBOX_POLICY, SANDBOX_TIMEOUT_SECS,
SANDBOX_MEMORY_LIMIT_MB, SANDBOX_IMAGE, SANDBOX_EXTRA_DOMAINS env vars.
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]>
Introduces a new CLI module with subcommands for managing WASM tools:
- `tool install`: Build and install tools from source or .wasm files
- `tool list`: List installed tools with optional verbose output
- `tool remove`: Remove installed tools
- `tool info`: Show detailed tool information including capabilities
The install command supports building from Cargo source directories using
cargo-component, or installing pre-compiled .wasm files directly. It
auto-detects capabilities JSON sidecar files and validates them before
installation.
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]>
Extends the WASM sandbox with HTTP API capabilities, secrets management,
tool aliasing, and leak detection. Key security principle: WASM never
sees credentials, injection happens at host boundary.
New modules:
- secrets: AES-256-GCM encrypted storage with HKDF key derivation
- leak_detector: Aho-Corasick + regex pattern matching for secret exfiltration
- capabilities: Extended capability system (HTTP, ToolInvoke, Secrets)
- allowlist: HTTP endpoint validation with glob patterns
- credential_injector: Host-boundary credential injection
- rate_limiter: Sliding window per-tool rate limiting
- storage: WASM binary storage with BLAKE3 integrity verification
Leak detection happens at two points:
1. Before HTTP request (prevents exfiltration via URL/headers/body)
2. After response (prevents exposure in outputs returned to WASM)
Co-Authored-By: Claude Opus 4.5 <[email protected]>