The generic `API_KEY` environment variable unconditionally overwrote the
api_key loaded from config.toml, even when a valid key was already
configured. Since `API_KEY` is a very common env var name set by many
unrelated tools, this caused silent auth failures when the unrelated
value was sent to the configured provider.
Change the precedence so that `ZEROCLAW_API_KEY` always wins (explicit
intent), while `API_KEY` is only used as a fallback when the config has
no api_key set.
- Switch from sessionWebhook to /v1.0/robot/oToMessages/batchSend API
- Add access_token caching with automatic refresh (60s buffer)
- Enable cron job delivery to DingTalk (no user interaction required)
This change allows DingTalk to actively send messages (e.g., cron
reminders) without requiring the user to send a message first.
Add SkillToolHandler that converts SKILL.toml definitions into native
tool schemas, enabling skills to be invoked as standard tools through
the agent's tool-use protocol.
Made-with: Cursor
- Replace brittle split("state=") with parse_query_params utility
- Use const PROFILE_MISMATCH_PREFIX with starts_with instead of fragile contains
Made-with: Cursor
Add stale pending login detection (auto-cleanup after 24h), improved
device-code flow error messages with Cloudflare/403 detection, shared
OAuth helpers, and Box::pin fixes for large async futures.
Made-with: Cursor
Add `hooks: Option<&crate::hooks::HookRunner>` as the last parameter
to the public `agent::run()` (re-exported from `loop_::run`).
This enables library consumers to inject custom HookHandler
implementations (before_tool_call, on_after_tool_call) without
patching the crate. The hooks are threaded through to
`run_tool_call_loop` which already accepts and dispatches them.
All existing call sites pass `None`, preserving backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds async background tool execution with auto-injection of completed results:
- BgRunTool: Dispatches any tool in background, returns job_id immediately
- BgStatusTool: Queries job status by ID or lists all jobs
- BgJobStore: In-memory job tracking per session
- Auto-injection: Completed jobs appear as <bg_result> XML in agent history
Security hardening (Track C):
- MAX_CONCURRENT_JOBS=5 prevents resource exhaustion
- XML escaping prevents injection attacks in format_bg_result_for_injection
- Recursion guard blocks bg_run spawning itself or bg_status
- Hard 600s timeout per job guaranteed
- One-time delivery prevents duplicate injection
- 5-minute auto-expiry bounds memory growth
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CachedCredentials with 50-minute TTL that transparently refreshes
from the ECS container credential endpoint, env vars, or EC2 IMDS.
- Add from_ecs() to credential resolve chain for ECS/Fargate support
- Move streaming credential fetch into async context for TTL validation
- Remove sync credential fallback (all paths now use TTL-aware cache)
- Double-checked locking prevents thundering herd on refresh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 14 test functions for ACP channel (allowlist logic and JSON-RPC structures)
- Fix mutex guard across await in send() method using take() pattern
- Add acp: None default fields to ChannelsConfig in schema.rs
- Integrate ACP channel into channels/mod.rs and collect_configured_channels()
- Update channels-reference.md documentation
- Resolve merge conflicts with upstream/dev
All 17 ACP tests pass successfully.
- add "channel.bluebubbles" to SUPPORTED_PROXY_SERVICE_KEYS so proxy
scope = "services" can target BlueBubbles via exact service key
(addresses final CodeRabbit finding on PR #2271)
- apply cargo fmt to auth_profile.rs and quota_tools.rs (pre-existing
formatting drift that would block cargo fmt --check in CI)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(web-fetch): remove dead feature gates, add noise stripping, add docstrings
The nanohtml2text and fast_html2md providers were both guarded by
cfg(feature) checks for features (web-fetch-plaintext, web-fetch-html2md)
that are never declared in Cargo.toml. This caused every web_fetch call
to silently return an error instead of fetching content.
Changes:
- Add strip_noise_elements() which removes <script>, <style>, <nav>,
<header>, <footer>, <aside>, <noscript>, <form>, <button> blocks
before text extraction, eliminating menu/ad/boilerplate noise.
- Fix fast_html2md path: when web-fetch-html2md feature is not compiled
in, fall through to nanohtml2text rather than returning an error.
- Fix nanohtml2text path: remove dead cfg(feature = "web-fetch-plaintext")
gate; nanohtml2text is a direct dependency and needs no feature flag.
- Both previously gated tests (html_to_markdown_conversion_preserves_structure,
html_to_plaintext_conversion_removes_html_tags) are now always-on.
Added strip_noise_removes_nav_scripts_footer test.
- Add docstrings to all public/private methods to meet coverage threshold.
Tavily and firecrawl providers are unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(web-fetch): align default provider to nanohtml2text, remove dead feature
- Change empty-provider default from deprecated 'fast_html2md' to
'nanohtml2text' to match WEB_FETCH_PROVIDER_HELP and PR description.
- Remove dead 'web-fetch-plaintext' feature from Cargo.toml (no code
references it after the feature-gate removal).
- Apply cargo fmt to strip_noise_elements array formatting.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: xj <gh-xj@users.noreply.github.com>
The Copilot API proxy for Claude models (Opus 4.6, Opus 4.6-1m) splits
text content and tool_calls into separate choices. Previously only
choices[0] was read, causing all tool calls to be silently dropped
when they appeared in choices[1].
Merge text and tool_calls from all choices so tool calling works
regardless of how the proxy splits the response.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SQLite WAL mode requires shared-memory (mmap/shm) which is unavailable
on many network and virtual shared filesystems (NFS, SMB/CIFS,
UTM/VirtioFS, VirtualBox shared folders), causing xShmMap I/O errors
at startup.
Add `sqlite_journal_mode` config option under `[memory]` that accepts
"wal" (default) or "delete". When set to "delete", SQLite uses the
legacy DELETE journal mode and disables mmap, allowing ZeroClaw to run
with workspaces on shared/network filesystems.
Usage:
[memory]
sqlite_journal_mode = "delete"
Changes:
- config/schema.rs: Add sqlite_journal_mode field to MemoryConfig
- memory/sqlite.rs: Add with_options() supporting journal mode selection
- memory/mod.rs: Pass journal_mode from config to SqliteMemory
- onboard/wizard.rs: Include new field in default MemoryConfig
- Register 4 new tools (ManageAuthProfileTool, CheckProviderQuotaTool,
SwitchProviderTool, EstimateQuotaCostTool) in all_tools_with_runtime
- SwitchProviderTool now loads config from disk and calls save() to
persist default_provider/default_model to config.toml
- Inject Provider & Budget Context section into system prompt when
Config is available
- Remove emoji from tool output for cleaner parsing
- Replace format! push_str with std::fmt::Write for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire QuotaMetadata into ChatResponse for all provider implementations,
enabling quota tracking data to flow from API responses through the
agent loop to quota monitoring tools.
Depends on: circuit breaker (#1842) + quota monitoring (#1904)
Made-with: Cursor
WebDriver's execute() wraps the script as a function body. The snapshot
script used an IIFE without a top-level return, so the IIFE's return
value was discarded and the WebDriver function returned undefined (null).
All other execute() calls in the file (scroll, scrollIntoView, click)
correctly use explicit return statements.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>