* fix(signal): send approval prompts to users
The Signal channel was not handling StatusUpdate::ApprovalNeeded,
causing approval requests to be silently ignored and users to
never see approval prompts.
This adds proper handling of ApprovalNeeded status that sends
a formatted message to the user with:
- Tool name and description
- Parameters (formatted as JSON)
- Request ID for reference
- Instructions on how to approve/deny/always-approve
The message uses Signal's markdown-style formatting for better
readability on mobile devices.
* feat(signal): add missing StatusUpdate handlers
Add handling for all StatusUpdate variants in Signal channel,
bringing it on par with Telegram's implementation:
- ToolStarted: Shows spinner icon when tool execution begins
- ToolCompleted: Shows checkmark/X based on success/failure
- JobStarted: Shows sandbox job start with ID and URL
- AuthRequired: Shows auth prompt with instructions and URLs
- AuthCompleted: Shows auth success/failure with optional message
This ensures Signal status feedback users receive full during
tool execution, approvals, and authentication flows, matching
the experience of Telegram and other channels.
fix(signal): address clippy warnings and improve error handling
- Collapse nested if statements into let-chains
- Fix needless borrow on Status message
- Extract send_status_message helper to reduce duplication
- Add warning logs for failed message sends
* fix(signal): suppress 'Done' status messages to user
* feat(signal): debug mode parity with REPL
- Add debug_mode to SignalChannel toggled via /debug command
- Gate ToolResult, ToolStarted, ToolCompleted behind debug mode
- Add tests: debug_mode_disabled_by_default, debug_mode_toggle, debug_mode_persists_across_toggles
* feat: add OpenRouter preset to setup wizard
Add OpenRouter as a top-level provider option in the onboarding wizard
(Step 3). Selecting it pre-fills the base URL (https://openrouter.ai/api/v1)
and prompts for an API key, avoiding manual URL entry. Under the hood it
uses the existing openai_compatible backend.
Inlines the key collection flow (rather than delegating to
setup_api_key_provider) so success messages consistently say "OpenRouter"
instead of "openai_compatible", including the early-return env-key path.
Closes#178
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address serrrfirat review comments on OpenRouter wizard preset
- Re-run path now recognizes OpenRouter: display shows "OpenRouter"
and keep-current routes to setup_openrouter() when base URL contains
openrouter.ai
- Refactor setup_openrouter() to delegate to setup_api_key_provider()
with a display_name override, eliminating ~40 lines of duplication
- Update README: remove false claim about model fetching from
OpenRouter API, add footnote explaining shared secret/env var
between OpenRouter and OpenAI-compatible
- Fix pre-existing clippy warning in settings.rs (field_reassign_with_default)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
When installing the Telegram WASM channel via the web UI, a name collision
between registry/tools/telegram.json and registry/channels/telegram.json
caused the tool entry to win, installing to ~/.ironclaw/tools/ instead of
~/.ironclaw/channels/. This made activation fail with "WASM runtime not
available".
- Add `get_with_kind()` to ExtensionRegistry for kind-aware lookup
- Use `kind_hint` parameter in `install()` to resolve collisions
- Rename tool entries to avoid future collisions: telegram → telegram-mtproto,
slack → slack-tool
- Fix `_bundles.json` stale reference (tools/slack → tools/slack-tool)
- Fix `cache_discovered()` to deduplicate by (name, kind) consistently
- Add path traversal validation to install/activate/remove entry points
- Add tests for kind-aware lookup, discovery cache, and bundle resolution
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
* feat(channels): add native Signal channel via signal-cli HTTP daemon
Implement a native Rust Signal channel that connects to a running
signal-cli daemon's HTTP endpoint, enabling Signal messaging without
WASM overhead.
Architecture:
- SSE listener at /api/v1/events for receiving messages with automatic
reconnection and exponential backoff
- JSON-RPC client at /api/v1/rpc for sending messages and typing
indicators
- Reply target tracking via Arc<RwLock<HashMap>> to route responses
back to the correct DM or group conversation
Features:
- User allowlisting supporting E.164 phone numbers, bare UUIDs, and
uuid:-prefixed identifiers (matching OpenClaw's format)
- Group allowlisting with wildcard (*) support
- Configurable story and attachment-only message filtering
- Health check via signal-cli /api/v1/check
- Broadcast support to all tracked reply targets
Configuration via environment variables:
- SIGNAL_HTTP_URL, SIGNAL_ACCOUNT (required)
- SIGNAL_ALLOWED_USERS, SIGNAL_ALLOWED_GROUPS
- SIGNAL_IGNORE_ATTACHMENTS (default: false)
- SIGNAL_IGNORE_STORIES (default: true)
Includes unit tests covering allowlist logic, envelope parsing,
recipient targeting, SSE deserialization, and edge cases.
* refactor(signal): remove expect|unwrap calls
- Change SignalChannel::new to return Result<Self, ChannelError>
- Replace .expect() on reqwest client build with proper error handling
- Replace .expect() on NonZeroUsize with compile-time const using unsafe new_unchecked
- Propagate errors through test helpers to avoid unwraps in tests
* fix(signal): prevent OOM from chunked response without Content-Length
Use bytes_stream() to check response size during download rather than
buffering entire body first. This closes the OOM vector where a
malicious signal-cli daemon could send unbounded chunked data.
* fix(signal): align is_e164 minimum digits with setup wizard
Both now require 7-15 digits after '+', preventing environment
variable bypass of the stricter onboarding validation.
* refactor(signal): extract from_parts constructor
Extract SignalChannel::from_parts() used by both new() and
sse_listener() to ensure consistent object construction.
* chore: remove redundant unused var
* refactor(signal): rename allowed_users to allow_from and add dm_policy/group_policy
- Rename allowed_users -> allow_from for consistency with other channels
- Rename allowed_groups -> allow_from_groups
- Add dm_policy field: 'open', 'allowlist', or 'pairing' (default: 'pairing')
- Add group_policy field: 'allowlist', 'open', or 'disabled' (default: 'allowlist')
- Add group_allow_from field that inherits from allow_from if empty
- Implement dm_policy and group_policy logic in message processing
- Add environment variable resolution: SIGNAL_ALLOW_FROM, SIGNAL_ALLOW_FROM_GROUPS,
SIGNAL_DM_POLICY, SIGNAL_GROUP_POLICY, SIGNAL_GROUP_ALLOW_FROM
- Add setup wizard prompts for new policy options
- Note: full pairing flow (PairingStore integration) marked as pending for future PR
* feat(signal): implement DM pairing workflow for unapproved senders
- Add PairingStore integration to check approved senders
- Handle pairing requests for unknown senders with dm_policy=pairing
- Send pairing reply message with approval instructions
- Update FEATURE_PARITY.md to reflect DM pairing support
* chore(ci): fix clippy warnings
* fix: make onboarding installs prefer release artifacts with source fallback
* fix: harden extension fallback errors and surface setup warnings
* fix: validate registry artifacts and harden fallback errors
* fix: address review feedback on installer fallback
- Add upfront validate_manifest_install_inputs() in
install_with_source_fallback so bad manifests fail fast without
relying on inner methods to catch them
- Document ALLOWED_ARTIFACT_HOSTS as GitHub-only by design
- Document intentional url omission from DownloadFailed Display
- Add channel manifest validation tests (wrong prefix rejected,
correct prefix accepted)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: require SHA256 checksum for artifact downloads
Reject artifact installs when the manifest has sha256: null instead of
warning and proceeding. This prevents installing unverified pre-built
binaries during onboarding. The check runs before downloading to avoid
wasting bandwidth.
Since InvalidManifest blocks source fallback, manifests with URLs but
no checksums will hard-fail rather than silently falling back to source
build — forcing the manifest to be fixed.
The release CI already computes SHA256 for each bundle; the manifests
just need to be populated with the actual values.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: enforce SHA256 checksums and auto-patch manifests in CI
- Fix cargo fmt on SHA256 check code
- Reorder release CI: build WASM extensions before binary so manifests
can be patched with computed SHA256 before build.rs embeds them
- Add "Patch manifests with WASM checksums" step in build-local-artifacts
that reads checksums.txt and updates registry JSON files before building
- Add update-registry-checksums job that commits patched manifests back
to main after release, keeping the repo in sync with released artifacts
This closes the integrity gap where all manifests had sha256: null and
artifact downloads were unverified. The binary now embeds correct SHA256
values and the installer hard-rejects null checksums.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Bowen Wang <[email protected]>
* fix: copy missing files in Dockerfile to fix build
The Docker build failed because Cargo.toml references files that were
not copied into the builder stage:
1. tests/html_to_markdown.rs — declared as [[test]] in Cargo.toml,
Cargo validates the path exists even when only building a binary.
2. build.rs — auto-discovered build script that embeds registry
manifests at compile time via include_str!(env!("OUT_DIR")).
3. registry/ — contains extension manifests read by build.rs to
generate the embedded catalog.
Added COPY directives for build.rs, tests/, and registry/.
Fixesnearai/ironclaw#320
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Address serrrfirat review feedback on WASM channel omission
- Add Dockerfile comment documenting that channels-src/ is intentionally
omitted since WASM compilation requires wasm32-wasip2 and wasm-tools
which are not installed in the builder stage
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Add WASM channel compilation support to Docker build
- Copy channels-src/ into builder stage for Telegram/Slack/Discord/WhatsApp
- Install wasm32-wasip2 target and wasm-tools so build.rs can compile
WASM channel components instead of silently skipping them
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat: add Docker detection module with platform guidance
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: add Docker sandbox step to setup wizard
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: show Docker status in boot screen
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: check Docker availability at startup
When SANDBOX_ENABLED=true, proactively detect whether Docker is
installed and running before creating the ContainerJobManager.
If Docker is unavailable, log a warning with platform-specific
guidance and disable the sandbox for the session.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: enable sandbox by default, improve wizard explanation, document detection limits
- SandboxConfig defaults to enabled=true (startup check disables
gracefully if Docker is unavailable)
- Wizard step explains why Docker matters: isolation for LLM-generated
code vs running directly on the host
- Document detection confidence per platform in detect.rs module docs:
high on macOS/Linux, medium on Windows (named pipe edge cases)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: cargo fmt + update test_builder_defaults for enabled-by-default
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: deduplicate wizard Docker status handling per review
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: fix skills system - enable by default, fix registry connectivity and install
- Enable skills system by default (SKILLS_ENABLED no longer required)
- Bypass Vercel TLS fingerprint blocking by pointing DEFAULT_REGISTRY_URL
directly at the Convex backend (wry-manatee-359.convex.site)
- Handle ZIP archives from ClawHub download API - the registry returns
ZIP files containing SKILL.md, not raw text. Uses flate2 (existing dep)
to extract SKILL.md from the archive.
- Surface catalog search errors in the UI with a yellow warning banner
instead of silently returning empty results
- Handle both {"results":[...]} envelope and bare [...] array JSON formats
from the search API
- Add ClawHub links and metadata to search result cards (clickable skill
names linking to clawhub.ai, relevance score, "updated X ago" recency)
- Fix 3 pre-existing clippy warnings in tests/html_to_markdown.rs
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address security review feedback on ZIP extraction and SSRF
- Cap download size to 10 MB before reading response body
- Guard against ZIP bombs: cap uncompressed_size at 1 MB, wrap
DeflateDecoder with .take() read limit
- Use checked_add for ZIP header offset arithmetic to prevent overflow
- Remove .unwrap() on try_into() -- use direct array construction
- Handle IPv4-mapped IPv6 addresses (::ffff:192.168.x.x) in SSRF checks
- Don't leak internal registry URLs in user-facing catalog_error messages
- Fix non-ASCII panic in catalog response debug logging (use .get() instead
of byte slicing)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: add /skills command and enrich search results with ClawHub metadata
- Parse /skills and /skills search <query> as SystemCommands in submission.rs
- Add skill_catalog to AgentDeps and wire it through main.rs
- Handle "skills" command in commands.rs: list installed skills and search ClawHub
- Add /skills and /skills search <q> entries to /help output
- Add SkillDetail, SkillStats, SkillOwner structs to catalog.rs
- Add fetch_skill_detail() calling GET /api/v1/skills/{slug} on Convex backend
- Add enrich_search_results() to fetch stars/downloads/owner for top 5 results in parallel
- Fix SkillDetailResponse wrapper struct to match actual API shape: {"skill":{...},"owner":{...}}
- Surface stars, downloads, owner in web UI skill search cards (app.js)
- Surface enriched data in skills web handler and skill_search tool output
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* fix: cargo fmt after merge conflict resolution
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* fix: separate installed_skills dir for correct trust on restart, remove duplicate handlers
Trust level bug: skills installed from ClawHub were written to user_dir
(~/.ironclaw/skills/) which is discovered as Trusted on restart. Now installs
go to ~/.ironclaw/installed_skills/ which is discovered as Installed, matching
the documented skill directory layout.
Changes:
- SkillsConfig: add installed_dir field (SKILLS_INSTALLED_DIR env var,
default ~/.ironclaw/installed_skills/)
- SkillRegistry: add with_installed_dir() builder, installed_dir()/
install_target_dir() accessors, and discover installed_dir with
SkillTrust::Installed in discover_all()
- All install paths (web handler, skill tool) use install_target_dir()
instead of user_dir() so new installs land in the correct directory
- 3 new registry tests: test_installed_dir_uses_installed_trust,
test_install_target_dir_prefers_installed_dir,
test_user_dir_stays_trusted_with_installed_dir
Duplicate handler cleanup: handlers/skills.rs was the canonical implementation
but the handlers module was never compiled (not declared in web/mod.rs), so
server.rs had its own duplicate inline definitions that the router used.
Wire up the handlers module, delete the 260-line duplicate in server.rs, and
have server.rs import skills handlers from handlers::skills. Fix pre-existing
compile error in handlers/extensions.rs (missing needs_setup field). Add
#[allow(dead_code)] on not-yet-migrated handler modules to suppress warnings.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: probe more Docker socket paths on macOS
Docker Desktop 4.13+ (stabilised in 4.18) no longer creates the
/var/run/docker.sock symlink by default. The API socket lives at
~/.docker/run/docker.sock, which bollard's connect_with_local_defaults()
does not try.
Add a fallback probe list covering the common macOS container runtimes:
- ~/.docker/run/docker.sock — Docker Desktop 4.13+
- ~/.colima/default/docker.sock — Colima
- ~/.rd/docker.sock — Rancher Desktop
Remove the bogus ~/.docker/desktop/docker.sock path that was added
previously; it is not an API socket on any known Docker installation.
Fixes the false-negative "Docker is installed but not running" warning
reported by Illia on macOS with Docker Desktop 4.18+.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* Harden Docker detection for rootless Linux and Windows fallback
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: fall back to build-from-source when extension download fails
Extension manifests hardcode GitHub release URLs for WASM artifacts,
but these artifacts are not yet published to any release. This causes
all WASM extension installs to fail with HTTP 404.
Add a fallback_source field to RegistryEntry so that when the primary
WasmDownload source fails (e.g., 404), the installer automatically
falls back to WasmBuildable (build from source). The manifest
conversion now populates this fallback whenever a download URL is set.
Fixesnearai/ironclaw#298
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Address Copilot/Gemini review feedback
- Skip fallback for AlreadyInstalled errors (Gemini)
- Include both primary and fallback errors in combined message (Copilot)
- Fix comment to match broader behavior (any error, not just download) (Copilot)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Address serrrfirat review feedback
- Forward AlreadyInstalled from fallback directly instead of wrapping
in ExtensionError::Other (defensive, prevents misleading error message)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Add unit tests for fallback install logic
Extract fallback_decision() and combine_install_errors() from
install_from_entry() to enable direct unit testing without requiring
a full ExtensionManager setup.
Tests cover:
- Primary success returns directly (no fallback attempted)
- AlreadyInstalled short-circuits (no fallback attempted)
- Download failure with fallback available triggers fallback
- Error without fallback source returns primary error
- Both-fail produces combined error with both messages
- AlreadyInstalled from fallback is forwarded directly
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: firat.sertgoz <[email protected]>
* fix: auto-compact and retry on ContextLengthExceeded in agentic loop
When the LLM returns a context-length-exceeded error mid-turn, the
dispatcher now automatically compacts the conversation history and
retries once instead of propagating the raw error to the user.
The compaction keeps all system messages (system prompt, skill context),
the last user message, and all subsequent messages (current turn's tool
calls and results), dropping older conversation history. A note is
inserted to inform the LLM that earlier context was dropped.
If the retry also fails, the original error is returned.
Fixesnearai/ironclaw#260
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Address Gemini/Copilot review feedback
- Fix system message duplication: only collect system messages before the
last User message to avoid duplicating nudges in the tail slice (Gemini + Copilot)
- Only add compaction note when earlier history is actually dropped (Copilot)
- Propagate actual retry error instead of masking with original (Copilot)
- Fix else branch to preserve system messages when no User messages exist
- Add test for nudge-after-user deduplication
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: make Telegram status prompts reliable
Approval and auth prompts could be missed when polling or reply-context sends failed, leaving users stuck in waiting states. This adds explicit status mapping and retries, keeps typing active through intermediate work while suppressing noisy tool telemetry, and adds regression tests plus CI coverage for the Telegram channel crate.
* fix: normalize terminal status handling
Terminal status strings from the agent loop can vary in casing and formatting, which could leak internal status lines to Telegram. This normalizes Done/Interrupted mapping and filters terminal status text consistently to keep chat UX clean while preserving actionable prompts.
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: persist user message at turn start before agentic loop
Split persist_turn into persist_user_message + persist_assistant_response.
The user message is now written to DB immediately after thread.start_turn(),
before the agentic loop runs. This ensures the message survives process
crashes mid-response. The assistant response is persisted only on completion.
Updated all 6 call sites in thread_ops.rs (success, error, approval
success/error, rejection, and auth intercept paths).
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix: document persist_assistant_response dependency on persist_user_message
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* style: apply cargo fmt
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix: re-ensure conversation in persist_assistant_response
Add ensure_conversation call and user_id parameter to
persist_assistant_response so assistant replies are still persisted
even if persist_user_message failed transiently at turn start.
Addresses PR review feedback from @ilblackdragon.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
* feat: add web UI test skill for Chrome extension testing
Add a SKILL.md checklist for manually testing the IronClaw web gateway
UI using the Claude for Chrome browser extension. Covers connection,
chat, skills tab (search, install by search, install by URL, remove),
and smoke tests for other tabs.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: use placeholder token and correct cleanup path per review
- Replace hardcoded test123 token with <your-token> placeholder
- Fix cleanup path: ~/.ironclaw/installed_skills/ (not skills/)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: block send until thread is selected
Prevents messages from ending up in orphan threads when user sends
while currentThreadId is null during page load.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix: guard enableChatInput against null thread + add user feedback
Prevents SSE events from re-enabling input before a thread is selected.
Adds status message when user tries to send without a thread.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
When SSE auto-reconnects after a server restart, the chat now
re-syncs from the database so no messages are lost.
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
* feat: implement FullJob routine mode with scheduler dispatch
FullJob routines previously fell back to lightweight mode (single LLM call,
no tools) with a warning. This wires them to the existing Scheduler/Worker
infrastructure so they dispatch real jobs with full tool access.
Fire-and-forget model: the routine creates a job via ContextManager, schedules
it, links the routine_run to the job_id, and completes immediately. The job
runs independently with full tool access.
- Add RoutineError::JobDispatchFailed variant
- Add RoutineStore::link_routine_run_to_job (PostgreSQL + libSQL)
- Add execute_full_job() in routine_engine with context_manager/scheduler
- Wire context_manager + scheduler into RoutineEngine from agent_loop
- Fix pre-existing clippy warnings in tests/html_to_markdown.rs
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: persist job to DB before scheduling in execute_full_job
The worker emits job_actions and llm_calls rows that reference agent_jobs
via foreign key. Without persisting the job first, those inserts can fail.
Match the pattern from commands.rs: fetch JobContext, save_job(), then schedule.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: consolidate job dispatch into Scheduler::dispatch_job and wire max_iterations
Move the create + persist + schedule sequence into a single
Scheduler::dispatch_job() method so callers (commands.rs, routine_engine.rs)
don't duplicate the logic. FullJob routines now pass max_iterations via job
metadata, and the worker reads it (defaulting to 50 if unset).
Also removes the context_manager field from RoutineEngine since dispatch_job
handles everything internally.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: clamp max_iterations to 500 and log category update failures
Address PR review feedback:
- worker.rs: clamp max_iterations from metadata to MAX_WORKER_ITERATIONS (500)
to prevent unbounded LLM token usage from malicious/buggy configs
- commands.rs: log warning on category update failure instead of silently
discarding the error
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: persist worker events to DB and fix activity tab rendering
In-process Worker (used by Scheduler::dispatch_job) now persists events
via save_job_event at key execution points: plan creation, LLM
responses, tool_use, tool_result, and job completion/failure/stuck.
Event data shapes match the container worker format so the gateway
activity tab renders them correctly.
Frontend: tool_result errors now show a red X icon with danger styling
instead of a silent empty output. The result event falls back to the
error field when message is absent.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* refactor: unify WASM artifact resolution into registry/artifacts.rs
Consolidate duplicated WASM find/build/install logic from 5+ files into
a single src/registry/artifacts.rs module. This fixes two bugs:
- registry/installer.rs now respects CARGO_TARGET_DIR (was hardcoded)
- channels/wasm/bundled.rs now searches all WASM triples (was wasip2 only)
Also includes: extension manager hot-activation for WASM channels,
extension guidance in LLM prompts, channel manager hot-add support,
webhook router channel lookup, and minor cleanups.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: send approval prompts as messages on WASM channels (Telegram, Slack)
WASM channels mapped ApprovalNeeded status to a typing indicator,
so users on Telegram never saw tool approval prompts — the agent
got stuck in AwaitingApproval and all subsequent messages failed
with "Waiting for approval".
- Intercept ApprovalNeeded in WasmChannel::handle_status_update and
send the prompt as an actual message via call_on_respond, showing
tool name, description, parameters, and yes/no/always instructions
- Guard against empty LLM responses after clean_response() strips
reasoning_content think-tags (defense-in-depth for reasoning models)
- Add reasoning_content fallback to NearAiChatProvider::complete()
for consistency with complete_with_tools()
- Add debug logging when empty responses are suppressed
- Improve error logging for channel respond() failures
- Register WASM channel webhook routes before credential checks so
platforms don't deactivate webhook URLs with 404s
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR #297 review comments
- ChannelManager::add: use async write().await instead of try_write()
- resolve_target_dir: resolve relative CARGO_TARGET_DIR against crate_dir
- install_wasm_files: log warning on capabilities copy failure
- refresh_active_channel: load capabilities file for webhook secret name
- activate_wasm_channel: validate name against path traversal
- Fix cargo fmt formatting in nearai_chat.rs
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: wire up channel runtime for hot-activation and address PR review round 2
- Wire up set_channel_runtime() in main.rs so hot-activation actually works
(with_channel_runtime was never called — hot-activation was dead code)
- Change ExtensionManager channel runtime fields to RwLock<Option<...>>
interior mutability so set_channel_runtime(&self) works after Arc wrapping
- Fix artifact tests to use resolve_target_dir() instead of hardcoding
"target/" (breaks when CARGO_TARGET_DIR is set)
- Fix bundled.rs build hint: cargo component build (not cargo build --target)
- Fix wasm_artifact_path doc: binary_name should not include .wasm extension
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: use char-aware truncation to prevent UTF-8 panic in approval prompt
&s[..77] panics on multi-byte UTF-8 (CJK, emoji). Use s.chars().take(77)
for safe truncation at character boundaries.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: remove union type arrays from tool schemas for OpenAI compatibility
OpenAI rejects JSON Schema union types containing "array" without an
"items" subschema. The http tool's "body" and json tool's "data" params
used union types to accept any value. Replace with freeform (untyped)
schemas which OpenAI treats as accepting any JSON value.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: update schema tests to assert type is absent, fix missed json.rs test
- http.rs test: assert body has no "type" (not just has description)
- json.rs test: update to match the freeform schema change (was still
asserting type is present)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* refactor: simplify config resolution and consolidate main.rs init into AppBuilder
- Add parse_bool_env() and parse_string_env() helpers to eliminate repetitive
5-line optional_env/parse/map_err/unwrap_or boilerplate across 12 config files
- Add EmbeddingsConfig::create_provider() to centralize embeddings construction
(fixes hardcoded 1536 dimensions and missing Ollama provider in app.rs)
- Extract init_cli_tracing(), setup_wasm_channels(), start_tunnel(),
run_memory_command(), run_worker(), run_claude_bridge() from main.rs
- Replace ~600 lines of inline init in main.rs with AppBuilder::build_all()
- Expose catalog_entries from AppComponents for gateway registry entries
- Net reduction: ~738 lines across 15 files
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: propagate dev_loaded_tool_names from AppBuilder and add parse_option_env helper
Address PR review feedback:
- Capture dev_loaded_tool_names from WASM loading in init_extensions()
and expose via AppComponents so bootstrap_hooks receives the actual
dev tool names instead of an empty slice (fixes silent hook skip)
- Add parse_option_env<T>() helper for Option<T> config fields,
simplifying max_cost_per_day_cents and max_actions_per_hour in agent.rs
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: fetch real NEAR AI pricing and unify cost calculation path
CostGuard was independently looking up pricing via costs::model_cost(),
falling back to GPT-4o default rates when NEAR AI model names didn't
match the static table — causing ~3x cost overestimates in logs.
- Add pricing map to NearAiChatProvider that fetches real rates from
/v1/model/list at startup (background, non-blocking)
- Update cost_per_token() to check fetched pricing first, then static
table, then default
- Add cost_per_token parameter to CostGuard::record_llm_call() so the
dispatcher passes provider-sourced rates directly
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore: update default NEAR AI model to GLM-latest
Replace fireworks llama4-maverick-instruct-basic with zai-org/GLM-latest
as the default model in config and setup wizard.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: align wizard default model name with config
Change "zai/GLM-latest" to "zai-org/GLM-latest" in wizard.rs to match
the default in config/llm.rs.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
The Bundled variant and its local-artifacts fallback are superseded by the
embedded registry catalog which provides WasmDownload entries with GitHub
release URLs. The in-chat extension manager now always downloads channel
WASM binaries from releases, simplifying the install path.
The setup wizard retains its own local install_bundled_channel path for
dev builds where build artifacts exist on disk.
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: allow OAuth callback to work on remote servers via OAUTH_CALLBACK_HOST
Fixes#186.
The OAuth callback URL was hardcoded to `http://127.0.0.1:9876` in two
places (NEAR AI login and MCP server auth). On a remote server this URL
is unreachable from the user's browser, making authentication impossible.
Changes:
- Add `callback_host()` to `oauth_defaults` that reads `OAUTH_CALLBACK_HOST`
(default: `127.0.0.1`)
- Update `bind_callback_listener()` to bind to `0.0.0.0` when a non-loopback
host is configured, so the port is reachable from outside the machine
- Update `session.rs` and `mcp/auth.rs` to use `callback_host()` instead
of hardcoded `127.0.0.1` / `localhost`
Usage on a remote server:
export OAUTH_CALLBACK_HOST=<your-server-ip>
ironclaw login
* fix: address PR review comments for OAuth callback security
* fix: address serrrfirat review comments on PR #212
---------
Co-authored-by: firat.sertgoz <[email protected]>
Port Telegram's permission model (owner_id, dm_policy, allow_from, pairing codes)
to Discord, Slack, and WhatsApp WASM channels. Add web UI for configuration and
pairing approval. Fix extension registry issues preventing Discord install and
causing Slack activation to hit the wrong endpoint.
WASM channels:
- Discord: add DiscordConfig, permission checks, ephemeral pairing replies,
fix capabilities.json (header_name→name), downgrade wit-bindgen to 0.36
- Slack: expand SlackConfig with permission fields, add check_sender_permission
and send_pairing_reply via chat.postMessage
- WhatsApp: expand WhatsAppConfig with permission fields, add permission checks
and pairing reply via Cloud API
- Telegram: reformat capabilities.json, add setup.required_secrets
Extension system:
- Add Discord to KNOWN_CHANNELS in bundled.rs and to extension registry
- Rename "slack" MCP→"slack-mcp", "slack-channel"→"slack" to fix name collision
- Add ExtensionSource::Bundled variant handling in discovery.rs
- Add get_setup_schema/save_setup_secrets to ExtensionManager
- Add needs_setup field to InstalledExtension
Web gateway:
- Add GET/POST /api/extensions/{name}/setup for configuration modal
- Add GET /api/pairing/{channel} and POST /api/pairing/{channel}/approve
- Add configure modal UI (password fields, provided badges, auto-generate hints)
- Add pairing request UI on active WASM channel cards
- Show "Restart to activate" label instead of Activate button for WASM channels
Co-authored-by: Claude Opus 4.6 <[email protected]>
Prevent personal memory (MEMORY.md) from leaking into group chat contexts
by adding system_prompt_for_context(is_group_chat) to the workspace. Add
channel-specific formatting hints (Discord, Telegram, Slack, WhatsApp),
runtime metadata injection, group chat behavioral guidance with NO_REPLY
silent token, safety rules in the system prompt, tool call style guidance,
wrap_external_content() for untrusted data, and improved workspace seed
files with richer identity/soul/agent templates and heartbeat checklist.
Co-authored-by: Claude Opus 4.6 <[email protected]>
- Add docs/LLM_PROVIDERS.md with setup instructions for all supported providers
- Expand .env.example with Together AI and Fireworks AI example configs
- Add "Alternative LLM Providers" section to README with quickstart snippet
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: firat.sertgoz <[email protected]>
* feat: add HTML-to-Markdown conversion for web content
- Add readabilityrs for content extraction
- Add html-to-markdown for conversion
- Feature-gated behind html-markdown flag
- Integrates with HTTP tool response handling
- Includes comprehensive tests and examples
Closes#106
* Update comments for is_html_response helper and fix tests to not fail silently in certain instances
---------
Co-authored-by: Zach Frederick <[email protected]>
Co-authored-by: Illia Polosukhin <[email protected]>
* feat: embedded registry catalog and WASM bundle install pipeline
Embed registry manifests at compile time so the extension catalog is
available without network access. Add tar.gz bundle support for WASM
extension downloads (tools and channels), a /api/extensions/registry
endpoint, CI job to build and publish WASM bundles on release, and
ephemeral in-memory secrets fallback so the extension manager works
even without a persistent secrets store.
Key changes:
- build.rs: collect registry/*.json into embedded_catalog.json at compile time
- src/registry/embedded.rs + catalog.rs: load embedded or on-disk catalog
- src/extensions/manager.rs: download_and_install_wasm handles tar.gz bundles,
bare .wasm files, and separate capabilities downloads; wasm channel install
- src/channels/web/server.rs: /api/extensions/registry endpoint + no-cache headers
- src/app.rs: ephemeral InMemorySecretsStore fallback for extension manager
- registry/*.json: populate artifact download URLs for release bundles
- .github/workflows/release.yml: build-wasm-extensions CI job
- Simplified setup wizard and CLI registry commands
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review — archive hardening, decompression bomb guard, test fix
- Add 100 MB decompressed entry size cap to tar.gz extraction in both
manager.rs and installer.rs to prevent decompression bombs
- Add archive.set_preserve_permissions(false) and set_unpack_xattrs(false)
for defense-in-depth against malicious archives
- Fix test assertion logic in catalog.rs (|| → || with correct negation)
- Replace silent tar fallback in CI with explicit if/else for capabilities
- Add warning when installing without SHA256 verification
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: resolve clippy warning in settings.rs and enforce zero-warnings policy
Use struct initializer with ..Default::default() instead of field
reassignment. Update CLAUDE.md to codify zero clippy warnings policy —
all warnings must be fixed before committing, including pre-existing ones.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review round 2 — build reliability, caps validation, naming
- build.rs: emit per-file rerun-if-changed for reliable content tracking;
fix bundles fallback to match BundlesFile shape ({"bundles":{}})
- embedded.rs: parse catalog once via OnceLock instead of double-parsing
- manager.rs + installer.rs: add 1 MB size cap on capabilities_url downloads
with proper error surfacing
- secrets/store.rs: rename misleading `pub mod testing` to `pub mod in_memory`
- server.rs: track installed extensions by (name, kind) tuple to avoid
false positives across different extension kinds
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat: show token usage, cost tracker, and uptime in gateway status popover
The "Connected" hover popover in the web gateway now displays three
sections: connection info (SSE/WS counts, uptime), daily cost tracker
(spend + actions/hr), and per-model token usage (input/output counts
with cost per model). Also fixes the field name mismatch between the
backend response and JS rendering that prevented the popover from
showing correct data.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review — escape HTML in popover, add model_usage test
- Escape model name and cost strings with escapeHtml() before inserting
into innerHTML to prevent XSS via crafted model names
- Add test_model_usage_per_model_tracking test covering multi-model
token/cost accumulation in CostGuard
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Add LLM_EXTRA_HEADERS env var (format: Key:Value,Key2:Value2) to inject
custom HTTP headers into every request to OpenAI-compatible endpoints.
This enables OpenRouter attribution headers (HTTP-Referer, X-Title)
and other service-specific headers without code changes.
Closes#179
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Illia Polosukhin <[email protected]>
* fix: move Logs to status bar and fix chat history ordering after restart
Move the Logs tab out of the main tab bar and into the right-side status
area as a compact pill button next to "Connected". Remove it from the
Ctrl+1-N shortcut order (now Ctrl+1-5).
Fix chat message ordering in libSQL backend: datetime('now') has only
second precision, so back-to-back user+assistant inserts got identical
timestamps causing non-deterministic ORDER BY. Now passes explicit
millisecond-precision timestamps and uses rowid as tiebreaker for
existing data.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: separate WASM extensions from MCP servers on Extensions page
Reorganize the Extensions tab into 5 distinct sections: Installed
Extensions, Available WASM Extensions, Install WASM Extension (by
tar.gz URL), MCP Servers (with Add Custom form), and Registered Tools.
Registry entries are now filtered client-side by kind so WASM tools/
channels and MCP servers each have dedicated UI sections.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: adopt agent-market design language for web UI
Refresh the web gateway visual identity with a cleaner, modern aesthetic:
deeper blacks, green accent palette, DM Sans + IBM Plex Mono typography,
larger border-radii, glassmorphic navigation, refined hover effects,
pill badges, and green focus rings. CSS-only change plus Google Fonts.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Update src/channels/web/static/style.css
Co-authored-by: Copilot <[email protected]>
* Update src/channels/web/static/style.css
Co-authored-by: Copilot <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Copilot <[email protected]>
* feat: add smart routing provider for cost-optimized model selection
Route simple tasks (greetings, status checks, short questions) to a cheap
model (e.g. Haiku) and complex tasks (code generation, analysis) to the
primary model, reducing agent costs without sacrificing quality.
Activates automatically when NEARAI_CHEAP_MODEL is set. Cascade mode
retries uncertain cheap-model responses with the primary model.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: apply cargo fmt formatting
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: extract provider chain into shared build_provider_chain()
Consolidate the duplicated LLM provider chain construction from main.rs
and app.rs into a single build_provider_chain() function in llm/mod.rs.
This fixes the inconsistency where app.rs was missing retry wrapping
that main.rs had, and ensures both paths apply identical decorators:
retry → smart routing → failover → circuit breaker → cache.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review — uncertainty detection and clippy lint
- Remove false-positive short response (<20 chars) uncertainty check
that would escalate "Yes.", "42" etc. Now only empty responses and
explicit uncertainty phrases trigger cascade escalation.
- Add #[allow(clippy::type_complexity)] to build_provider_chain() to
fix CI clippy -D warnings failure.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Three high-impact changes eliminate most startup latency:
1. Enable wasmtime persistent compilation cache — call
cache_config_load_default() so compiled native code is serialized to
disk (~/.cache/wasmtime). Subsequent startups deserialize instead of
recompiling, dropping the WASM phase from ~13s to <1s.
2. Cache compiled Component in PreparedModule — store the compiled
wasmtime::component::Component directly instead of raw bytes.
Eliminates ~2.6s recompilation on every first tool/channel execution.
3. Move blocking housekeeping to background tasks — embedding backfill
(~1.3s of failing HTTP calls) and stale job cleanup are fire-and-forget
work that no longer blocks the critical startup path.
Also: deduplicate Workspace creation in main.rs (two identical instances
reduced to one), and replace leftover println! in session validation with
tracing calls.
Co-authored-by: Claude Opus 4.6 <[email protected]>
* refactor: consolidate tool approval into single param-aware method
Replace the two confusing approval methods (requires_approval() and
requires_approval_for()) with a single requires_approval(&self, params)
returning a 3-variant ApprovalRequirement enum (Never, UnlessAutoApproved,
Always). This enables param-aware approval decisions: HTTP calls without
auth headers now skip approval entirely, while authenticated requests
always require it. Shell tool merges its destructive-command detection
into the same method.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: add credential injection to built-in HTTP tool
Wire the WASM credential injection system into the built-in HTTP tool
so credentials are auto-injected at the boundary (zero-exposure model).
- Add SharedCredentialRegistry: thread-safe, append-only registry of
credential mappings populated by WASM tools at registration time
- Add credential_detect module with broad auth detection for headers
(12 exact + 5 substring matches), header values (7 auth scheme
prefixes), and URL query params (17 exact + 5 substring matches)
- HttpTool now accepts optional credential registry + secrets store,
auto-injects matching credentials in execute(), and uses broader
auth detection in requires_approval()
- ToolRegistry passes credential registry to HttpTool at startup and
populates it when WASM tools register
- Remove old hardcoded AUTH_HEADER_NAMES / has_auth_headers in favor
of the new params_contain_manual_credentials()
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR #274 review comments (query param injection, lock poisoning, visibility)
- Fix injected query params not being sent on outbound HTTP requests by
also calling .query() on the RequestBuilder alongside parsed_url mutation
- Recover from poisoned RwLock in SharedCredentialRegistry instead of
silently ignoring failures, with tracing::warn for visibility
- Narrow inject_credential and host_matches_pattern to pub(crate) to
avoid committing to them as stable public API
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Show a shield indicator in the tab bar when the instance is running
inside a TEE deployment. On hover, fetches and displays the TDX
attestation report (image digest, TLS cert fingerprint, report data,
VM config) from the management API.
Co-authored-by: Cursor <[email protected]>
Nginx buffers responses by default, breaking SSE connections that go
through a reverse proxy. Add X-Accel-Buffering: no header to chat and
log SSE handlers to match what compose-api and chat-api already do.
* feat: direct agentic loop for SWE-bench benchmarks
Replace the full Agent-based runner with a purpose-built agentic loop
that directly calls the LLM with tools. The old path routed through
SafetyLayer (which blocked SWE-bench prompts), dispatcher (capped at
10 iterations), approval flow (wasted iterations), and 20+ irrelevant
builtin tools (diluted the model's focus).
New architecture:
- AgenticLoop: LLM call -> tool execution -> repeat (up to 30 iters)
- Per-task tool scoping via BenchSuite::task_tools() with working dirs
- Suite-provided system prompts via BenchSuite::system_prompt()
- No safety layer, no approval flow, no sessions/threads overhead
- Configurable max_iterations in BenchConfig and TOML
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: apply --model CLI override to LLM provider
The --model flag was updating matrix entry labels but not the actual
LLM provider, so requests were still sent using the model from .env.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: configurable tool iterations and auto-approve for benchmarks
Add max_tool_iterations and auto_approve_tools settings to AgentConfig,
replacing the hardcoded MAX_TOOL_ITERATIONS constant. Fix shell_injection
policy rule to not block markdown backtick code snippets.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address benchmarks crate audit findings
High:
- Fix truncate_output UTF-8 panic on multi-byte char boundaries
- Fix parallel results durability (write JSONL per-task, not after all)
Medium:
- Fix --sample to use random shuffle instead of first-N
- Delegate all LlmProvider methods in InstrumentedLlm
- Fix LLM-as-judge to return fail instead of misleading 0.5
- Remove unnecessary shallow clone (always gets unshallowed)
- Replace .unwrap() with .expect() in LazyLock regex init
Low:
- Remove dead code: unused error variants, trait methods, struct fields
- Remove BenchSuite::name() (redundant with id())
- Remove TaskSubmission::conversation, ConversationTurn, TurnRole
- Remove unused methods from BenchChannel, results, config
- Clean up ChannelCapture conversation tracking
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: add SWE-bench dataset and Docker scoring infrastructure
Add the SWE-bench Lite dataset (300 tasks) and Docker files for
isolated test execution and scoring of SWE-bench patches.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore: remove benchmarks (extracted to separate repo)
Benchmarks crate has been extracted to its own repository.
Remove the workspace member and all benchmarks/ files.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: add missing AgentConfig fields in test initializer
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>