Ports remaining changes from feat/unify-web-fetch-providers that were
not yet integrated into dev:
- config/schema.rs: add `user_agent` field (default "ZeroClaw/1.0") to
HttpRequestConfig, WebFetchConfig, and WebSearchConfig, with a shared
default_user_agent() helper. Field is serde-default so existing configs
remain backward compatible.
- tools/http_request.rs: accept user_agent in constructor; pass it to
reqwest::Client via .user_agent() replacing the implicit default.
- tools/web_fetch.rs: accept user_agent in constructor; replace hardcoded
"ZeroClaw/0.1 (web_fetch)" in build_http_client with the configured value.
- tools/web_search_tool.rs: accept user_agent in constructor; replace
hardcoded Chrome UA string in search_duckduckgo and add .user_agent()
to the Brave and Firecrawl client builders.
- tools/mod.rs: wire user_agent from each config struct into the
corresponding tool constructor (HttpRequestTool, WebFetchTool,
WebSearchTool).
- onboard/wizard.rs: add setup_web_tools() as wizard Step 6 "Web &
Internet Tools" (total steps bumped from 9 to 10). Configures
WebSearchConfig, WebFetchConfig, and HttpRequestConfig interactively
with provider selection and optional API key/URL prompts. Step 5
setup_tool_mode() http_request and web_search outputs are now discarded
(_, _) since step 6 owns that configuration. Uses dev's generic
api_key/api_url schema fields unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit fb83da8db021903cf5844852bdb67b9b259941d7)
`supports_vision` is currently hardcoded per-provider. The same Ollama instance can run `llava` (vision) or
`codellama` (no vision), but the code fixes vision support at the provider level with no user override.
This adds a top-level `model_support_vision: Option<bool>` config key — tri-state:
- **Unset (default):** provider's built-in value, zero behavior change
- **`true`:** force vision on (e.g. Ollama + llava)
- **`false`:** force vision off
Follows the exact same pattern as `reasoning_enabled`. Override is applied at the wrapper layer (`ReliableProvider` /
`RouterProvider`) — no concrete provider code is touched.
## Changes
**Config surface:**
- Top-level `model_support_vision` field in `Config` struct with `#[serde(default)]`
- Env override: `ZEROCLAW_MODEL_SUPPORT_VISION` / `MODEL_SUPPORT_VISION`
**Provider wrappers (core logic):**
- `ReliableProvider`: `vision_override` field + `with_vision_override()` builder + `supports_vision()` override
- `RouterProvider`: same pattern
**Wiring (1-line each):**
- `ProviderRuntimeOptions` struct + factory functions
- 5 construction sites: `loop_.rs`, `channels/mod.rs`, `gateway/mod.rs`, `tools/mod.rs`, `onboard/wizard.rs`
**Docs (i18n parity):**
- `config-reference.md` — Core Keys table
- `providers-reference.md` — new "Ollama Vision Override" section
- Vietnamese sync: `docs/i18n/vi/` + `docs/vi/` (4 files)
## Non-goals
- Does not change any concrete provider implementation
- Does not auto-detect model vision capability
## Test plan
- [x] `cargo fmt --all -- --check`
- [x] `cargo clippy --all-targets -- -D warnings` (no new errors)
- [x] 5 new tests passing:
- `model_support_vision_deserializes` — TOML parse + default None
- `env_override_model_support_vision` — env var override + invalid value ignored
- `vision_override_forces_true` — ReliableProvider override
- `vision_override_forces_false` — ReliableProvider override
- `vision_override_none_defers_to_provider` — passthrough behavior
## Risk and Rollback
- **Risk:** Low. `None` default = zero behavior change for existing users.
- **Rollback:** Revert commit. Field is `#[serde(default)]` so old configs without it will deserialize fine.
(cherry picked from commit a1b8dee785)
Extract shared init logic (pragmas, schema creation, agent registration)
into IpcDb::init(), eliminating ~45 lines of duplication between open()
and open_with_id(). Extract SQL strings into PRAGMA_SQL and SCHEMA_SQL
constants for single source of truth. Remove unused (i64, Value) tuple
in AgentsInboxTool by collecting directly into Vec<Value>.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 5 LLM-callable IPC tools (agents_list, agents_send, agents_inbox,
state_get, state_set) backed by a shared SQLite database, enabling
independent ZeroClaw processes on the same host to discover and
communicate with each other. Gated behind [agents_ipc] enabled = true.
Related #88 (item 3: Sessions / Sub-Agent Orchestration)
Related #1518 (design spec)
- Base branch target: dev
- Problem: ZeroClaw agents have no structured way to decompose complex tasks into trackable steps, falling behind
every comparable agent runtime
- Why it matters: Without task tracking, multi-step work is fragile (lost on context compression), invisible to users
(no progress signal), and error-prone (agent loses track of what's done vs. pending)
- What changed: Added a session-scoped task_plan tool with create/add/update/list/delete actions, integrated with
SecurityPolicy, registered in the tool factory
- What did not change: No config schema changes, no persistence layer, no CLI subcommand, no changes to agent loop or
any other subsystem
Label Snapshot
- Risk label: risk: low
- Size label: size: S
- Scope labels: tool
- Module labels: tool: task_plan
- Contributor tier label: (auto-managed)
- If any auto-label is incorrect: N/A
Change Metadata
- Change type: feature
- Primary scope: tool
Linked Issue
- Closes #(issue number)
- Related: N/A
- Depends on: N/A
- Supersedes: N/A
Supersede Attribution
N/A — no superseded PRs.
Validation Evidence
cargo fmt --all -- --check # pass (no output)
cargo clippy --all-targets -- -D warnings # task_plan.rs: 0 warnings (pre-existing warnings in other files
unrelated)
cargo test --lib tools::task_plan # 15/15 passed
- Evidence provided: test output (15 passed, 0 failed)
- If any command is intentionally skipped: cargo clippy reports pre-existing warnings in unrelated files
(onboard/wizard.rs etc.); task_plan.rs itself has zero clippy warnings
Security Impact
- New permissions/capabilities? No — uses existing ToolOperation::Act enforcement
- New external network calls? No
- Secrets/tokens handling changed? No
- File system access scope changed? No
Privacy and Data Hygiene
- Data-hygiene status: pass
- Redaction/anonymization notes: No identity data in code or tests. Test fixtures use neutral strings ("step one",
"do thing", "first")
- Neutral wording confirmation: All naming follows ZeroClaw/project-native conventions
Compatibility / Migration
- Backward compatible? Yes
- Config/env changes? No
- Migration needed? No
i18n Follow-Through
- i18n follow-through triggered? No — no docs or user-facing wording changes
Human Verification
- Verified scenarios: Ran ./target/debug/zeroclaw agent -m "调用 task_plan 工具,action=list" — agent correctly
identified and called task_plan, returned "No tasks."
- Edge cases checked: read-only mode blocks mutations, empty task list, invalid action names, missing required
parameters, create replaces existing list, ID auto-increment after add
- What was not verified: Behavior with non-CLI channels (Telegram, Discord); behavior with XML-fallback dispatcher
(non-native-tool providers)
Side Effects / Blast Radius
- Affected subsystems/workflows: src/tools/ only — tool factory gains one additional entry
- Potential unintended effects: Marginally increases tool spec payload size sent to LLM (one more tool definition).
Could theoretically cause tool name confusion with schedule if LLM descriptions are ambiguous — mitigated by distinct
naming (task_plan vs schedule) and different description wording.
- Guardrails/monitoring for early detection: Standard tool dispatch logging. Tool is session-scoped so no persistent
side effects on failure.
Agent Collaboration Notes
- Agent tools used: Claude Code for implementation assistance and review
- Workflow/plan summary: Implement Tool trait → register in factory → validate with tests → manual agent session test
- Verification focus: Security policy enforcement, parameter validation edge cases, all 5 action paths
- Confirmation: naming + architecture boundaries followed (CLAUDE.md §6.3, §6.4, §7.3): Yes
Rollback Plan
- Fast rollback command/path: git revert <commit> — removes 3 lines from mod.rs and deletes task_plan.rs
- Feature flags or config toggles: None needed — tool is stateless and session-scoped
- Observable failure symptoms: Tool not appearing in agent tool list, or tool returning errors on valid input
Risks and Mitigations
- Risk: LLM may occasionally confuse task_plan (action: list) with schedule (action: list) due to similar parameter
structure
- Mitigation: Distinct tool names and descriptions; task_plan description emphasizes "session checklist" while
schedule emphasizes "cron/recurring tasks"
Scheduled jobs created via channel conversations (Discord, Telegram, etc.)
never delivered output back to the channel because:
1. The agent had no channel context (channel name + reply_target) in its
system prompt, so it could not populate the delivery config.
2. The schedule tool only creates shell jobs with no delivery support,
and the cron_add tool's delivery schema was opaque.
3. OpenAiCompatibleProvider was missing the native_tool_calling field,
causing a compile error.
Changes:
- Inject channel context (channel name + reply_target) into the system
prompt so the agent knows how to address delivery when scheduling.
- Improve cron_add tool description and delivery parameter schema to
guide the agent toward correct delivery config.
- Update schedule tool description to warn that output is only logged
and redirect to cron_add for channel delivery.
- Fix missing native_tool_calling field in OpenAiCompatibleProvider.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(composio): fix v3 compatibility with parameter discovery, NLP text execution, and error enrichment
Three-layer fix for the Composio v3 API compatibility issue where the LLM
agent cannot discover parameter schemas, leading to repeated guessing and
execution failures.
Layer 1 – Surface parameter hints in list output:
- Add input_parameters field to ComposioV3Tool and ComposioAction structs
- Pass through input_parameters from v3 list response via map_v3_tools_to_actions
- Add format_input_params_hint() to show required/optional param names in list output
Layer 2 – Support natural-language text execution:
- Add text parameter to tool schema (mutually exclusive with params)
- Thread text through execute handler → execute_action → execute_action_v3
- Update build_execute_action_v3_request to send text instead of arguments
- Skip v2 fallback when text-mode is used (v2 has no NLP support)
Layer 3 – Enrich execute errors with parameter schema:
- Add get_tool_schema() to fetch full tool metadata from GET /api/v3/tools/{slug}
- Add format_schema_hint() to render parameter names, types, and descriptions
- On execute failure, auto-fetch schema and append to error message
Root cause: The v3 API returns input_parameters in list responses but
ComposioV3Tool was silently discarding them. The LLM had no way to discover
parameter schemas before calling execute, and error messages provided no
remediation guidance — creating an infinite guessing loop.
Co-Authored-By: unknown <>
(cherry picked from commit fd92cc5eb0)
* fix(composio): use floor_char_boundary for safe UTF-8 truncation in format_schema_hint
Co-Authored-By: unknown <>
(cherry picked from commit 18e72b6344)
* fix(composio): restore coherent v3 execute flow after replay
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Problem: The existing http_request tool returns raw HTML/JSON, which is nearly unusable for LLMs to extract
meaningful content from web pages.
- Why it matters: All mainstream AI agents (Claude Code, Gemini CLI, Aider) have dedicated web content extraction
tools. ZeroClaw lacks this capability, limiting its ability to research and gather information from the web.
- What changed: Added a new web_fetch tool that fetches web pages and converts HTML to clean plain text using
nanohtml2text. Includes domain allowlist/blocklist, SSRF protection, redirect following, and content-type aware
processing.
- What did not change (scope boundary): http_request tool is untouched. No shared code extracted between http_request
and web_fetch (DRY rule-of-three: only 2 callers). No changes to existing tool behavior or defaults.
Label Snapshot (required)
- Risk label: risk: medium
- Size label: size: M
- Scope labels: tool, config
- Module labels: tool: web_fetch
- If any auto-label is incorrect, note requested correction: N/A
Change Metadata
- Change type: feature
- Primary scope: tool
Linked Issue
- Closes #
- Related #
- Depends on #
- Supersedes #
Supersede Attribution (required when Supersedes # is used)
N/A
Validation Evidence (required)
cargo fmt --all -- --check # pass
cargo clippy --all-targets -- -D warnings # no new warnings (pre-existing warnings only)
cargo test --lib -- web_fetch # 26/26 passed
cargo test --lib -- tools::tests # 12/12 passed
cargo test --lib -- config::schema::tests # 134/134 passed
- Evidence provided: unit test results (26 new tests), manual end-to-end test with Ollama + qwen2.5:72b
- If any command is intentionally skipped, explain why: Full cargo clippy --all-targets has 43 pre-existing errors
unrelated to this PR (e.g. await_holding_lock, format! appended to String). Zero errors from web_fetch code.
Security Impact (required)
- New permissions/capabilities? Yes — new web_fetch tool can make outbound HTTP GET requests
- New external network calls? Yes — fetches web pages from allowed domains
- Secrets/tokens handling changed? No
- File system access scope changed? No
- If any Yes, describe risk and mitigation:
- Deny-by-default: enabled = false by default; tool is not registered unless explicitly enabled
- Domain filtering: allowed_domains (default ["*"] = all public hosts) + blocked_domains (takes priority).
Blocklist always wins over allowlist.
- SSRF protection: Blocks localhost, private IPs (RFC 1918), link-local, multicast, reserved ranges, IPv4-mapped
IPv6, .local TLD — identical coverage to http_request
- Rate limiting: can_act() + record_action() enforce autonomy level and rate limits
- Read-only mode: Blocked when autonomy is ReadOnly
- Response size cap: 500KB default truncation prevents context window exhaustion
- Proxy support: Honors [proxy] config via tool.web_fetch service key
Privacy and Data Hygiene (required)
- Data-hygiene status: pass
- Redaction/anonymization notes: No personal data in code, tests, or fixtures
- Neutral wording confirmation: All test identifiers use neutral project-scoped labels
Compatibility / Migration
- Backward compatible? Yes — new tool, no existing behavior changed
- Config/env changes? Yes — new [web_fetch] section in config.toml (all fields have defaults)
- Migration needed? No — #[serde(default)] on all fields; existing configs without [web_fetch] section work unchanged
i18n Follow-Through (required when docs or user-facing wording changes)
- i18n follow-through triggered? No — no docs or user-facing wording changes
Human Verification (required)
- Verified scenarios:
- End-to-end test: zeroclaw agent with Ollama qwen2.5:72b successfully called web_fetch to fetch
https://github.com/zeroclaw-labs/zeroclaw, returned clean plain text with project description, features, star count
- Tool registration: tool_count increased from 22 to 23 when enabled = true
- Config: enabled = false (default) → tool not registered; enabled = true → tool available
- Edge cases checked:
- Missing [web_fetch] section in existing config.toml → works (serde defaults)
- Blocklist priority over allowlist
- SSRF with localhost, private IPs, IPv6
- What was not verified:
- Proxy routing (no proxy configured in test environment)
- Very large page truncation with real-world content
Side Effects / Blast Radius (required)
- Affected subsystems/workflows: all_tools_with_runtime() signature gained one parameter (web_fetch_config); all 5
call sites updated
- Potential unintended effects: None — new tool only, existing tools unchanged
- Guardrails/monitoring for early detection: enabled = false default; tool_count in debug logs
Agent Collaboration Notes (recommended)
- Agent tools used: Claude Code (Opus 4.6)
- Workflow/plan summary: Plan mode → approval → implementation → validation
- Verification focus: Security (SSRF, domain filtering, rate limiting), config compatibility, tool registration
- Confirmation: naming + architecture boundaries followed (CLAUDE.md + CONTRIBUTING.md): Yes — trait implementation +
factory registration pattern, independent security helpers (DRY rule-of-three), deny-by-default config
Rollback Plan (required)
- Fast rollback command/path: git revert <commit>
- Feature flags or config toggles: [web_fetch] enabled = false (default) disables completely
- Observable failure symptoms: tool_count in debug logs drops by 1; LLM cannot call web_fetch
Risks and Mitigations
- Risk: SSRF bypass via DNS rebinding (attacker-controlled domain resolving to private IP)
- Mitigation: Pre-request host validation blocks known private/local patterns. Same defense level as existing
http_request tool. Full DNS-level protection would require async DNS resolution before connect, which is out of scope
for this PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit 04597352cc)
* ci(homebrew): prefer HOMEBREW_UPSTREAM_PR_TOKEN with fallback
* ci(homebrew): handle existing upstream remote and main base
* feat(tools): Use system default browser instead of hard-coded Brave Browser
---------
Co-authored-by: Will Sarg <12886992+willsarg@users.noreply.github.com>
When max_response_size is set to 0, the condition `text.len() > 0` is
true for any non-empty response, causing all responses to be truncated
to empty strings. The conventional meaning of 0 for size limits is
"no limit" (matching ulimit, nginx client_max_body_size, curl, etc.).
Add an early return when max_response_size == 0 and update the doc
comment to document this behavior.
Thinking/reasoning models (Kimi K2.5, GLM-4.7, DeepSeek-R1) return a
reasoning_content field in assistant messages containing tool calls.
ZeroClaw was silently dropping this field when constructing conversation
history, causing provider APIs to reject follow-up requests with 400
errors: "thinking is enabled but reasoning_content is missing in
assistant tool call message".
Add reasoning_content: Option<String> as an opaque pass-through at every
layer of the pipeline: ChatResponse, ConversationMessage, NativeMessage
structs, parse/convert/build functions, and dispatcher. The field is
skip_serializing_if = None so it is invisible for non-thinking models.
Closes#1327