* feat: add smart routing provider for cost-optimized model selection
Route simple tasks (greetings, status checks, short questions) to a cheap
model (e.g. Haiku) and complex tasks (code generation, analysis) to the
primary model, reducing agent costs without sacrificing quality.
Activates automatically when NEARAI_CHEAP_MODEL is set. Cascade mode
retries uncertain cheap-model responses with the primary model.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: apply cargo fmt formatting
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: extract provider chain into shared build_provider_chain()
Consolidate the duplicated LLM provider chain construction from main.rs
and app.rs into a single build_provider_chain() function in llm/mod.rs.
This fixes the inconsistency where app.rs was missing retry wrapping
that main.rs had, and ensures both paths apply identical decorators:
retry → smart routing → failover → circuit breaker → cache.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review — uncertainty detection and clippy lint
- Remove false-positive short response (<20 chars) uncertainty check
that would escalate "Yes.", "42" etc. Now only empty responses and
explicit uncertainty phrases trigger cascade escalation.
- Add #[allow(clippy::type_complexity)] to build_provider_chain() to
fix CI clippy -D warnings failure.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Three high-impact changes eliminate most startup latency:
1. Enable wasmtime persistent compilation cache — call
cache_config_load_default() so compiled native code is serialized to
disk (~/.cache/wasmtime). Subsequent startups deserialize instead of
recompiling, dropping the WASM phase from ~13s to <1s.
2. Cache compiled Component in PreparedModule — store the compiled
wasmtime::component::Component directly instead of raw bytes.
Eliminates ~2.6s recompilation on every first tool/channel execution.
3. Move blocking housekeeping to background tasks — embedding backfill
(~1.3s of failing HTTP calls) and stale job cleanup are fire-and-forget
work that no longer blocks the critical startup path.
Also: deduplicate Workspace creation in main.rs (two identical instances
reduced to one), and replace leftover println! in session validation with
tracing calls.
Co-authored-by: Claude Opus 4.6 <[email protected]>
* refactor: consolidate tool approval into single param-aware method
Replace the two confusing approval methods (requires_approval() and
requires_approval_for()) with a single requires_approval(&self, params)
returning a 3-variant ApprovalRequirement enum (Never, UnlessAutoApproved,
Always). This enables param-aware approval decisions: HTTP calls without
auth headers now skip approval entirely, while authenticated requests
always require it. Shell tool merges its destructive-command detection
into the same method.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: add credential injection to built-in HTTP tool
Wire the WASM credential injection system into the built-in HTTP tool
so credentials are auto-injected at the boundary (zero-exposure model).
- Add SharedCredentialRegistry: thread-safe, append-only registry of
credential mappings populated by WASM tools at registration time
- Add credential_detect module with broad auth detection for headers
(12 exact + 5 substring matches), header values (7 auth scheme
prefixes), and URL query params (17 exact + 5 substring matches)
- HttpTool now accepts optional credential registry + secrets store,
auto-injects matching credentials in execute(), and uses broader
auth detection in requires_approval()
- ToolRegistry passes credential registry to HttpTool at startup and
populates it when WASM tools register
- Remove old hardcoded AUTH_HEADER_NAMES / has_auth_headers in favor
of the new params_contain_manual_credentials()
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR #274 review comments (query param injection, lock poisoning, visibility)
- Fix injected query params not being sent on outbound HTTP requests by
also calling .query() on the RequestBuilder alongside parsed_url mutation
- Recover from poisoned RwLock in SharedCredentialRegistry instead of
silently ignoring failures, with tracing::warn for visibility
- Narrow inject_credential and host_matches_pattern to pub(crate) to
avoid committing to them as stable public API
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Show a shield indicator in the tab bar when the instance is running
inside a TEE deployment. On hover, fetches and displays the TDX
attestation report (image digest, TLS cert fingerprint, report data,
VM config) from the management API.
Co-authored-by: Cursor <[email protected]>
Nginx buffers responses by default, breaking SSE connections that go
through a reverse proxy. Add X-Accel-Buffering: no header to chat and
log SSE handlers to match what compose-api and chat-api already do.
* feat: direct agentic loop for SWE-bench benchmarks
Replace the full Agent-based runner with a purpose-built agentic loop
that directly calls the LLM with tools. The old path routed through
SafetyLayer (which blocked SWE-bench prompts), dispatcher (capped at
10 iterations), approval flow (wasted iterations), and 20+ irrelevant
builtin tools (diluted the model's focus).
New architecture:
- AgenticLoop: LLM call -> tool execution -> repeat (up to 30 iters)
- Per-task tool scoping via BenchSuite::task_tools() with working dirs
- Suite-provided system prompts via BenchSuite::system_prompt()
- No safety layer, no approval flow, no sessions/threads overhead
- Configurable max_iterations in BenchConfig and TOML
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: apply --model CLI override to LLM provider
The --model flag was updating matrix entry labels but not the actual
LLM provider, so requests were still sent using the model from .env.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: configurable tool iterations and auto-approve for benchmarks
Add max_tool_iterations and auto_approve_tools settings to AgentConfig,
replacing the hardcoded MAX_TOOL_ITERATIONS constant. Fix shell_injection
policy rule to not block markdown backtick code snippets.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address benchmarks crate audit findings
High:
- Fix truncate_output UTF-8 panic on multi-byte char boundaries
- Fix parallel results durability (write JSONL per-task, not after all)
Medium:
- Fix --sample to use random shuffle instead of first-N
- Delegate all LlmProvider methods in InstrumentedLlm
- Fix LLM-as-judge to return fail instead of misleading 0.5
- Remove unnecessary shallow clone (always gets unshallowed)
- Replace .unwrap() with .expect() in LazyLock regex init
Low:
- Remove dead code: unused error variants, trait methods, struct fields
- Remove BenchSuite::name() (redundant with id())
- Remove TaskSubmission::conversation, ConversationTurn, TurnRole
- Remove unused methods from BenchChannel, results, config
- Clean up ChannelCapture conversation tracking
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: add SWE-bench dataset and Docker scoring infrastructure
Add the SWE-bench Lite dataset (300 tasks) and Docker files for
isolated test execution and scoring of SWE-bench patches.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore: remove benchmarks (extracted to separate repo)
Benchmarks crate has been extracted to its own repository.
Remove the workspace member and all benchmarks/ files.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: add missing AgentConfig fields in test initializer
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* 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]>
* 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]>
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]>
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]>
* ci: add automated PR labeling system
Add two independent workflows for PR auto-labeling:
- Scope labels via actions/labeler (path glob matching)
- Size, risk, and contributor tier via custom shell script
Includes idempotent label bootstrap script (create-labels.sh).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* ci: temporarily use pull_request trigger for testing
Switch to pull_request so workflows run from the PR branch.
Will revert to pull_request_target before merge.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(ci): use absolute path for search/issues API call
gh api requires a leading slash for REST endpoints.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(ci): use gh pr list instead of search API for contributor count
The search/issues API returns 404 with the default GITHUB_TOKEN.
gh pr list --state merged works with standard permissions.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* ci: revert to pull_request_target for fork PR support
Restore pull_request_target trigger and base branch checkout
now that testing is complete.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* 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]>
* 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]>
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.
Agent::new gained an 8th parameter (session_manager) but the benchmark
runner was not updated, breaking compilation of the bench crate.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* 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]>
* 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]>
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.
* 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]>
* 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]>
* 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]>
* 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]>
* 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]>
* 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]>
* 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]>
* 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]>
* 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
* Fix Telegram control commands being stripped
The `clean_message_text()` function was returning an empty string for
bare slash commands like `/interrupt`, `/stop`, `/help`, etc. This
caused the commands to be replaced with "[User started the bot]" placeholder
which broke command parsing in the agent.
Changes:
- Line 1079: Return the command unchanged instead of empty string
- Line 1042: Only replace with placeholder for `/start` specifically
- Add test coverage for control commands
This fixes the issue where `/interrupt` doesn't work when bot is stuck
waiting for approval.
Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
* Add workspace declaration to Telegram package
Fixes workspace conflict when building WASM component standalone.
* Fix content_to_emit logic for bare control commands
Addresses code review feedback: keep clean_message_text() returning
empty for bare commands (its job is to extract user text, not pass
commands through). Instead, fix the caller to distinguish:
- /start (no args) → welcome placeholder
- Other bare /commands → pass raw command to Submission::parse()
- Commands with args → pass cleaned args
- Empty/whitespace → skip
Add comprehensive test_content_to_emit_logic() covering all edge cases
including /start, control commands, args, plain text, and empty input.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: ubuntu <ubuntu@tyo-dev>
Co-authored-by: Claude Sonnet 4.5 <[email protected]>
Co-authored-by: firat.sertgoz <[email protected]>
Co-authored-by: Illia Polosukhin <[email protected]>
* 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]>
Scanned the repo and past two weeks of commits to reconcile the feature
matrix with reality. Upgraded implemented features from ❌ to ✅ (skills,
memory CLI, embeddings batching, session permissions, OpenRouter, Ollama).
Marked partial implementations as 🚧 (agent event broadcast, payload
guard, skill routing, env sanitization). Added new OpenClaw features from
Feb 2025 (Telegram/Discord/Slack-specific, new hooks, security items).
Added IronClaw-only entries (Tinfoil, OpenAI-compatible, GitHub WASM tool).
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat: add issue triage skill
Adds a /triage-issues skill that classifies open GitHub issues into bugs
and feature requests, ranks bugs by severity and features by opportunity,
and flags under-specified issues needing clarification.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review feedback on issue triage skill
- Fix invalid `comments` field to `commentsCount` + add `reactionGroups`
- Correct severity/opportunity max scores from 17 to base 14 (boosted 16)
- Clarify boost is one-time (+2 if any condition matches)
- Add explicit `gh pr list` command for PR exclusion filtering
- Adjust severity/opportunity thresholds in report section
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]>
* 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]>
* feat: add PR triage dashboard skill
Adds /triage-prs slash command that classifies all open PRs by module,
review state, scope, and architectural impact to produce a prioritized
triage dashboard for maintainers.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* fix: address review feedback on triage-prs skill
- Add body and updatedAt to PR query fields for superseded detection
- Use --label/--author flags directly instead of post-filtering
- Use date-based --search for merged PRs instead of --limit 20
- Simplify LLM module listing, add missing module categories
- Use updatedAt for staleness, clarify lines changed metric
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: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* 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]>
The benchmarks crate is an internal tool, not intended for crates.io.
Adding `publish = false` fixes the release-plz CI failure caused by
the path-only ironclaw dependency lacking a version specifier.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Remove unused fields, methods, and error variants. Allow dead_code on
public API types intended for future use. Drop needless Default spread.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* 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]>
* 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]>
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]>
* 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]>
* 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]>
* 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]>
* 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]>