mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
dbd3e0807f269eba6da01487625590432289ecb8
144
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dbd3e0807f |
Feat/html to markdown #106 (#115)
* 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]> |
||
|
|
436066415b |
feat: embedded registry catalog and WASM bundle install pipeline (#283)
* 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]> |
||
|
|
3d4c647216 |
fix: map Esc to interrupt and Ctrl+C to graceful quit (#267)
* fix: map Esc to interrupt and Ctrl+C to graceful quit * Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> |
||
|
|
b68d67bd35 |
feat: show token usage and cost tracker in gateway status popover (#284)
* 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]> |
||
|
|
493e4578d0 |
feat: support custom HTTP headers for OpenAI-compatible provider (#269)
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]> |
||
|
|
250551799b |
style: adopt agent-market design language for web UI (#282)
* 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]>
|
||
|
|
c038c7705b |
feat: add smart routing provider for cost-optimized model selection (#281)
* 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]> |
||
|
|
98ee648fcb |
perf: speed up startup from ~15s to ~2s (#280)
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]> |
||
|
|
2cdd1acb1e |
refactor: consolidate tool approval into single param-aware method (#274)
* 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]> |
||
|
|
3b6105d5ea |
feat: add TEE attestation shield to web gateway UI (#275)
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]> |
||
|
|
df8616b604 |
fix: add X-Accel-Buffering header to SSE endpoints (#277)
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. |
||
|
|
e8dcb52fda |
feat: configurable tool iterations, auto-approve, and policy fix (#251)
* 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]> |
||
|
|
448383cfb0 |
refactor: remove Responses API, consolidate to Chat Completions (#272)
* fix: strip reasoning from LLM responses and persist assistant messages reliably - Filter out `type: "reasoning"` output items from NEAR AI Responses API parsing so chain-of-thought never reaches the UI (nearai.rs) - Rewrite clean_response with regex-based tag stripping that is code-aware (preserves tags inside fenced blocks and inline backticks), supports 9+ tag names (think, thought, reasoning, reflection, etc.), handles <final> extraction, pipe-delimited tags, and case/whitespace tolerance (reasoning.rs) - Add Reasoning::complete() helper so all non-agentic LLM call sites (summarize, suggest, heartbeat, compaction) get automatic response cleaning; thread SafetyLayer through to those callers - Change persist_turn from fire-and-forget tokio::spawn to awaited async so both user and assistant messages are written before returning, preventing data loss on shutdown/restart - Pass input_count through seed_response_chain so response chaining delta calculation is accurate after thread hydration on restart - Make NearAiResponse.usage optional and preserve response_id in alt response path for chaining continuity - Persist session token to DB during onboarding wizard so runtime loads it without legacy-key fallback; suppress spurious warning on fresh installs - Fix dev tool double-registration when builder already registers them - Load dotenv/ironclaw env for doctor and status subcommands - Reduce startup log noise (demote info→debug for skills, remove redundant info lines) Co-Authored-By: Claude Opus 4.6 <[email protected]> * Nudge to not loop over tools continuesly * refactor: remove Responses API, consolidate NEAR AI to Chat Completions only The Responses API provider (nearai.rs, 1278 lines) added significant complexity (response chaining state machine, delta message calculation, previous_response_id persistence) for marginal benefit. This consolidates to the Chat Completions API only, upgrading NearAiChatProvider with dual auth (session token + API key) and 401 retry for session token renewal. - Delete src/llm/nearai.rs (Responses API provider) - Upgrade nearai_chat.rs with SessionManager, dual auth, flexible list_models - Remove response_id from CompletionResponse and ToolCompletionResponse - Remove seed_response_chain/get_response_chain_id from LlmProvider trait - Remove response chain persistence from agent (thread_ops, session) - Remove NearAiApiMode enum and NEARAI_API_MODE config - Clean up all wrapper providers (retry, circuit_breaker, failover, cache) - Update documentation (CLAUDE.md, .env.example) Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: runtime log level control via gateway UI and URL parameter Add server-side log level switching using tracing_subscriber::reload::Layer so the EnvFilter can be swapped at runtime without restarting. Expose via GET/PUT /api/logs/level endpoints, a "Server: LEVEL" dropdown in the logs toolbar, and a ?log_level=debug URL parameter for one-click activation. Also applies cargo fmt to pre-existing files (llm/, tests/). Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
7df356c109 |
fix: persist WASM channel workspace writes across callbacks (#264)
* fix: persist WASM channel workspace writes across callbacks WASM channel callbacks (polling, webhooks, on_start) call workspace_write() to persist state, but the host code never committed these writes — take_pending_writes() was never called. Additionally, no WorkspaceReader was injected into channel capabilities, so workspace_read() always returned None. This caused Telegram's polling offset to reset to 0 on every tick, making getUpdates re-deliver already-processed messages and producing 2-4 duplicate LLM responses per user message. Add ChannelWorkspaceStore (Arc-wrapped HashMap with std::sync::RwLock) that persists across callback invocations within a channel's lifetime. Inject it as the WorkspaceReader and commit pending writes after every callback execution (on_start, on_poll, on_http_request, execute_poll). Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix formatting Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
3829d81269 |
fix: consolidate per-module ENV_MUTEX into crate-wide test lock (#246)
Each config test module (llm.rs, embeddings.rs) defined its own ENV_MUTEX, which doesn't prevent cross-module env races since cargo test runs in parallel. Move to a single shared mutex in config/helpers.rs so all unsafe set_var/remove_var calls are serialized crate-wide. Closes #245 Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
8a4f3b6f88 |
fix: remove auto-proceed fake user message injection from agent loop (#255)
The agentic loop injected fake user messages ("Please proceed and use
the available tools to complete this task.") when the LLM responded
with text instead of tool calls. This caused hallucinated conversations
during casual chat, 3x wasted LLM calls, and trust issues.
Remove the `resume_after_tool` parameter and `tools_executed` tracking
entirely. Text responses now return immediately, trusting the LLM to
decide when tools are needed (consistent with ZeroClaw and OpenClaw).
Closes #145
Co-authored-by: Claude Opus 4.6 <[email protected]>
|
||
|
|
5725a62c83 |
fix: onboarding errors reset flow and remote server auth (#185, #186) (#248)
* fix: incremental settings persistence and remote server auth (#185, #186) Persist settings after each wizard step so failures don't lose prior progress. Load existing settings on re-run to recover from partial onboarding. Add manual token paste option for remote/headless servers where browser OAuth is unreachable, and support IRONCLAW_OAUTH_CALLBACK_URL for custom callback URLs. Color prompt output (green/red/blue prefixes). Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: replace session token paste with API key entry, address PR review Replace option 4 in NEAR AI auth menu from session token paste to NEAR AI Cloud API key entry (cloud.near.ai). Also address all PR review feedback: restrict .env file permissions to 0o600, mask API key input with secret_input, fix libsql loaded flag in try_load_existing_settings, add ENV_MUTEX to oauth_defaults tests, and add NEARAI_API_KEY to secrets injection. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: deduplicate keys in upsert_bootstrap_var When the .env file contains duplicate keys (e.g. from manual editing), only write the replacement once and skip subsequent duplicates. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: NEARAI_SESSION_TOKEN env var takes precedence over file-based tokens Hosting providers inject session tokens via env var and expect them to be used directly. Previously the env var was only picked up when no session file existed and was treated as a legacy migration. Now the env var always wins, without persisting to disk. Co-Authored-By: Claude Opus 4.6 <[email protected]> * docs: distinguish NEAR AI Chat and NEAR AI Cloud providers Split documentation into two clearly named modes: - NEAR AI Chat: Responses API at private.near.ai, session token auth - NEAR AI Cloud: Chat Completions API at cloud-api.near.ai, API key auth Update default base URLs so each mode points to its correct endpoint. Update .env.example, deploy/env.example, CLAUDE.md, setup spec, and code comments across config/llm.rs, nearai.rs, nearai_chat.rs, mod.rs. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: wizard recovery ordering — load DB before persist, fresh choices win Previously, persist_after_step() ran after Step 1 but before try_load_existing_settings(), bulk-upserting defaults that clobbered prior settings. Additionally, merge_from gave stale DB values precedence over fresh Step 1 choices. Fix: snapshot Step 1 settings, load DB, then re-apply the snapshot. This ensures prior progress (steps 2-7) is recovered while fresh Step 1 choices override stale DB values. Add two tests verifying wizard recovery merge ordering. Addresses PR review comments from Copilot on wizard.rs:150, wizard.rs:1607, and wizard.rs:1626. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix rustfmt formatting in config/llm.rs Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: collapse nested if per clippy collapsible_if lint Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: use print_success for API key confirmation, fix menu spacing - Use print_success() for colored output consistency in api_key_login - Fix box-drawing alignment: options 1-2 had an extra trailing space Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
bfe393eb38 |
fix: parallelize tool call execution via JoinSet (#219) (#252)
* fix: parallelize tool call execution via JoinSet (#219) When the LLM returns multiple tool_calls in a single response, they were executed sequentially. This change makes both the worker and dispatcher paths concurrent using tokio::task::JoinSet, so N independent tool calls complete in ~max(latency) instead of sum(latency). Worker path: migrate execute_tools_parallel from join_all to JoinSet and route the respond_with_tools branch through the same parallel path. Dispatcher path: restructure the while-idx loop into three phases — preflight (sequential approval/hook checks), parallel execution via JoinSet, and sequential post-flight processing (session recording, auth detection, sanitization). Also fixes a pre-existing infinite loop bug where hook rejection used `continue` inside a `while idx` loop, skipping `idx += 1` and retrying the same rejected tool forever. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review — ordered results, deferred auth, dedup standalone fn - Fix auth early return skipping unrecorded tool results: defer auth response until after all results in the batch are recorded in session history and context_messages (both dispatcher and thread_ops paths) - Fix tool results appearing out of order: collect Phase 1 hook rejections indexed by original position, merge with Phase 2 execution results, and emit all in Phase 3 in original tool_calls order - Deduplicate execute_chat_tool: Agent method now delegates to the standalone function instead of duplicating 90 lines of logic - Fix benchmark compilation: add missing session_manager arg to Agent::new Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: rustfmt alignment for CI compatibility Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address second round of PR review comments - Distinguish JoinError panic vs cancellation in log messages and error reasons across all 3 files (dispatcher, thread_ops, worker) - Simplify deferred_auth from Option<(String, String)> to Option<String> since only the instructions string is used - Add single-tool short-circuit in worker execute_tools_parallel to avoid JoinSet overhead for the common single-tool case Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
9906190de7 |
fix: prevent pipe deadlock in shell command execution (#140)
Drain stdout and stderr concurrently with child.wait() using tokio::join to prevent deadlocks when command output exceeds the OS pipe buffer (64KB on Linux, 16KB on macOS). Use AsyncReadExt::take() for memory-bounded reads and tokio::io::copy to sink for draining excess output. Add regression test that generates 128KB of output to verify the fix prevents deadlocks. |
||
|
|
3f135bdde9 |
fix: persist turns after approval and add agent-level tests (#250)
* fix: persist turns after approval and add agent-level tests Port relevant changes from PR #112 that were not carried over to #237: - Add persist_turn calls in process_approval for the response, error, and auth-required paths. Previously, turns completed after tool approval were never persisted to DB — if the process crashed after approval the entire turn (user message + assistant response) was lost. - Add agent-level unit tests: StaticLlmProvider mock, make_test_agent helper, tests for auto-approval logic, destructive shell command detection, and PendingApproval backward-compatible deserialization (without deferred_tool_calls field). - Remove unused _thread_state binding in process_approval. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address 14 audit findings in src/agent/ Audit of the agent module found 2 High, 7 Medium, 3 Low, and 2 Nit severity issues. This commit fixes all of them: High: - Remove 4 `.expect()` calls in session.rs (entry API, match, direct indexing, if-let) to eliminate panic paths in production - Add typed RoutineError enum replacing Result<_, String> across routine.rs, routine_engine.rs, and callers in history/store.rs and db/libsql/mod.rs Medium: - Sanitize routine names in path construction to prevent directory traversal (routine_engine.rs) - Log warnings for 5 silently-swallowed errors in scheduler.rs, compaction.rs, and worker.rs - Extract shared handle_auth_intercept helper to deduplicate auth interception in thread_ops.rs - Add session count warning threshold in session_manager.rs - Make FullJob stub degradation visible via warn-level log and prepended warning in output Low: - Restrict dead code visibility with #[cfg(test)] on 19 unused items in submission.rs, task.rs, and undo.rs - Narrow pub to pub(crate) on self_repair.rs builder methods - Remove TaskStatus from mod.rs re-exports (test-only type) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review comments - Reorder persist_turn before persist_response_chain so the conversation row exists before the metadata UPDATE runs - Add persist_response_chain call to handle_auth_intercept so auth-required paths preserve the response chain - Harden sanitize_routine_name to use allowlist (alphanumeric, dash, underscore) instead of denylist replacements - Fix stale active_thread ID in get_or_create_thread: fall back to create_thread() when the stored ID is missing from the map - Persist turn on approval rejection so user messages survive crashes after a tool is rejected Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
97a7637f30 |
feat: extension registry with metadata catalog and onboarding integration (#238)
* feat: add extension registry with metadata catalog, CLI, and onboarding integration Adds a central registry that catalogs all 14 available extensions (10 tools, 4 channels) with their capabilities, auth requirements, and artifact references. The onboarding wizard now shows installable channels from the registry and offers tool installation as a new Step 7. - registry/ folder with per-extension JSON manifests and bundle definitions - src/registry/ module: manifest structs, catalog loader, installer - `ironclaw registry list|info|install|install-defaults` CLI commands - Setup wizard enhanced: channels from registry, new extensions step (8 steps) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(setup): resolve workspace errors for tool crates and channels-only onboarding Tool crates in tools-src/ and channels-src/ failed `cargo metadata` during onboard install because Cargo resolved them as part of the root workspace. Add `[workspace]` table to each standalone crate and extend the root `workspace.exclude` list so they build independently. Channels-only mode (`onboard --channels-only`) failed with "Secrets not configured" and "No database connection" because it skipped database and security setup. Add `reconnect_existing_db()` to establish the DB connection and load saved settings before running channel configuration. Also improve the tunnel "already configured" display to show full provider details (domain, mode, command) instead of just the provider name. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(registry): address PR review feedback on installer and catalog - Use manifest.name (not crate_name) for installed filenames so discovery, auth, and CLI commands all agree on the stem (#1) - Add AlreadyInstalled error variant instead of misleading ExtensionNotFound (#2) - Add DownloadFailed error variant with URL context instead of stuffing URLs into PathBuf (#3) - Validate HTTP status with error_for_status() before reading response bytes in artifact downloads (#4) - Switch build_wasm_component to tokio::process::Command with status() so build output streams to the terminal (#6) - Find WASM artifact by crate_name specifically instead of picking the first .wasm file in the release directory (#7) - Add is_file() guard in catalog loader to skip directories (#8) - Detect ambiguous bare-name lookups when both tools/<name> and channels/<name> exist, with get_strict() returning an error (#9) - Fix wizard step_extensions to check tool.name for installed detection, consistent with the new naming (#11, #12) - Fix redundant closures and map_or clippy warnings in changed files Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(setup): restore DB connection fields after settings reload reconnect_postgres() and reconnect_libsql() called Settings::from_db_map() which overwrote database_url / libsql_path / libsql_url set from env vars. Also use get_strict() in cmd_info to surface ambiguous bare-name errors. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix clippy collapsible_if and print_literal warnings Collapse nested if-let chains and inline string literals in format macros to satisfy CI clippy lint checks (deny warnings). Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(registry): prefer artifacts for install-defaults and improve dir lookup - InstallDefaults now defaults to downloading pre-built artifacts (matching `registry install` behavior), with --build flag for source builds. - find_registry_dir() walks up 3 ancestor levels from the exe and adds a CARGO_MANIFEST_DIR fallback, matching load_registry_catalog() logic. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
dae26d640e |
feat(models): add GPT-5.3 Codex, full GPT-5.x family, Claude 4.x series, o4-mini (#197)
Fixes #184 — updates model selection, priority sort, and cost table to match current OpenAI and Anthropic model catalogs. OpenAI: GPT-5.3 Codex, GPT-5.2 Codex/Pro, GPT-5.1 Codex/Mini/Max, GPT-5/Mini/Nano, GPT-4.1/Mini/Nano, o4-mini, o3/Pro Anthropic: Claude Opus 4.6/4.5/4.1/4.0, Claude Sonnet 4.6/4.5/4.0, Claude Haiku 4.5, Claude 3.7 Sonnet, Claude 3.5 Haiku Also resolves stale merge-conflict markers in http.rs and json.rs. |
||
|
|
fa64df05ff |
feat: wire memory hygiene into the heartbeat loop (#195)
* feat: wire memory hygiene into heartbeat loop (#166) * refactor: address PR review comments for hygiene wiring * style: fix fmt import ordering and clippy too_many_arguments warning * fix: update heartbeat integration test to pass HygieneConfig argument HeartbeatRunner::new() now requires a HygieneConfig as its second argument after the hygiene wiring refactor. Pass the default config in the integration test. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
356f56f77c |
docs: update CLAUDE.md for recently merged features (#183)
* docs: update CLAUDE.md for recently merged features Document skills system, sandbox network proxy, leak detector, Tinfoil private inference, setup wizard, and shell env scrubbing that were merged but not reflected in CLAUDE.md. Co-Authored-By: Claude Opus 4.6 <[email protected]> * docs: fix SKILL.md format example and scoring description Align SKILL.md frontmatter example with actual SkillManifest struct: activation block with patterns/keywords/max_context_tokens, requires nested under metadata.openclaw. Fix scoring pipeline description to mention keywords, tags, and regex patterns instead of triggers/intents. Co-Authored-By: Claude Opus 4.6 <[email protected]> * docs: optimize CLAUDE.md structure and reduce from 959 to 671 lines - Update llm/ directory tree (4 -> 12 files to match actual codebase) - Fix "NEAR AI (required)" -> "NEAR AI (when LLM_BACKEND=nearai)" - Remove 28-item Completed changelog list (no actionable value) - Deduplicate 3 config blocks with cross-references - Extract Workspace deep-dive to src/workspace/README.md - Extract Tool Architecture deep-dive to src/tools/README.md - Consolidate Code Style and Review Discipline under Key Patterns - Add workspace and tools to Module Specifications table Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
3f58ed6232 |
fix: persist onboard_completed to bootstrap .env so config survives restart (#241)
* fix: persist onboard_completed to bootstrap .env so config survives restart (#187) The wizard saved settings to the database but check_onboard_needed() read from the legacy settings.json on disk, causing re-onboarding on every run for non-NEAR AI users. Write ONBOARD_COMPLETED=true to ~/.ironclaw/.env and check that env var instead of the legacy file. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Copilot <[email protected]> |
||
|
|
097a26ace6 |
fix: harden openai-compatible provider, approval replay, and embeddings defaults (#237)
* fix: harden openai-compatible tool flow and local defaults * fix: close approval replay gaps and harden openai-compatible flow * fix: address review feedback and code improvements (takeover #112) - Make ChatCompletionResponse.id Optional<String> to handle providers that omit or null the field - Propagate HTTP client builder errors instead of silently dropping timeout configuration (openai_compatible_chat, nearai_chat) - Add EMBEDDING_DIMENSION env var with smart per-model defaults instead of hardcoding 768/1536 everywhere - Remove duplicated dimension inference logic from main.rs Co-Authored-By: panosAthDBX <[email protected]> Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: harden src/llm/ module from crate audit findings - Replace 9x .expect() on RwLock with graceful poison recovery (nearai.rs: 7, nearai_chat.rs: 2) — eliminates production panics - Propagate HTTP client builder errors in nearai.rs instead of silently dropping timeout config (NearAiProvider::new now returns Result) - Make nearai_chat ChatCompletionResponse.id Optional<String> (mirrors openai_compatible_chat.rs fix for providers that omit id) - Make nearai_chat usage fields optional with defensive parse_usage() helper (was required u32 fields that crash on null/missing) - Truncate error responses to 512 chars in nearai_chat.rs error messages to prevent log bloat and potential data leakage - Delegate 4 missing LlmProvider methods in FailoverProvider (model_metadata, seed_response_chain, get_response_chain_id, calculate_cost) to last-used provider instead of trait defaults Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor(llm): add RetryProvider, remove openai_compatible_chat, harden decorators - Add composable RetryProvider decorator wrapping any LlmProvider with exponential backoff + jitter, respecting RateLimited retry_after hints - Remove openai_compatible_chat.rs — replaced by rig adapter + RetryProvider - Remove internal retry loop from nearai.rs (was causing double-retry with external RetryProvider, up to 16 attempts instead of 4) - Remove internal retry loop from nearai_chat.rs (same issue) - Wire RetryProvider into main.rs composition chain: each provider gets its own retry wrapper before failover - Move normalize_tool_name to rig_adapter.rs for all rig-based providers - Reconcile is_retryable() vs is_transient() error classification: ModelNotAvailable no longer retryable, Json no longer transient - Fix unchecked Duration subtraction panic in circuit_breaker.rs - Make failover.rs use shared is_retryable() from retry.rs - Remove stale #[allow(dead_code)] on NearAiResponse::id (field is used) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review feedback — error handling, dimension validation, libSQL warning - Replace response.text().await.unwrap_or_default() with proper error propagation in nearai.rs and nearai_chat.rs (4 call sites). Failures now return LlmError::RequestFailed with context instead of silently proceeding with an empty string. - Add embedding dimension validation in OllamaEmbeddings::embed_batch(): returns EmbeddingError if Ollama returns embeddings with a dimension that doesn't match the configured value. - Add runtime warning when libSQL backend is used with non-1536 embedding dimension, since the libSQL schema uses F32_BLOB(1536) and cannot store different-dimension vectors. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: panosAthDbx <[email protected]> Co-authored-by: panosAthDBX <[email protected]> Co-authored-by: panosAthDBX <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Copilot <[email protected]> |
||
|
|
e87d7bd066 |
feat: extend lifecycle hooks with declarative bundles (#176)
* feat: add bundled and declarative hook bundle loading * fix: load plugin hooks only for active extensions * fix: avoid duplicate plugin hook registration * security: harden outbound webhook hooks * fix: pin webhook DNS resolutions for outbound hooks * fix: block IPv4-mapped local webhook targets * style: format webhook hardening changes for CI * fix: pass HookRegistry to ExtensionManager in AppBuilder After merging main (which extracted AppBuilder from main.rs in #198), the ExtensionManager::new() call in app.rs was missing the `hooks` parameter that PR #176 added. This moves HookRegistry creation before init_extensions() and threads it through, matching the existing pattern in main.rs. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
e42b1e5ec1 |
fix: Network Security Findings (#201)
* docs(security): add network security reference for all listeners Catalogs every network-facing surface (web gateway, webhook server, orchestrator API, OAuth callback, sandbox proxy) with auth mechanisms, bind addresses, egress controls, known findings, and a review checklist for PRs that touch network-facing code. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(security): address three network security findings - Use constant-time comparison (ct_eq) for webhook secret validation, matching the pattern in web gateway and orchestrator auth - Add X-Content-Type-Options and X-Frame-Options security headers to the web gateway via SetResponseHeaderLayer - Warn at startup when HTTP webhook server binds to 0.0.0.0 - Update NETWORK_SECURITY.md to mark findings 1, 4, 5 as resolved Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(security): address PR #201 review findings - Reorder web gateway layers so security headers (X-Content-Type-Options, X-Frame-Options) are outermost and apply to all responses including DefaultBodyLimit 413 rejections - Move 0.0.0.0 warning to final bind address resolution so it fires for WASM-only webhook servers that fall back to the default address - Add webhook handler auth tests: correct secret -> 200, wrong secret -> 401, missing secret -> 401 - Rewrite NETWORK_SECURITY.md: replace brittle line-number references with function/struct name anchors, add threat model section, document graceful shutdown per listener, fill content gaps (health endpoint responses, content-type validation, CSRF analysis, WS auth flow, MCP trust boundary, orchestrator rate limiting), change findings F-4/F-5 from "Resolved" to "Mitigated" with caveats Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix rustfmt and clippy warnings from main merge Fix formatting in llm/mod.rs and llm/rig_adapter.rs introduced by PR #132, and collapse nested if in rig_adapter.rs per clippy. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
ccf60055f4 |
feat: support per-request model override in /v1/chat/completions (#103)
* feat: support per-request model override for /v1/chat/completions - add optional model override to completion request types\n- forward request model through gateway, worker, and orchestrator proxy paths\n- use request model in NEAR AI providers with fallback to active model\n- replace model-mismatch integration test with override propagation checks\n- update FEATURE_PARITY.md note for OpenAI-compatible API behavior\n\nRefs #49 * Wire gateway OpenAI-compatible routes to active LLM provider * Validate OpenAI model name length before streaming * Address PR103 review feedback on model override and validation * Report effective model in OpenAI-compatible responses * Use async mutexes in OpenAI compatibility integration tests * fix tests for per-request model field in response cache * fix formatting and clippy lint after main merge * Fix model override reporting and cache correctness --------- Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
fd46cbd30d |
fix(rig): prevent OpenAI Responses API panic on tool call IDs (#182)
* fix(rig): prevent responses API panic on missing tool call IDs * style: format rig adapter * test(rig): add coverage for empty/whitespace tool call IDs Add tests for assistant tool calls with empty and whitespace-only IDs, and an end-to-end test documenting the seed mismatch limitation when both assistant call and tool result are missing IDs. * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Copilot <[email protected]> |
||
|
|
8dbb0996da |
Fix division by zero panic in ValueEstimator::is_profitable (#139)
* fix: prevent division-by-zero panic in ValueEstimator::is_profitable Guard against Decimal division by zero when price is zero. rust_decimal::Decimal panics on division by zero (unlike f64 which returns infinity), so we short-circuit before the division. When price is zero, a job is only profitable if the estimated cost is negative (i.e., we get paid to do it). Add test covering zero-price scenarios including the negative cost edge case. * style: fix pre-existing rustfmt and clippy issues in llm module Fix formatting and lint issues that cause CI Code Style check to fail: - src/llm/mod.rs: fix method chain indentation - src/llm/rig_adapter.rs: collapse multi-line single-expression statements, fix collapsible_if clippy warning |
||
|
|
c18f6730f8 |
fix: OpenAI tool calling — schema normalization, missing types, and Responses API panic (#132)
* fix: add missing type key to http tool body schema The body property in HttpTool::parameters_schema() was missing the required \"type\" key, causing OpenAI to reject all tool calls with: Invalid schema for function 'http' Fixes #131 * fix: add missing type key to json tool data schema Same class of bug as http tool body — the data property in JsonTool::parameters_schema() was missing the required "type" key, causing OpenAI to reject all tool calls. Fixes #131 * fix: use Chat Completions API to avoid rig-core Responses API panic The default openai::Client routes through rig-core's Responses API, which panics at "The tool call ID should exist!" because ironclaw doesn't thread call_id through its ToolCall type. Switch to openai::CompletionsClient which uses the Chat Completions API and works correctly with the existing code. * fix: normalize tool schemas for OpenAI strict mode compliance GPT-5/5.2 enforce strict function calling by default. Add normalize_schema_strict() that recursively transforms tool parameter schemas at the provider boundary: - Forces additionalProperties: false on all objects - Makes required list ALL property keys - Converts optional fields to nullable types - Handles nested objects, array items, and combinators Original schemas remain unchanged for other providers. Closes #131 --------- Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
ffb1cc9be8 |
refactor: architecture improvements for contributor velocity (#198)
* refactor: split large files and consolidate test stubs for contributor velocity - Extract 7 Database sub-traits (ConversationStore, JobStore, SandboxStore, RoutineStore, ToolFailureStore, SettingsStore, WorkspaceStore) with Database as a supertrait combining them all - Split libsql_backend.rs (2769 lines) into src/db/libsql/ directory with one file per sub-trait implementation - Split config.rs (1753 lines) into src/config/ directory with 16 domain files - Consolidate 3 duplicate test LLM stubs into shared StubLlm in src/testing.rs - Split server.rs handlers into src/channels/web/handlers/ directory - Extract main.rs init phases into AppBuilder (src/app.rs) - Add developer setup script (scripts/dev-setup.sh) Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: move heartbeat test from examples/ to tests/ Convert standalone example binary into a proper #[ignore] integration test, matching the convention of the other integration tests. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix rustfmt formatting for CI Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review comments from Copilot - tunnel.rs: replace .ok().flatten() with ? to propagate env var errors - secrets.rs: remove misleading "process-wide cache" comment - database.rs: use uppercase "DATABASE_URL" in error key - testing.rs: gate harness tests with #[cfg(feature = "libsql")] Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
9e6e1471ab |
style: fix rustfmt formatting from PR #137
Co-Authored-By: Claude Opus 4.6 <[email protected]> |
||
|
|
2d3eb4de9a |
fix(security): prevent path traversal bypass in WASM HTTP allowlist (#137)
* fix(security): prevent path traversal bypass in WASM HTTP allowlist The allowlist validator checked url_path.starts_with(prefix) on the raw, unnormalized path. A WASM tool could request a URL like: https://api.openai.com/v1/../admin The starts_with("/v1/") check would pass, but the server would resolve the ".." and serve /admin — effectively bypassing the path prefix restriction. This commit adds normalize_path() which resolves . and .. segments before validation, closing the bypass. It also includes 6 new tests covering traversal attacks and normalization correctness. * deslop: remove redundant comments, consolidate tests * chore(allowlist): trim nonessential traversal helper comment * harden URL parsing for wasm allowlist and proxy paths --------- Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
750a94030b |
fix: persist OpenAI-compatible provider and respect embeddings disable (#177)
* fix: persist OpenAI-compatible provider and respect embeddings disable (#129) Three interrelated bugs caused the agent to ignore user choices made during onboarding when using an OpenAI-compatible LLM provider: 1. Session auth ran before DB config reload, so Config::from_env() defaulted to NearAi and attempted Clerk auth before the real backend was known. Moved session auth to after final config resolution. 2. EmbeddingsConfig::resolve() force-enabled embeddings whenever OPENAI_API_KEY was present, ignoring the user's explicit disable. Changed to respect the stored setting as source of truth. 3. LLM_BACKEND was not saved to the bootstrap .env file, so Config::from_env() always defaulted to NearAi before the DB was connected. Now saves LLM_BACKEND, LLM_BASE_URL, and OLLAMA_BASE_URL alongside the database bootstrap vars. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: add SAFETY comments and sanitize .env value escaping Address PR review feedback: - Add SAFETY comments to all unsafe env var manipulation in config tests (gemini-code-assist). - Escape backslashes and double quotes in save_bootstrap_env() to prevent env var injection via malicious URLs (gemini-code-assist). - Add test verifying injection attempt is neutralized. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: incorporate PR #138 changes (chat completions, model sorting, tool schemas) Includes all changes from bigguybobby's PR #138: - Use Chat Completions API for OpenAI-compatible providers (avoids Responses API assumptions like required tool call IDs) - Fall back to settings.selected_model when LLM_MODEL env var is unset - Update OpenAI model list (add gpt-5 family) with priority-based sorting - Add is_openai_chat_model() filter with broader exclusion patterns - Fix http tool: headers schema → array of {name,value}, body → string type, parse_headers_param() accepts both legacy object and array formats - Fix json tool: data schema → string type, parse_json_input() normalizer, validate uses strict string-only check - Add mutex-serialized config tests for env var manipulation - Update NEAR AI config comment for accuracy Co-Authored-By: Bobby (bigguybobby) <[email protected]> Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Bobby (bigguybobby) <[email protected]> |
||
|
|
c3340c60ef |
fix: remove .expect() calls in FailoverProvider::try_providers (#156)
* fix: remove .expect() calls in FailoverProvider::try_providers (#155) Replace two .expect() calls with proper error propagation to comply with the project no-panic convention. Both were logically unreachable but would panic if invariants were broken by a future refactor. Closes #155 Co-Authored-By: Claude Opus 4.6 <[email protected]> * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Copilot <[email protected]> |
||
|
|
3669a7b1cd |
fix: sentinel value collision in FailoverProvider cooldown (#125) (#154)
ProviderCooldown used 0 as both the "not in cooldown" sentinel and a valid timestamp from now_nanos(), so activate_cooldown(0) would silently fail to activate. Store max(now_nanos, 1) to keep 0 reserved. Closes #125 Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
96d5fc0d39 |
feat: add Tinfoil private inference provider (#62)
* feat: add Tinfoil private inference provider Add a dedicated Tinfoil LLM backend (`LLM_BACKEND=tinfoil`) for Tinfoil's private inference service (https://tinfoil.sh). The existing `openai_compatible` backend cannot be used with Tinfoil because rig-core 0.30.0 defaults to the OpenAI Responses API (`/v1/responses`), which Tinfoil does not support — it only implements the Chat Completions API (`/v1/chat/completions`), returning 403 "shim: path not allowed" when hit on the responses endpoint. Rather than changing `openai_compatible` to use Chat Completions (which would break users expecting the Responses API), this adds a dedicated provider that explicitly uses rig's `.completions_api()` client. This also lays the groundwork for integrating Tinfoil's privacy wrapper client (enclave attestation, TLS certificate pinning) once their Rust SDK is available. The provider implementation can be swapped to use the Tinfoil Rust client without changing the LlmProvider interface. Configuration: LLM_BACKEND=tinfoil TINFOIL_API_KEY=tk_... TINFOIL_MODEL=kimi-k2-5 # optional, default * style: fix rustfmt formatting in Tinfoil provider * style: remove unnecessary tin_foil alias for Tinfoil backend * Update src/llm/mod.rs Co-authored-by: Copilot <[email protected]> * fix: add tinfoil field to LlmConfig test fixture * style: fix rustfmt output in session manager --------- Co-authored-by: firat.sertgoz <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
c1926c83d9 |
fix: skills module audit cleanup (#173)
* fix: skills module audit cleanup — deduplicate loading, async gating, pre-compute scoring fields Address 7 issues from the skills module audit (#157–#163): - Extract shared `load_and_validate_skill` helper, eliminating ~90 lines of duplication between `load_skill_md` and `load_skill_md_standalone` - Wrap blocking gating subprocess calls (`which`/`where`) in `tokio::task::spawn_blocking` to avoid blocking the async runtime - Remove dead `SkillParseError::FileTooLarge` and `SkillSource::Registry` - Replace `HashMap<String, ()>` with `HashSet<String>` in discovery - Fix misleading doc comment and unnecessary `ref` clone pattern - Use `CARGO_PKG_VERSION` for catalog HTTP user-agent instead of hardcoded "0.1" - Pre-compute lowercased keywords/tags at load time to avoid per-message allocation in the scoring hot path - Add tests for flat SKILL.md layout, mixed layouts, and lowercased field population Closes #157, closes #158, closes #159, closes #160, closes #161, closes #162, closes #163 Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR #173 review feedback - Distinguish cancel vs panic in spawn_blocking JoinError and include error details in the gating failure message (Copilot review) - Restore lowercased_keywords/lowercased_tags to `pub` for consistency with other LoadedSkill fields (Copilot review) Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
a1b0e34b3b |
feat: shell env scrubbing and command injection detection (#164)
* feat: shell env scrubbing and command injection detection Add two security hardening layers to the shell tool: 1. Environment scrubbing (CWE-200): When executing commands directly (no sandbox), clear the process environment and only forward safe variables (PATH, HOME, LANG, CARGO_HOME, etc.). API keys, session tokens, and credentials are no longer inherited by child processes. 2. Command injection detection: Catch obfuscation and exfiltration patterns that bypass existing blocked/dangerous command checks: - Null bytes (bypass string matching) - Base64/hex/xxd decode piped to shell - DNS exfiltration via command substitution - Netcat with data piping - curl/wget posting file contents - String reversal piped to shell Includes 14 new tests covering all injection patterns, false negative verification for legitimate dev workflows, and env scrubbing validation. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address codex review findings - Add Windows env vars to SAFE_ENV_VARS (SystemRoot, ComSpec, PATHEXT, etc.) so env scrubbing doesn't break direct execution on Windows. - Add has_command_token() helper for word-boundary-aware command matching. Prevents false positives where substrings match: "sync" no longer triggers "nc" detection, "ghost"/"--host" no longer triggers "host" detection, "digital" no longer triggers "dig". - Use has_command_token() in DNS exfil and netcat checks. - Add regression tests for all identified false positive scenarios. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review feedback - Fix contains_shell_pipe word boundary: "| shell", "| shift", "| show" no longer false-positive against "| sh". Uses has_pipe_to() helper that validates the char after the shell name. - Add "dash" to shell interpreter list. - Add PWD to SAFE_ENV_VARS (many tools and scripts depend on it). - Add curl -d@file (no space) pattern to injection detection. - Use has_command_token for "od " to avoid matching "method", "period". - Switch env-mutating tests to #[tokio::test(flavor = "current_thread")] to prevent data races (tokio defaults to multi-threaded runtime). - Add regression tests for all fixed false-positive scenarios. - Add more legitimate pipe-heavy commands to false-negative test. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
cfb579a4bb |
feat: Add PR review tools, job monitor, and channel injection for E2E sandbox workflows (#57)
* feat: Add PR review tools, job monitor, and channel injection for E2E sandbox workflows Adds JobEventsTool and JobPromptTool so the main agent can read container event logs and send follow-up prompts to running Claude Code sessions. A background JobMonitor forwards container assistant messages into the agent loop via a new inject channel on ChannelManager. CreateJobTool now accepts a project_dir parameter for mounting existing cloned repos into containers, and spawns the monitor automatically for async jobs. Also: Dockerfile bumped to Rust 1.88 (rig-core needs let chains), GITHUB_TOKEN forwarded into containers for gh CLI auth, and truncate() fixed for multi-byte char boundary panics. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address PR #57 review comments (IDOR, Dockerfile, truncate, logging) - Add ownership checks to JobEventsTool and JobPromptTool via ContextManager to prevent users from accessing other users' jobs (IDOR) - Combine Dockerfile gh CLI install into single apt-get layer - Handle truncate() edge case when max falls inside first multi-byte char - Log actual count of registered job management tools - Document fire-and-forget job monitor lifecycle - Add tests for ownership rejection and schema validation Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: Replace hardcoded GITHUB_TOKEN with on-demand credential delivery Containers now fetch credentials via authenticated GET /worker/{id}/credentials endpoint instead of receiving them baked into env vars at creation time. Secrets are decrypted from SecretsStore on demand, scoped per-job via CredentialGrant, and revoked automatically when the job completes. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address sandbox audit findings (CONNECT tunnel, readonly_rootfs, type consolidation) - Implement real CONNECT tunnel with bidirectional TCP piping via hyper upgrade - Fix readonly_rootfs to apply for both ReadOnly and WorkspaceWrite policies - Consolidate duplicate CredentialMapping/CredentialLocation into secrets::types - Share reqwest::Client across proxy requests instead of per-request allocation - Store Docker connection and reuse across executions - Remove .unwrap() from proxy response builders with safe fallbacks - Add output truncation to direct (non-container) execution (64KB limit) - Delete dead src/tools/sandbox.rs (ToolSandbox never used) - Fix connect_docker error message to list all attempted socket paths - Update proxy credential injection to handle all CredentialLocation variants - Use glob-based host_patterns matching for credential lookup in proxy policy Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address PR #57 review comments (IDOR, Dockerfile, truncate, logging) - Dockerfile: install curl+ca-certificates before fetching GitHub CLI GPG key - JobEventsTool/JobPromptTool: reject missing context (prevents IDOR bypass) - parse_credentials: validate env var names against denylist and pattern - resolve_project_dir: require explicit paths to exist before validation - Credential serving: lower log level from info to debug Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address orchestrator audit findings (constant-time auth, error handling, tests) - auth: constant-time token comparison via subtle::ConstantTimeEq - auth: replace hand-rolled hex_encode with std::fmt::Write fold - api: report_status now updates ContainerHandle (was a no-op) - api: log complete_job errors instead of silently discarding - job_manager: log Docker cleanup errors in stop_job/complete_job - job_manager: extract validate_bind_mount_path with proper error on missing home_dir and mandatory base dir creation before canonicalize - job_manager: cache Docker connection across operations - error: remove dead OrchestratorError::AuthFailed and ContainerTimeout - Add 13 new tests (prompt queue, credentials, events, status, paths) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: use floor_char_boundary in sandbox manager truncate to prevent multi-byte panics String::truncate() panics when the index falls mid-way through a multi-byte UTF-8 character. Use the same floor_char_boundary utility already used in worker/runtime.rs and tools/builtin/shell.rs. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: default base_url to private.near.ai for Responses API mode Session tokens only authenticate against private.near.ai, not cloud-api.near.ai. The default base_url now matches the api_mode: - Responses (session token): https://private.near.ai - ChatCompletions (API key): https://cloud-api.near.ai This broke when the multi-provider merge introduced cloud-api.near.ai as the unconditional default. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: use private.near.ai as default base URL for all API modes private.near.ai now supports both Responses and ChatCompletions endpoints, so there is no reason to route through cloud-api.near.ai. This also fixes session token auth which only works against private.near.ai. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: harden libSQL concurrency, fix Claude Code Docker auth and permissions Three fixes for the sandbox/Claude Code pipeline: 1. SQLite "database is locked": set WAL journal mode in migrations and PRAGMA busy_timeout=5000 on every connection across LibSqlBackend, LibSqlSecretsStore, and LibSqlWasmToolStore (~83 async call sites). 2. Claude Code container auth: extract OAuth token from macOS Keychain (or Linux ~/.claude/.credentials.json) at startup and inject via CLAUDE_CODE_OAUTH_TOKEN env var. Removes the broken bind-mount approach that failed on uid mismatch. 3. Claude Code tool permissions: wire CLAUDE_CODE_ALLOWED_TOOLS env var through to the worker binary (was hardcoded to empty vec), and expand defaults to include all standard tools (Read, Write, Edit, Glob, Grep, NotebookEdit, Bash, Task, WebFetch, WebSearch). Also adds --verbose flag to claude CLI (required with stream-json + -p), failover provider model switching, and nearai models endpoint fix. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: stream event parsing, job ID prefix resolution, session renewal in list_models Three fixes for the Docker/gateway pipeline: 1. Claude Code stream event parsing (claude_bridge.rs): Rewrite ClaudeStreamEvent to match actual NDJSON format where content blocks are nested under message.content[], not at the top level. Add handler for "user" events (tool_result blocks) and emit result text as a "message" event so reviews appear in gateway activity view. 2. Job ID prefix resolution (job.rs): Add resolve_job_id() that accepts short hex prefixes (like git short SHAs) in addition to full UUIDs. The LLM sees truncated IDs in job monitor messages like "[Job f2854dd8]" and can now use them directly with job_status/cancel/events/prompt tools. 3. Session renewal in list_models (nearai.rs): list_models() now retries with OAuth renewal on 401, matching send_request()'s existing behavior. Previously it returned SessionExpired immediately, causing the setup wizard to fall back to defaults instead of prompting re-authentication. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: /model command now lists available models Previously /model with no args only showed the current model name. Now it fetches and displays all available models from the provider, marking the active one, so users can see what's available before switching with /model <name>. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR #57 review findings (set_var UB, tunnel timeout, restart creds) - Replace unsafe `std::env::set_var` in worker runtime and Claude bridge with `Command::envs()` injection via a new `extra_env` field on `JobContext`, avoiding undefined behavior in the multi-threaded tokio runtime. - Add 30-minute timeout to CONNECT tunnel `copy_bidirectional` in the sandbox proxy to prevent stuck connections from leaking spawned tasks. - Persist credential grants (as JSON in the description column) on `SandboxJobRecord` so `jobs_restart_handler` can restore them instead of passing `vec![]`, which caused restarted containers to lose access to their original secrets. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address second round of PR #57 review comments - Normalize host_patterns to lowercase in proxy policy matching - Push LIMIT into SQL for list_job_events (Database trait + both backends) - Remove unused was_explicit binding in job tool - Return 500 instead of 200 in make_response fallback path - Update copy_auth_from_mount docstring for env-var default - Use entry.file_type() instead of is_dir() to avoid following symlinks Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address third round of PR #57 review comments - Restore glob patterns in default_claude_code_allowed_tools (Bash -> Bash(*)) - Add tracing::warn for credential grant serialize/deserialize failures - Wrap extra_env in Arc<HashMap> to avoid deep cloning per tool call - Document unsupported credential locations (AuthorizationBasic, UrlPath) - Document TOCTOU window in validate_bind_mount_path - Expand doc comments on JobEventsTool and JobPromptTool Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address fourth round of PR #57 review comments - Document CONNECT tunnel task lifecycle (timeout is the cleanup mechanism) - Remove secret names from error-level credential logs to prevent leaking - Expand DANGEROUS_ENV_VARS denylist with language runtime hijack vectors Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address fifth round of PR #57 review comments - Promote job monitor startup log to info level for observability - Require minimum 4-char prefix in resolve_job_id to limit enumeration - Cap credential grants at 20 per job to bound column storage - Clamp job events limit to 1..1000 to prevent memory abuse Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: add missing closing brace for SkillsConfig impl block The merge resolution dropped the closing `}` for `impl SkillsConfig`, causing a compilation error in CI. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
bac2d75713 |
feat: Secure prompt-based skills system (Phases 1-4) (#51)
* feat: Add secure prompt-based skills system (Phase 1 MVP) Implement a skills system that extends the agent with prompt-level instructions from local directories. Skills declare activation criteria, tool permissions, and trust tiers that determine authority attenuation. Core security model: the minimum trust level of any active skill determines a tool ceiling -- tools above the ceiling are removed from the LLM's tool list entirely at the API level, preventing prompt-based manipulation. New modules: - skills/mod.rs: Core types (SkillTrust, SkillManifest, LoadedSkill) - skills/scanner.rs: Content scanner for manipulation detection - skills/registry.rs: Filesystem discovery and manifest parsing - skills/selector.rs: Deterministic two-phase prefilter (no LLM) - skills/attenuation.rs: Trust-based tool filtering Integration: - Agent loop selects skills per-turn and applies tool attenuation - Reasoning engine injects skill context with structural isolation - Config supports SKILLS_ENABLED, SKILLS_DIR, SKILLS_MAX_ACTIVE, SKILLS_MAX_CONTEXT_TOKENS environment variables - Disabled by default (SKILLS_ENABLED=false) 41 new tests covering all modules. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address all adversarial review findings for skills system Security fixes: - Escape skill name/version in XML attributes to prevent trust spoofing - Escape prompt content to prevent </skill> tag breakout - Require integrity hash for Verified/Community tier skills - Validate skill names against [a-zA-Z0-9][a-zA-Z0-9._-]{0,63} - Add 64 KiB file size limit on prompt.md Bug fixes: - Use actual SkillsConfig from AgentDeps instead of SkillsConfig::default() - Add skills_config field to AgentDeps, wired through from main.rs Performance: - Pre-compile regex patterns at load time (cached on LoadedSkill) - Selector uses pre-compiled patterns instead of recompiling per message - Switch all std::fs to tokio::fs for non-blocking async I/O Hardening: - Cap keyword score at 30 points to prevent keyword stuffing attacks - Enforce max 20 keywords and 5 patterns per skill - Normalize line endings (CRLF/CR to LF) before hashing - Also includes cargo fmt formatting fixes for adjacent code Tests: 54 skills tests pass (up from 41), zero new clippy warnings. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address medium/low severity findings from adversarial review Fixes all 18 medium/low severity findings identified by the security review: - mod.rs: Add MAX_TAGS_PER_SKILL cap (10) in enforce_limits(); use RegexBuilder with 64 KiB size_limit to prevent ReDoS; replace case-enumerated escape_skill_content with regex matching all case variants plus whitespace/null byte injection between </ and skill; document allowed_patterns as unenforced until Phase 2; document Marketplace URL validation as Phase 3 concern - registry.rs: Add MAX_MANIFEST_FILE_SIZE (16 KiB) check before reading; add symlink detection via symlink_metadata to reject symlinks in discover_local; add MAX_DISCOVERED_SKILLS (100) cap; validate prompt_hash format (sha256: + 64 hex chars); warn on name collision before overwriting; accept SkillSource parameter in load_skill instead of always using Local; add InvalidHashFormat, ManifestTooLarge, SymlinkDetected error variants - selector.rs: Add MAX_TAG_SCORE (15) cap parallel to keyword cap; warn when declared max_context_tokens diverges >2x from actual prompt size - scanner.rs: Add mixed-script homoglyph detection (Cyrillic, Greek, Armenian unicode ranges); document token-boundary bypass and semantic paraphrasing as known limitations - attenuation.rs: Document READ_ONLY_TOOLS maintenance requirements - agent_loop.rs: Surface scan warnings via structured tracing; add structured audit events for skill activation and tool attenuation 61 tests pass, 0 new clippy warnings. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address 12 findings from second adversarial security review HIGH: - Escape opening <skill tags in prompt content (prevents fake skill block injection) - Scan manifest metadata fields (description, author, tags, reasons) not just prompt - Block trust downgrade on name collision (existing Local can't be replaced by Community) MEDIUM: - Eliminate TOCTOU gap: read files then check size instead of metadata-then-read - Reject file-level symlinks in load_skill (prompt.md, skill.toml) - Truncate and filter manifest.skill.tags (prevent unlimited tag scoring) - Cap regex pattern score at 40 (prevent 5x20=100 dominating keyword+tag) - Add doc comment about skill_list tool exposing metadata (sanitization required) - Move Community disclaimer inside <skill> tags (not outside structural boundary) - Filter keywords/tags shorter than 3 chars (prevent broad matching) LOW: - Enforce minimum token_cost of 1 (max_context_tokens=0 can't bypass budget) - Remove redundant try_exists checks in discover_local (let load_skill handle errors) 70 skills tests passing. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: Add HTTP endpoint scoping for skills (Phase 1) Skills that declare an [http] section in skill.toml now have their HTTP requests constrained to declared endpoints at runtime. This addresses the gap where allowed_patterns was parsed but never enforced -- once the http tool was visible via attenuation, the LLM could reach any URL. Enforcement reuses EndpointPattern/AllowlistValidator from the WASM capability system. Semantics: if no active skill declares [http], all requests pass through (backward compat). If any skill declares [http], URLs must match at least one skill's allowlist (union). Community skills' [http] declarations are silently ignored (defense in depth). Shell commands using curl/wget are also validated against scopes. Scanner gains detection for known exfiltration domains (webhook.site, ngrok.io, etc.), overly broad wildcards, and credential/host mismatches. Closes #38 Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: Apply cargo fmt to http_scoping.rs Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: Apply cargo fmt across codebase Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: Add parameter-level permission enforcement for skills (Phase 2) Activates enforcement of `allowed_patterns` in skill.toml permissions. Previously these patterns were parsed but not enforced -- a Verified skill declaring `permissions.shell` with `allowed_patterns = [{command = "cargo *"}]` could still run any shell command. Now the enforcer validates tool parameters against declared glob patterns before execution. Key changes: - New `enforcer.rs` module with `SkillPermissionEnforcer`, `glob_to_regex()`, and `validate_tool_call()` with union semantics across active skills - Typed pattern enums (`ShellPattern`, `FilePathPattern`, `MemoryTargetPattern`) replace the previous `Vec<serde_json::Value>` in `ToolPermissionDeclaration` - Scanner gains `scan_permission_patterns()` detecting dangerous patterns (rm, sudo, curl, bare wildcards, command chaining, sensitive paths, identity files) - Registry blocks non-Local skills with critical permission pattern warnings - Agent loop threads enforcer into `execute_chat_tool` alongside HTTP scoping Trust interaction: Community patterns ignored, Verified enforced, Local without patterns unrestricted, Local with patterns enforced as guidance. Union semantics across skills -- tool call allowed if ANY skill's patterns permit it. 34 new tests. All 818 library tests pass. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: Add worker permission enforcement and LLM behavioral analysis (Phase 3+4) Phase 3 - Worker-side permission enforcement: - Add SerializedToolPermission/SerializedPattern DTOs for HTTP boundary crossing - Extend JobDescription, ContainerHandle, and orchestrator API to carry permissions - CreateJobTool snapshots and forwards skill permissions to spawned workers - Worker runtime builds SkillPermissionEnforcer and checks before tool execution - Load-time token budget enforcement rejects prompts exceeding 2x declared budget - Deduplicate enforcer construction: from_active_skills() delegates to from_serialized() Phase 4 - LLM behavioral analysis: - BehavioralAnalyzer with cached, LLM-based semantic content analysis - Structured output parsing (FINDING|CATEGORY|SEVERITY|DESCRIPTION or CLEAN) - Content-hash caching with bounded size (MAX_CACHE_ENTRIES=256) - Graceful degradation when LLM unavailable - Integrated into load_skill() for non-Local skills; critical findings block loading Review fixes: - Real cache tests with CountingLlm mock (test_cache_hit, test_cache_miss, test_cache_bounded) - UTF-8-safe truncate() in worker runtime - Few-shot examples in behavioral analysis prompt - Documented max_context_tokens=0 opt-out and create_job() permission gap 848 tests passing, no new clippy warnings. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: Address review feedback from serrrfirat on skills-phase2 - Fix truncate_cmd UTF-8 panic: use char-boundary-aware slicing - Remove redundant effective_tools branching in reasoning.rs - Document cache eviction as known limitation (arbitrary, not LRU) - Add safety comment on SkillTrust enum ordering (security-critical) - Simplify active_skills selection (prefilter_skills handles empty input) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address remaining skills review feedback * refactor: replace skills system with OpenClaw SKILL.md format + 2-state trust Replace the 5-gate, 3-tier trust hierarchy (scanner, behavioral analyzer, parameter-level enforcer, HTTP endpoint scoping) with a simplified 3-layer security model: gating -> attenuation -> Docker confinement. Key changes: - SKILL.md format (YAML frontmatter + markdown prompt) replaces skill.toml + prompt.md - 2-state trust (Installed/Trusted) replaces 3-tier (Community/Verified/Local) - New parser.rs for SKILL.md parsing with serde_yaml - New gating.rs for requirements checking (bins/env/config) - Simplified registry with 2-location discovery (workspace + user dirs) - Removed scanner, behavioral_analyzer, enforcer, http_scoping (~4,100 lines) - Removed skill_permissions propagation through job/orchestrator/worker pipeline - Added serde_yaml dependency for YAML frontmatter parsing Net: -5,298 lines, 59 skills tests pass, 907 total tests pass. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add in-app skill management tools and ClawHub catalog integration Add 4 chat-callable tools (skill_list, skill_search, skill_install, skill_remove) plus matching web gateway endpoints for managing skills at runtime. The catalog fetches from ClawHub's public registry API at runtime rather than bundling entries at compile time. Key changes: - SkillRegistry gains mutation methods (install_skill, remove_skill, reload, find_by_name) with Arc<RwLock> for concurrent access - New catalog module queries ClawHub /api/v1/search with in-memory caching (5-min TTL, configurable via CLAWHUB_REGISTRY env var) - skill_list and skill_search added to READ_ONLY_TOOLS for safe use under Installed trust ceiling - Web gateway gets /api/skills, /api/skills/search, /api/skills/install, and /api/skills/{name} DELETE endpoints Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR #51 review feedback from ilblackdragon Security: - Add SSRF protection to fetch_skill_content: require HTTPS, reject private/loopback/link-local IPs and internal hostnames, disable redirects. Gateway install handler now reuses the same validation. - URL-encode slug in skill_download_url to prevent query injection. - Require X-Confirm-Action header on gateway skill install/remove endpoints (equivalent to chat tool requires_approval gate). Correctness: - Eliminate all block_in_place/block_on usage in skill tools and gateway handlers. Split install into prepare_install_to_disk (static async, no lock) + commit_install (sync, brief write lock). Same pattern for remove: validate_remove + delete_skill_files + commit_remove. - Write normalized content to disk in install_skill (was writing original un-normalized content, causing hash mismatch on re-read). - Fix token estimation from 0.75 to 0.25 tokens/byte (~4 chars per token) in registry.rs, selector.rs, and standalone loader. Dependencies: - Replace deprecated serde_yaml 0.9 with serde_yml 0.0.12. - Remove unused toml dependency. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
a158eee1b0 |
feat: 10 infrastructure improvements from zeroclaw (#126)
* refactor: break up agent_loop.rs into four focused modules Split the monolithic 2835-line agent_loop.rs into: - agent_loop.rs (722L): Agent struct, event loop, message dispatch - dispatcher.rs (635L): Agentic tool loop, tool execution, auth detection - commands.rs (484L): System commands, job handlers, heartbeat, summarize - thread_ops.rs (1059L): Thread lifecycle, approval, undo/redo, persistence Each module gets its own impl Agent block. Agent fields changed to pub(super) so sibling modules in the agent package can access them. All 16 existing tests pass in their new locations. Inspired by ZeroClaw's agent module split (agent.rs, loop_.rs, dispatcher.rs, prompt.rs, memory_loader.rs). Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add cost caps and guardrails for autonomous agent spending Daily budget (MAX_COST_PER_DAY_CENTS) and hourly action rate (MAX_ACTIONS_PER_HOUR) limits prevent runaway agents from burning through API credits, especially in daemon/heartbeat modes. - CostGuard with pre-flight check and post-call recording - Sliding window for hourly rate, midnight-UTC daily reset - 80% threshold warning, atomic fast-path for exceeded budget - Wired into dispatcher loop (check before LLM call, record after) Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add circuit breaker on LLM providers Wraps LlmProvider with a Closed/Open/HalfOpen state machine that trips after consecutive transient failures, preventing request storms against a degraded backend. Automatically probes for recovery. - CircuitBreakerProvider implements LlmProvider (drop-in wrapper) - Transient error classification (server, rate-limit, network, auth infra) - Client errors (wrong model, context overflow) don't trip the breaker - Configurable via CIRCUIT_BREAKER_THRESHOLD and CIRCUIT_BREAKER_RECOVERY_SECS - Composes with existing FailoverProvider (circuit breaker wraps failover) - 12 tests covering full state machine and error classification Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add tunnel abstraction for remote access Trait-based tunnel system with lifecycle management (start/stop/health) for exposing the agent to the internet through external tunnel binaries. Five providers: - Cloudflare Tunnel (cloudflared, Zero Trust token auth) - Tailscale (serve for tailnet, funnel for public) - ngrok (with optional custom domain) - Custom (arbitrary command with {host}/{port} placeholders) - None (local-only, no external exposure) Config via TUNNEL_PROVIDER + provider-specific env vars. Extends existing TunnelConfig with optional managed provider alongside the static TUNNEL_URL path. Factory, shared process management, and 37 tests covering all providers and edge cases. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add OS service management (launchd/systemd) Adds `ironclaw service {install,start,stop,status,uninstall}` for running the agent as a background daemon. macOS uses launchd plists under ~/Library/LaunchAgents, Linux uses systemd user units. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add observability trait system with noop, log, and multi backends Introduces an Observer trait for recording agent lifecycle events and metrics, with pluggable backends. The noop backend compiles to zero overhead, log backend uses tracing, and multi fans out to multiple observers. Configured via OBSERVABILITY_BACKEND env var. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add in-memory LLM response cache with TTL and LRU eviction CachedProvider wraps any LlmProvider and caches complete() responses keyed by SHA-256(model + messages). Tool-calling requests are never cached since they trigger side effects. Configurable via RESPONSE_CACHE_ENABLED, RESPONSE_CACHE_TTL_SECS, and RESPONSE_CACHE_MAX_ENTRIES env vars. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add memory hygiene with cadence-gated daily log cleanup Adds workspace::hygiene module that automatically deletes daily log documents older than a configurable retention period (default 30 days). Runs on a 12-hour cadence tracked via a local state file to avoid redundant passes. Best-effort design: failures are logged, never fatal. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add doctor diagnostics command for active health probing Probes external dependencies (Docker, cloudflared, ngrok, tailscale), validates NEAR AI session, checks database connectivity, and verifies workspace directory. Complements the passive `status` command. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: add structured TOML config file support Adds ~/.ironclaw/config.toml as a configuration layer between env vars and database settings. Priority: env var > TOML file > DB > defaults. - `ironclaw config init` generates a commented config.toml from current settings - `ironclaw --config path/to/config.toml` loads a custom config file - Settings.merge_from() only overlays non-default values from the TOML file - `ironclaw config path` now shows TOML file status Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address codex review findings - apply_toml_overlay now returns Result and errors on explicit missing or invalid config paths (was log-only, violating the documented contract that explicit paths are fatal) - custom tunnel url_pattern is now used to filter extracted URLs, not just as a gate for scanning stdout - systemd ExecStart path is now quoted to handle spaces in paths Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review feedback - Cache key now includes max_tokens, temperature, and stop_sequences so different request parameters produce distinct keys - to_cents() uses .trunc() + parse::<u64> instead of f64 intermediary, avoiding precision loss for large values - Tailscale public URL no longer includes local port (serve/funnel expose on standard HTTPS port 443) Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: wire up tunnel lifecycle and fix audit findings Connect the tunnel module to the rest of the application so that setting TUNNEL_PROVIDER actually starts a managed tunnel at boot and stops it on shutdown. Previously create_tunnel() was never called outside tests. Changes: - Expand TunnelSettings with provider credential fields (settings.rs) - TunnelConfig::resolve() falls back to DB settings when env vars unset - Start tunnel at boot, stop on shutdown, show URL in boot screen - Setup wizard collects provider-specific credentials (ngrok, cloudflare, tailscale, custom, static URL) - Fix public_url() returning None under lock contention (SharedUrl) - Fix local_host parameter ignored by cloudflare/ngrok/tailscale - Fix tailscale silent fallback to "localhost" on bad JSON - Fix ngrok globally mutating config via add-authtoken (use env var) - Add 10s timeout to tailscale status --json Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review comments - Document split_whitespace limitation in CustomTunnel doc comment - Remove unnecessary quotes from systemd ExecStart directive Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review feedback (round 3) - doctor: missing libSQL DB on fresh install is Pass, not Fail - service: quote ExecStart path for systemd space handling Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: correct cost guard doc comment (LLM calls, not LLM/tool) Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
e499795b8c |
fix: undo() peeks without popping, breaking repeated undo and leaking redo stack (#71)
* fix: undo() peeks without popping, breaking repeated undo and leaking redo stack undo() used self.undo_stack.back() (peek) instead of pop_back(), so repeated undo always returned the same checkpoint while pushing to the redo stack unboundedly. Additionally, redo() did not save the current state to the undo stack, breaking the undo/redo cycle. Changes: - undo(): change back() to pop_back(), return owned Checkpoint - redo(): accept current_turn/current_messages params, save current state to undo stack before popping from redo stack - Update process_undo/process_redo callers in agent_loop.rs - Add tests for repeated undo, undo/redo cycling, stack size invariant * fix: standardize lock ordering and extract push_undo helper Address review feedback: - Standardize lock order (Session before UndoManager) in process_undo and process_redo to match process_user_input and prevent deadlocks - Extract push_undo() helper to deduplicate push-and-trim logic shared by checkpoint() and redo() * docs: add move-semantics notes and stack invariant to UndoManager Address review feedback requesting documentation about the ownership semantics of undo/redo parameters and the stack size invariant. --------- Co-authored-by: Yi LIU <[email protected]> Co-authored-by: firat.sertgoz <[email protected]> |
||
|
|
5e1da4827a |
fix: check Content-Length before downloading HTTP response body (#74)
* fix: check Content-Length before downloading HTTP response body The HTTP tool previously downloaded the entire response body into memory before checking the size limit, allowing a malicious server to cause OOM. Now the Content-Length header is checked first to reject obviously oversized responses, and the body is streamed with a hard size cap so reading stops as soon as the limit is exceeded. * fix: check chunk size before allocation and fix Content-Length parsing Address review feedback: - Check body.len() + chunk.len() before extend_from_slice to prevent OOM from a single oversized chunk - Use let-chain for Content-Length parsing instead of unwrap_or to gracefully handle invalid headers * docs: document MAX_RESPONSE_SIZE rationale and add tracing on rejection Address review feedback: explain why 5 MB was chosen for the response size limit and log a warning when Content-Length causes early rejection. --------- Co-authored-by: Yi LIU <[email protected]> |
||
|
|
d04af5cd75 |
web: add integrity check for marked CDN and cap highlight regex input (#109)
* web: add integrity check for marked CDN and cap highlight regex input * web: normalize memory search query before snippet+highlight matching * web: place memory query length constant with top-level config --------- Co-authored-by: Clawyered <[email protected]> Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
956037c4d3 |
llm: fallback to legacy nearai.session key when loading DB session (#111)
* llm: fallback to legacy nearai.session when loading DB session * llm: simplify session fallback load with if-let form --------- Co-authored-by: Clawyered <[email protected]> Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
68a1851c19 |
feat: add cooldown management to FailoverProvider (#114)
Track per-provider failure state with lock-free atomics and temporarily skip providers that have repeatedly failed with retryable errors. This reduces latency when a provider is known to be down, instead of wasting time on every request trying all providers sequentially. - Add CooldownConfig (duration + threshold) and ProviderCooldown (atomics) - Rewrite try_providers() to skip cooled-down providers, with a safety net that always tries the oldest-cooled provider if all are down - Add 2 env vars: LLM_FAILOVER_COOLDOWN_SECS, LLM_FAILOVER_THRESHOLD - Add MultiCallMockProvider and 7 new test cases - Mark "Cooldown management" as complete in FEATURE_PARITY.md Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
6783cba4e4 |
feat: move per-invocation approval check into Tool trait (#119)
* feat: move per-invocation approval check into Tool trait (#94) Move shell-specific destructive command detection out of agent_loop.rs into a new `requires_approval_for(params)` method on the Tool trait. ShellTool overrides it to check for destructive patterns (rm -rf, git push --force, etc.) while the default delegates to `requires_approval()`. This follows the project's tool architecture principle of keeping tool-specific logic out of the main agent codebase, and enables other tools to implement per-invocation gating without modifying the agent loop. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: requires_approval_for default should return false, not self.requires_approval() The previous default broke auto-approval for all tools: since requires_approval_for() delegated to requires_approval(), any auto-approved tool would have its auto-approval immediately overridden on every invocation. The correct semantic is: - requires_approval(): "Does this tool use the approval system?" - requires_approval_for(params): "Should this invocation override auto-approval?" The default for the latter must be false (allow auto-approval). ShellTool's fallback for safe commands is also changed to false. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |