Commit Graph

1250 Commits

Author SHA1 Message Date
Argenis
2880ad5a69
fix(nextcloud_talk): accept "Create" event type for new messages (#3500)
Nextcloud Talk bot webhooks send event type "Create" for new chat
messages, but the parser only accepted "message". This caused all
valid messages to be skipped with "skipping non-message event: Create".

Accept both "message" and "Create" as valid event types.

Closes #3491
2026-03-24 15:15:58 +03:00
Argenis
0964f2180a
feat(providers): add 17 providers, total now 61 — most in any AI agent (#3492)
New fast inference providers:
- Cerebras, SambaNova, Hyperbolic

New model hosting platforms:
- DeepInfra, Hugging Face, AI21 Labs, Reka, Baseten, Nscale,
  Anyscale, Nebius AI Studio, Friendli AI, Lepton AI

New Chinese AI providers:
- Stepfun, Baichuan, 01.AI (Yi), Tencent Hunyuan

Also fixed missing list_providers() entries for Telnyx and Azure OpenAI.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 15:15:57 +03:00
Argenis
d7ae8fdc3d
feat(release+providers): fix release race condition, add 3 providers (#3489)
* feat(release+providers): fix release race condition, add 3 providers

Release fix (two parts):
1. Replace softprops/action-gh-release with `gh release create` — the
   CLI uploads assets atomically with the release in a single call,
   avoiding the immutable release race condition
2. Move website redeploy to a separate job with `if: always()` — so the
   website updates regardless of publish outcome

Both release-beta-on-push.yml and release-stable-manual.yml are fixed.

Provider additions:
- SiliconFlow (siliconflow, silicon-flow)
- AiHubMix (aihubmix)
- LiteLLM router (litellm, lite-llm)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: trigger CI

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 15:15:56 +03:00
guitaripod
5919becab9
fix(telegram): route image-extension Documents through vision pipeline (#3457)
Documents with image extensions (jpg, png, etc.) are routed to
[Document: name] /path instead of [IMAGE:/path], bypassing the
multimodal pipeline entirely. This causes the model to have no vision
input for images sent as Telegram Documents.

Re-applies fix from merged dev PR #1631 which was lost during the
master branch migration.

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-14 09:54:41 -04:00
guitaripod
287d9bdc17
fix(telegram): handle brackets in attachment filenames (#3458)
parse_attachment_markers uses .find(']') which matches the first ]
in the content. Filenames containing brackets (e.g. yt-dlp output
'Video [G4PvTrTp7Tc].mp4') get truncated at the inner bracket,
causing the send to fail with 'path not found'.

Uses depth-tracking bracket matching instead.

Re-applies fix from merged dev PR #1632 which was lost during the
master branch migration.

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-14 09:52:37 -04:00
guitaripod
f900d7079e
feat(channels): add show_tool_calls config to suppress tool notifications (#3480)
* feat(channels): add show_tool_calls config to suppress tool notifications

When show_tool_calls is false, the ChannelNotifyObserver drains tool
events silently instead of forwarding them as individual messages to
the channel. Server-side logs remain unaffected.

Defaults to true for backwards compatibility.

* docs: add before/after screenshots for show_tool_calls PR

* docs(config): add doc comment on show_tool_calls field

---------

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-14 09:49:35 -04:00
Argenis
46d4b13c22
feat(install): branded one-click installer with secure pairing flow (#3471)
* feat(install): consolidate one-click installer with branded output and inline onboarding

- Add blue color scheme with 🦀 crab emoji branding throughout installer
- Add structured [1/3] [2/3] [3/3] step output with ✓/·/✗ indicators
- Consolidate onboarding into install.sh: inline provider selection menu,
  API key prompt, and model override — no separate wizard step needed
- Replace --onboard/--interactive-onboard with --skip-onboard (opt-out)
- Add OS detection display, install method, version detection, upgrade vs
  fresh install logic
- Add post-install gateway service install/restart, doctor health check
- Add dashboard URL (port 42617) with clipboard copy and browser auto-open
- Add docs link (https://www.zeroclawlabs.ai/docs) to success output
- Display pairing code after onboarding in Rust CLI (src/main.rs)
- Remove --interactive flag from `zeroclaw onboard` CLI command
- Remove redundant scripts/install-release.sh legacy redirect
- Update all --interactive references across codebase

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(onboard): auto-pair and include bearer token in dashboard URL

After onboarding, the CLI now auto-pairs using the generated pairing
code to produce a bearer token, then displays the dashboard URL with
the token embedded (e.g. http://127.0.0.1:42617?token=zc_...) so
users can access the dashboard immediately without a separate pairing
step. The token is also persisted to config for gateway restarts.

The install script captures this token-bearing URL from the onboard
output and uses it for clipboard copy and browser auto-open.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* security(onboard): revert token-in-URL, keep pairing code terminal-only

Removes the auto-pair + token-in-URL approach in favor of the original
secure pairing flow. Bearer tokens should never appear in URLs where
they can leak via browser history, Referer headers, clipboard, or
proxy logs. The pairing code stays in the terminal and the user enters
it in the dashboard to complete the handshake securely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: apply cargo fmt formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 07:33:14 -04:00
Jacobinwwey
8fcbb6eb2d
fix(channels): harden slack threading and utf8 truncation (#3461)
* fix(channels): harden slack threading and utf8 truncation

* refactor(channel): collapse interrupt flags to satisfy clippy

---------

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-14 07:31:10 -04:00
Argenis
ce22eba7d0
fix(channels): proactively trim conversation history before provider call (#3473)
Conversation history on long-running channel sessions (e.g. Feishu) grew
unbounded until the provider returned a context-window-exceeded error.
The existing reactive compaction only kicked in *after* the error,
causing the user's message to be lost and requiring a resend.

Add proactive_trim_turns() which estimates total character count and
drops the oldest turns before the request reaches the provider.  The
budget (400 k chars ≈ 100 k tokens) leaves headroom for system prompt,
memory context, and model output.

Closes #3460
2026-03-14 07:15:34 -04:00
lilstaz
dc12d03876
feat(gateway): enable multi-turn chat for WebSocket connections (#3467)
Replace single-turn chat with persistent Agent to maintain conversation
history across WebSocket turns within the same connection.

Co-authored-by: staz <starzwan2333@gmail.com>
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-14 07:04:59 -04:00
guitaripod
c5fcda06ad
fix(agent): add channel media markers to system prompt (#3459)
The system prompt has no documentation of channel media markers
([Voice], [IMAGE:], [Document:]), causing the LLM to misinterpret
transcribed voice messages as unprocessable audio attachments instead
of responding to the transcribed text content.

Re-applies fix from merged dev PR #1697 which was lost during the
master branch migration.

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-14 06:58:40 -04:00
Ericsunsk
51a52dcadb
fix(memory): pass embedding_routes in gateway and agent loop (#3462)
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-14 06:56:55 -04:00
Christopher Wong
27936b051d
Windows/wizard deadlock (#3451)
* ci: add x86_64-pc-windows-msvc to build matrix

* fix: prevent test deadlock in ensure_onboard_overwrite_allowed

Gate non-interactive terminal check behind cfg!(not(test)) so tests with
force=false do not hang waiting on stdin. cfg!(test) path bails immediately
with a clear message. No changes to extra_headers, mcp, nodes, or shellexpand.

---------

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 21:59:05 -04:00
Alix-007
39d788a95f
fix(linq): accept latest webhook payload shape (#3351)
* fix(linq): accept current webhook payload shape

* style(linq): satisfy clippy lifetime lint

---------

Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
2026-03-13 21:53:41 -04:00
Alix-007
5d921bd37d
fix(config): decrypt feishu channel secrets on load (#3355)
* fix(config): decrypt feishu channel secrets on load

* style(config): format feishu secret assertions

---------

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 21:53:11 -04:00
Alix-007
d17f0a946c
fix(config): recover docker runtime path on save (#3354)
* fix(config): recover docker runtime path on save

* style(config): align docker save path formatting

---------

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 21:52:40 -04:00
Alix-007
2710ce65cc
fix(cli): handle empty invocation before clap parse (#3353) 2026-03-13 21:52:15 -04:00
Asuta
348c0c37b7
feat(agent): 支持交互会话状态持久化与恢复 (#3421)
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 18:55:42 -04:00
Fausto
cabd3de3cb
Allow ZEROCLAW_PROVIDER_URL env variable to override api_url (#3414)
* Ignore JetBrains .idea folder

* fix(ollama): support stringified JSON tool call arguments

* providers: allow ZEROCLAW_PROVIDER_URL env var to override Ollama base URL

Supports container deployments where Ollama runs on a Docker network host
(e.g. http://ollama:11434) without requiring config.toml changes.

Includes regression test ensuring the environment override works.

* fix(clippy): replace Default::default() with ProviderRuntimeOptions::default()

---------

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 18:24:37 -04:00
Argenis
87cf6b0e93
feat(gateway): add dynamic node discovery and capability advertisement (#3448)
Add a WebSocket endpoint at /ws/nodes where external processes and
devices can connect and advertise their capabilities at runtime.
The gateway tracks connected nodes in a NodeRegistry and exposes
their capabilities as dynamically available tools via NodeTool.

- Add src/gateway/nodes.rs: WebSocket endpoint, NodeRegistry, protocol
- Add src/tools/node_tool.rs: Tool trait wrapper for node capabilities
- Add NodesConfig to config schema (disabled by default)
- Wire /ws/nodes route into gateway router
- Add NodeRegistry to AppState and all test constructions
- Re-export NodesConfig and NodeTool from module roots

Closes #3093
2026-03-13 18:23:48 -04:00
Marcelo Correa
2e2c1da4fa
fix(cron): skip unparseable job rows instead of aborting the scheduler (#3405)
A single cron job with a malformed `next_run` timestamp in the database
was silently stopping all scheduled jobs. The `due_jobs` query matched
rows whose `next_run` was lexicographically past-due (including
non-RFC3339 values like "2026-03-12 03:11:13" which sort before valid
RFC3339 strings), then `map_cron_job_row` failed to parse the timestamp,
the `row?` propagation caused `due_jobs` to return `Err`, and the
scheduler marked itself as `error` and skipped every subsequent tick —
taking down all other healthy jobs with it.

The fix changes the row iteration in `due_jobs` to log a warning and
skip unparseable rows rather than aborting the entire result set. Valid
jobs continue to fire; the broken row is surfaced in the logs without
collateral damage to the scheduler.

Co-authored-by: ZeroClaw <zeroclaw@users.noreply.github.com>
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 18:17:08 -04:00
Argenis
c384c34c31
feat(provider): support custom API path suffix for custom: endpoints (#3447)
* feat(provider): support custom API path suffix for custom: endpoints

Allow users to configure a custom API path for custom/compatible
providers instead of hardcoding /v1/chat/completions. Some self-hosted
LLM servers use different API paths.

Adds an optional `api_path` field to:
- Config (top-level and model_providers profile)
- ProviderRuntimeOptions
- OpenAiCompatibleProvider

When set, the custom path is appended to base_url instead of the
default /chat/completions suffix.

Closes #3125

* fix: add missing api_path field to test ModelProviderConfig initializers
2026-03-13 17:54:21 -04:00
Argenis
ef770f15b9
feat(tool): on-demand MCP tool loading via tool_search (#3446)
Add deferred MCP tool activation to reduce context window waste.
When mcp.deferred_loading is true (the default), MCP tool schemas
are not eagerly included in the LLM context. Instead, only tool
names appear in an <available-deferred-tools> system prompt section,
and the LLM calls the built-in tool_search tool to fetch full schemas
on demand. Setting deferred_loading to false preserves the existing
eager behavior.

Closes #3095
2026-03-13 17:25:19 -04:00
Argenis
05a0cdf6f4
feat(tools): add Windows support for shell tool_call execution (#3442)
Use `cmd.exe /C` instead of `sh -c` on Windows via cfg(target_os).
Make the shell allowlist, forbidden paths, env vars, risk classification,
and path detection platform-aware so the shell tool works correctly on
Windows without changing Unix behavior.

Closes #3327

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:12:16 -04:00
Argenis
fc1b555b31
feat(matrix): add read markers and typing notifications (#3441)
Send a read receipt after receiving each message, start a typing
notification while processing, and stop it before sending the response.
This gives Matrix users visual feedback that the bot has seen their
message and is working on a reply.

Closes #3357

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:06:48 -04:00
Argenis
b8ebe7bcd3
feat(gateway): add cron run history API and dashboard panel (#3440)
Add GET /api/cron/{id}/runs?limit=N endpoint that returns recent stored
runs for a cron job, with server-side limit clamping to 1-100 (default 20).

Frontend adds a CronRun type, API client function, and an expandable
run history panel on the Cron page showing status, timestamps, duration,
and output for each run, with loading, empty, error, and refresh states.

Closes #3299

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:06:44 -04:00
Argenis
049edf4eec
feat(channel): add WeCom (WeChat Enterprise) Bot Webhook channel (#3439)
Implement the Channel trait for WeCom Bot Webhook, supporting
outbound text messages via the WeCom webhook API. The channel
is send-only; inbound messages can be routed through the gateway
webhook subsystem.

Closes #3396
2026-03-13 16:44:34 -04:00
Argenis
856a651dd1
feat(channels): add ack_reactions config to disable channel reactions (#3438)
Users can now set `ack_reactions = false` in `[channels_config]` to
suppress the 👀//⚠️ acknowledgement reactions on incoming messages.
The option defaults to `true`, preserving existing behavior.

Closes #3403
2026-03-13 16:43:47 -04:00
Argenis
a9c7697c6b
fix(slack): subscribe to thread message events in polling mode (#3435)
The polling-based Slack listener only called conversations.history, which
returns top-level channel messages but not thread replies. Users replying
inside a thread were invisible to the bot after its initial response.

Add conversations.replies polling for active threads discovered in
channel history. Track thread parents with reply_count > 0, periodically
fetch new replies, and emit them as ChannelMessage with the correct
thread_ts so the bot can continue multi-turn conversations in-thread.
Stale threads are evicted after 24 hours or when the tracker exceeds
50 entries.

Closes #3084
2026-03-13 16:33:26 -04:00
Argenis
939edf5e86
fix: expose MCP tools to delegate subagents (#3436)
MCP tools were not visible to delegate subagents because parent_tools
was a static snapshot taken before MCP tool wiring. Switch to interior
mutability (parking_lot::RwLock) so MCP wrappers pushed after
DelegateTool construction are visible at sub-agent execution time.

Closes #3069
2026-03-13 16:26:01 -04:00
Alix-007
bbc82fd4f9
fix(observability): support verbose backend selection (#3374)
Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
2026-03-13 16:15:43 -04:00
Argenis
a606f308f5
fix(security): respect allowed_roots in tool-level path pre-checks (#3434)
When workspace_only=true and allowed_roots is configured, several tools
(file_read, content_search, glob_search) rejected absolute paths before
the allowed_roots allowlist was consulted. Additionally, tilde paths
(~/...) passed is_path_allowed but were then incorrectly joined with
workspace_dir as literal relative paths.

Changes:
- Add SecurityPolicy::resolve_tool_path() to properly expand tilde
  paths and handle absolute vs relative path resolution for tools
- Add SecurityPolicy::is_under_allowed_root() for tool pre-checks to
  consult the allowed_roots allowlist before rejecting absolute paths
- Update file_read to use resolve_tool_path instead of workspace_dir.join
- Update content_search and glob_search absolute-path pre-checks to
  allow paths under allowed_roots
- Add tests covering workspace_only + allowed_roots scenarios

Closes #3082
2026-03-13 16:15:30 -04:00
Argenis
1162e3adc2
fix(channel): resolve Matrix channel compilation errors with channel-matrix feature (#3433)
Add missing Relation import from ruma events::room::message, remove
unused InReplyTo import, suppress unused matrix_skip_context variable,
and fix additional clippy lints (split_once, single-char patterns,
collapsible replace, wildcard match, ignored unit pattern).

Closes #3425
2026-03-13 15:45:28 -04:00
Alix-007
bd75799644
fix(build): unblock strict 32-bit no-default-features builds (#3375)
Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
2026-03-13 15:45:03 -04:00
Argenis
35217bf457
fix: use cfg-conditional AtomicU32 fallback for 32-bit targets in mcp_client (#3432)
PR #3409 fixed AtomicU64 usage on 32-bit targets in other files but
missed src/tools/mcp_client.rs. Apply the same cfg(target_has_atomic)
pattern used in channels/irc.rs to conditionally select AtomicU64 vs
AtomicU32.

Closes #3430
2026-03-13 15:33:31 -04:00
Alix-007
e5e3761020
fix(cron): support Matrix announce delivery (#3373)
* fix(cron): support Matrix announce delivery

* fix(cron): expose Matrix delivery in tool schemas
2026-03-13 15:16:10 -04:00
Vernon Stinebaker
a52446c637
feat(agent): add tool_filter_groups for per-turn MCP tool schema filtering (#3395)
* feat(agent): add tool_filter_groups for per-turn MCP tool schema filtering

Introduces per-turn MCP tool schema filtering to reduce token overhead when
many MCP tools are registered. Filtering is driven by a new config field
`agent.tool_filter_groups`, which is a list of named groups that each
specify tool glob patterns and an activation mode (`always` or `dynamic`).

Built-in (non-MCP) tools always pass through unchanged; the feature is fully
backward-compatible — an empty `tool_filter_groups` list (the default) leaves
all existing behaviour untouched.

Changes:
- src/config/schema.rs: add `ToolFilterGroupMode`, `ToolFilterGroup` types
  and `tool_filter_groups` field on `AgentConfig`
- src/config/mod.rs: re-export `ToolFilterGroup`, `ToolFilterGroupMode`
- src/agent/loop_.rs: add `glob_match()`, `filter_tool_specs_for_turn()`,
  `compute_excluded_mcp_tools()` helpers; wire call sites in both single-shot
  and interactive REPL modes; add unit tests for all three functions
- docs/reference/api/config-reference.md: document `tool_filter_groups`
  field and sub-table schema with example
- docs/i18n/el/config-reference.md: add Greek locale config-reference with
  `tool_filter_groups` section (2026-03-12 update)

* Remove accidentally committed worktree directories

---------

Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
2026-03-13 14:23:57 -04:00
Vernon Stinebaker
292952e563
feat(tools/mcp): add MCP subsystem tools layer with multi-transport client (#3394)
* feat(tools/mcp): add MCP subsystem tools layer with multi-transport client

Introduces a new MCP (Model Context Protocol) subsystem to the tools layer,
providing a multi-transport client implementation (stdio, HTTP, SSE) that
allows ZeroClaw agents to connect to external MCP servers and register their
exposed tools into the runtime tool registry.

New files:
- src/tools/mcp_client.rs: McpRegistry — lifecycle manager for MCP server connections
- src/tools/mcp_protocol.rs: protocol types (request/response/notifications)
- src/tools/mcp_tool.rs: McpToolWrapper — bridges MCP tools to ZeroClaw Tool trait
- src/tools/mcp_transport.rs: transport abstraction (Stdio, Http, Sse)

Wiring changes:
- src/tools/mod.rs: pub mod + pub use for new MCP modules
- src/config/schema.rs: McpTransport, McpServerConfig, McpConfig types; mcp field
  on Config; validate_mcp_config; mcp unit tests
- src/config/mod.rs: re-exports McpConfig, McpServerConfig, McpTransport
- src/channels/mod.rs: MCP server init block in start_channels()
- src/agent/loop_.rs: MCP registry init in run() and process_message()
- src/onboard/wizard.rs: mcp: McpConfig::default() in both wizard constructors

* fix(tools/mcp): inject MCP tools after built-in tool filter, not before

MCP servers are user-declared external integrations. The built-in
agent.allowed_tools / agent.denied_tools filter (filter_primary_agent_tools_or_fail)
governs built-in tool governance only. Injecting MCP tools before that
filter would silently drop all MCP tools when a restrictive allowlist is
configured.

Add ordering comments at both call sites (run() CLI path and
process_message() path) to make this contract explicit for reviewers
and future merges.

Identified via: shady831213/zeroclaw-agent-mcp@3f90b78

* fix(tools/mcp): strip approved field from MCP tool args before forwarding

ZeroClaw's security model injects `approved: bool` into built-in tool
args for supervised-mode confirmation. MCP servers have no knowledge of
this field and reject calls that include it as an unexpected parameter.

Strip `approved` from object-typed args in McpToolWrapper::execute()
before forwarding to the MCP server. Non-object args pass through
unchanged (no silent conversion or rejection).

Add two unit tests:
- execute_strips_approved_field_from_object_args: verifies removal
- execute_handles_non_object_args_without_panic: verifies non-object
  shapes are not broken by the stripping logic

Identified via: shady831213/zeroclaw-agent-mcp@c68be01

---------

Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
2026-03-13 14:23:48 -04:00
SimianAstronaut7
d115b28a1f
fix(daemon): expand tilde to home directory in file paths (#3424)
Rust treats `~` as a literal path character, not a home directory
shorthand. Several config resolution paths used `PathBuf::from()` on
user-provided strings without expanding `~` first, causing a literal
`~` folder to be created in the working directory.

Apply `shellexpand::tilde()` to all user-facing path inputs:
- ZEROCLAW_CONFIG_DIR env var (config/schema.rs, onboard/wizard.rs)
- ZEROCLAW_WORKSPACE env var (config/schema.rs, onboard/wizard.rs,
  channels/matrix.rs)
- active_workspace.toml marker file config_dir (config/schema.rs)

The WhatsApp Web session_path was already correctly expanded via
shellexpand::tilde() in whatsapp_web.rs.

Closes #3417

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 14:22:57 -04:00
Jacobinwwey
b563f5954e
fix(llamacpp): send responses fallback history in llama.cpp-compatible item shape (#3391)
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 14:21:17 -04:00
SimianAstronaut7
e3e711073a
feat(providers): support custom HTTP headers for LLM API requests (#3423)
Add `extra_headers` config field and `ZEROCLAW_EXTRA_HEADERS` env var
support so users can specify custom HTTP headers for provider API
requests. This enables connecting to providers that require specific
headers (e.g., User-Agent, HTTP-Referer, X-Title) without a reverse
proxy.

Config file headers serve as the base; env var headers override them.
Format: `Key:Value,Key2:Value2`

Closes #3189

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-13 14:15:42 -04:00
Argenis
6a4ccaeb73
fix: strip stale tool_result from conversation history and memory context (#3418)
Prevent orphan `<tool_result>` blocks from leaking into LLM sessions:

- Strip `<tool_result>` blocks from cached prior turns in
  `process_channel_message` so the LLM never sees a tool result
  without a preceding tool call (Case A — in-memory accumulation).
- Skip memory entries containing `<tool_result` in both
  `should_skip_memory_context_entry` (channel path) and
  `build_context` (agent path) so SQLite-recalled tool output
  is never injected as memory context (Case B — post-restart).

Closes #3402
2026-03-13 09:55:57 -04:00
Argenis
4aead04916
fix: skip documentation URLs in cloudflare tunnel URL parser (#3416)
The URL parser captured the first https:// URL found in cloudflared
stderr output. When cloudflared emits a quic-go UDP buffer warning
containing a github.com link, that documentation URL was incorrectly
captured as the tunnel's public URL.

Extract URL parsing into a testable helper function that skips known
documentation domains (github.com, cloudflare.com/docs,
developers.cloudflare.com) and recognises tunnel-specific log prefixes
("Visit it at", "Route at", "Registered tunnel connection") and the
.trycloudflare.com domain.

Closes #3413
2026-03-13 09:40:02 -04:00
Argenis
5d1543100d
fix: support Linq 2026-02-03 webhook payload shape (#3337) (#3410)
Closes #3337

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:31:53 -04:00
Argenis
e3a91bc805
fix: gate prometheus and fix AtomicU64 for 32-bit targets (#3409)
* fix: gate prometheus and fix AtomicU64 for 32-bit targets (#3335)

Closes #3335

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: fix import ordering for cfg-gated atomics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:31:25 -04:00
Argenis
833fdefbe5
fix: restore MCP support missing from master branch (#3412)
MCP (Model Context Protocol) config and tool modules were added on the
old `main` branch but never made it to `master`. This restores the full
MCP subsystem: config schema, transport layer (stdio/HTTP/SSE), client
registry, tool wrapper, config validation, and channel wiring.

Closes #3379

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:20:37 -04:00
Argenis
6fe8e3a5bb
fix: gracefully handle reasoning_enabled for unsupported Ollama models (#3411)
When reasoning_enabled is configured, the Ollama provider sends
think=true to all models. Models that don't support the think parameter
(e.g. qwen3.5:0.8b) cause request failures that the reliable provider
classifies as retryable, leading to an infinite retry loop.

Fix: when a request with think=true fails, automatically retry once
with think omitted. This lets the call succeed on models that lack
reasoning support while preserving thinking for capable models.

Closes #3183
Related #850

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:16:03 -04:00
SimianAstronaut7
d2b923ae07
Merge pull request #3322 from zeroclaw-labs/work-issues/2984-fix-cli-chinese-input-crash
fix(agent): use byte-level stdin reads to prevent CJK input crash
2026-03-12 16:58:46 +00:00
SimianAstronaut7
21fdef95f4
Merge pull request #3324 from zeroclaw-labs/work-issues/2907-channel-send-message
feat(channel): add `channel send` CLI command for outbound messages
2026-03-12 16:58:44 +00:00
SimianAstronaut7
d02fbf2d76
Merge pull request #3326 from zeroclaw-labs/work-issues/2978-tool-call-dedup-exempt
feat(agent): add tool_call_dedup_exempt config to bypass within-turn dedup
2026-03-12 16:58:41 +00:00
SimianAstronaut7
05cede29a8
Merge pull request #3328 from zeroclaw-labs/fix/2926-configurable-provider-timeout
feat(provider): make HTTP request timeout configurable
2026-03-12 16:58:38 +00:00
SimianAstronaut7
d34a2e6d3f
Merge pull request #3329 from zeroclaw-labs/work-issues/2443-approval-manager-shadowed-binding
fix(channel): remove shadowed variable bindings in test functions
2026-03-12 16:58:35 +00:00
SimianAstronaut7
576d22fedd
Merge pull request #3330 from zeroclaw-labs/work-issues/2884-ws-token-query-param
fix(gateway): restore multi-source WebSocket auth token extraction
2026-03-12 16:58:32 +00:00
SimianAstronaut7
d5455c694c
Merge pull request #3332 from zeroclaw-labs/work-issues/2896-discord-ws-silent-stop
fix(channel): handle websocket Ping frames and read errors in Discord gateway
2026-03-12 16:58:30 +00:00
SimianAstronaut7
90275b057e
Merge pull request #3339 from zeroclaw-labs/work-issues/2880-fix-workspace-path-blocked
fix(security): allow absolute paths within workspace when workspace_only is set
2026-03-12 16:58:27 +00:00
simianastronaut
f25835f98c fix(channel): prevent first-turn photo duplication in memory context (#2403)
When auto_save is enabled and a photo is sent on the first turn of a
Telegram session, the [IMAGE:] marker was duplicated because:

1. auto_save stores the photo message (including the marker) to memory
2. build_memory_context recalls the just-saved entry as relevant context
3. The recalled marker is prepended to the original message content

Fix: skip memory context entries containing [IMAGE:] markers in
should_skip_memory_context_entry so auto-saved photo messages are not
re-injected through memory context enrichment.

Closes #2403

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:03:29 -04:00
simianastronaut
376579f9fa fix(security): allow absolute paths within workspace when workspace_only is set (#2880)
When workspace_only=true, is_path_allowed() blanket-rejected all
absolute paths.  This blocked legitimate tool calls that referenced
files inside the workspace using an absolute path (e.g. saving a
screenshot to /home/user/.zeroclaw/workspace/images/example.png).

The fix checks whether an absolute path falls within workspace_dir or
any configured allowed_root before rejecting it, mirroring the priority
order already used by is_resolved_path_allowed().  Paths outside the
workspace and allowed roots are still blocked, and the forbidden-paths
list continues to apply to all other absolute paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:59:35 -04:00
simianastronaut
b620fd6bba fix(channel): handle websocket Ping frames and read errors in Discord gateway
The Discord gateway event loop silently dropped websocket Ping frames
(via a catch-all `_ => continue`) without responding with Pong. After
splitting the websocket stream into read/write halves, automatic
Ping/Pong handling is disabled, so the server-side (Cloudflare/Discord)
eventually considers the client unresponsive and stops sending events.

Additionally, websocket read errors (`Some(Err(_))`) were silently
swallowed by the same catch-all, preventing reconnection on transient
failures.

This patch:
- Responds to `Message::Ping` with `Message::Pong` to maintain the
  websocket keepalive contract
- Breaks the event loop on `Some(Err(_))` with a warning log, allowing
  the supervisor to reconnect

Closes #2896

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:15:53 -04:00
simianastronaut
98d6c5af9e fix(gateway): restore multi-source WebSocket auth token extraction (#2884)
The Electric Blue dashboard (PR #2804) sends the pairing token as a
?token= query parameter, but the WS handler only checked that single
source. Earlier PR #2193 had established a three-source precedence
chain (header > subprotocol > query param) which was lost.

Add extract_ws_token() with the documented precedence:
  1. Authorization: Bearer <token> header
  2. Sec-WebSocket-Protocol: bearer.<token> subprotocol
  3. ?token=<token> query parameter

This ensures browser-based clients (which cannot set custom headers)
can authenticate via query param or subprotocol, while non-browser
clients can use the standard Authorization header.

Includes 9 unit tests covering each source, precedence ordering,
and empty-value fallthrough.

Closes #2884

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:01:26 -04:00
simianastronaut
c51ca19dc1 fix(channel): remove shadowed variable bindings in test functions (#2443)
Rename shadowed `histories` and `store` bindings in three test functions
to eliminate variable shadows that are flagged under stricter lint
configurations (clippy::shadow_unrelated). The initial bindings are
consumed by struct initialization; the second bindings that lock the
mutex guard are now named distinctly (`locked_histories`, `cleanup_store`).

Closes #2443

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:00:31 -04:00
simianastronaut
ea6abc9f42 feat(provider): make HTTP request timeout configurable (#2926)
The provider HTTP request timeout was hardcoded at 120 seconds in
`OpenAiCompatibleProvider::http_client()`. This makes it configurable
via the `provider_timeout_secs` config key and the
`ZEROCLAW_PROVIDER_TIMEOUT_SECS` environment variable, defaulting
to 120s for backward compatibility.

Changes:
- Add `provider_timeout_secs` field to Config with serde default
- Add `ZEROCLAW_PROVIDER_TIMEOUT_SECS` env var override
- Add `timeout_secs` field and `with_timeout_secs()` builder on
  `OpenAiCompatibleProvider`
- Add `provider_timeout_secs` to `ProviderRuntimeOptions`
- Thread config value through agent loop, channels, gateway, and tools
- Use `compat()` closure in provider factory to apply timeout to all
  compatible providers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:40:18 -04:00
simianastronaut
e2f6f20bfb feat(agent): add tool_call_dedup_exempt config to bypass within-turn dedup (#2978)
Add `agent.tool_call_dedup_exempt` config key (list of tool names) to
allow specific tools to bypass the within-turn identical-signature
deduplication check in run_tool_call_loop. This fixes the browser
snapshot polling use case where repeated calls with identical arguments
are legitimate and should not be suppressed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:28:42 -04:00
simianastronaut
88df3d4b2e feat(channel): add channel send CLI command for outbound messages (#2907)
Add a `zeroclaw channel send` subcommand that sends a one-off message
through a configured channel without starting the full agent loop.
This enables hardware sensor triggers (e.g., range sensors on
Raspberry Pi) to push notifications to Telegram and other platforms.

Usage:
  zeroclaw channel send 'Alert!' --channel-id telegram --recipient <chat_id>

Supported channels: telegram, discord, slack.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:18:04 -04:00
simianastronaut
0dba55959d fix(agent): use byte-level stdin reads to prevent CJK input crash
When running through a PTY chain (kubectl exec, SSH, remote terminals),
the transport layer may split data frames at space (0x20) boundaries,
interrupting multi-byte UTF-8 characters mid-sequence. Rust's
BufRead::read_line requires valid UTF-8 and returns InvalidData
immediately, crashing the interactive agent loop.

Replace stdin().read_line() with byte-level read_until(b'\n') followed
by String::from_utf8_lossy() in both the main input loop and the
/clear confirmation prompt. This reads raw bytes without UTF-8
validation during transport, then does lossy conversion (replacing any
truly invalid bytes with U+FFFD instead of crashing).

Also set ENV LANG=C.UTF-8 in both Dockerfile stages as defense-in-depth
to ensure the container locale defaults to UTF-8.

Closes #2984

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:08:49 -04:00
SimianAstronaut7
893788f04d
fix(security): strip URLs before high-entropy token extraction (#3064) (#3321)
The credential leak detector's check_high_entropy_tokens would
false-positive on URL path segments (e.g. long alphanumeric filenames)
because extract_candidate_tokens included '/' in the token character
set, creating long mixed-alpha-digit tokens that exceeded the Shannon
entropy threshold.

Fix: strip URLs from content before extracting candidate tokens for
entropy analysis. Structural pattern checks (API keys, JWTs, AWS
credentials) use dedicated regexes and are unaffected.

Closes #3064

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:53:38 +00:00
SimianAstronaut7
0fea62d114
fix(tool): resolve Brave API key lazily with decryption support (#3078) (#3320)
WebSearchTool previously stored the Brave API key once at boot and never
re-read it. This caused three failures: (1) keys set after boot via
web_search_config were ignored, (2) encrypted keys passed as raw enc2:
blobs to the Brave API, and (3) keys absent at startup left the tool
permanently broken.

The fix adds lazy key resolution at execution time. A fast path returns
the boot-time key when it is plaintext and non-empty. When the boot key
is missing or still encrypted, the tool re-reads config.toml, decrypts
the value through SecretStore, and uses the result. This also means
runtime config updates (e.g. `web_search_config set brave_api_key=...`)
are picked up on the next search invocation.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:53:35 +00:00
SimianAstronaut7
cca3cf8f84
fix(agent): use char-boundary-safe slicing in scrub_credentials (#3024) (#3319)
Replace byte-level `&val[..4]` slice with `char_indices().nth(4)` to
prevent a panic when the captured credential value contains multi-byte
UTF-8 characters (e.g. Chinese text).  Adds a regression test with
CJK input.

Closes #3024

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:53:32 +00:00
SimianAstronaut7
7170810e98
fix(channel): drop MutexGuard before .await in WhatsApp Web listen (#3315)
Extract `self.bot_handle.lock().take()` into a separate `let` binding
so the parking_lot::MutexGuard is dropped before the `.await`, making
the listen future Send again.

Closes #3312

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:16:40 +00:00
SimianAstronaut7
816fa87d60
fix(channel): restore Lark/Feishu channel compilation (#3318)
Replace the `use_feishu: bool` field on `LarkChannel` with a
`platform: LarkPlatform` enum field, add `mention_only` to the
`new_with_platform` constructor, and introduce `from_lark_config` /
`from_feishu_config` factory methods so the channel factory in
`mod.rs` and the existing tests compile.

Resolves #3302

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:16:31 +00:00
Argenis
e03dc4bfce
fix(security): unify cron shell validation across API/CLI/scheduler (#3270)
Centralize cron shell command validation so all entrypoints enforce the
same security policy (allowlist + risk gate + approval) before
persistence and execution.

Changes:
- Add validate_shell_command() and validate_shell_command_with_security()
  as the single validation gate for all cron shell paths
- Add add_shell_job_with_approval() and update_shell_job_with_approval()
  that validate before persisting
- Add add_once_validated() and add_once_at_validated() for one-shot jobs
- Make raw add_shell_job/add_job/add_once/add_once_at pub(crate) to
  prevent unvalidated writes from outside the cron module
- Route gateway API through validated creation path
- Route schedule tool through validated helpers (single validation)
- Route cron_add/cron_update tools through validated helpers
- Unify scheduler execution validation via validate_shell_command_with_security
- CLI update handler uses full validate_command_execution instead of
  just is_command_allowed
- Add focused tests for validation parity across entrypoints
- Standardize error format to "blocked by security policy: {reason}"

Closes #2741
Closes #2742

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:48:13 +00:00
Darren.Zeng
2cb57e6b2d
fix(cli): honor config default_temperature in agent command (#3106)
* fix(cli): honor config default_temperature in agent command

Fixes #3033

The agent command was using a hardcoded default_value of 0.7 for the
--temperature parameter, which ignored the default_temperature setting
in the config file.

Changes:
- Changed temperature from f64 to Option<f64>
- Removed hardcoded default_value
- Use config.default_temperature when --temperature is not provided

Users can now set default_temperature in config.toml and have it
honored when running 'zeroclaw agent' without --temperature.

Risk: low (behavior change: now honors config instead of hardcoded value)

* fix: resolve moved value error for config in agent command

Extract final_temperature before passing config to agent::run() to
avoid use-of-moved-value error (config is moved as the first argument
while config.default_temperature was being accessed in the same call).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 00:34:32 -04:00
Argenis
448682c440
feat(providers): add Azure OpenAI provider support (#3246)
Closes #3176
2026-03-12 00:06:11 -04:00
Argenis
3fe3fe23b1
feat(build): add 32-bit system support via feature gates (#3245)
Closes #3174
2026-03-12 00:06:08 -04:00
ImanHashemi
273bd00d08
feat(hooks): add webhook-audit builtin hook (#3212)
Co-authored-by: ImanHashemi
2026-03-11 23:34:17 -04:00
Darren.Zeng
d33b73974b
feat(provider): add vision support for Anthropic provider (#3177)
Add vision support to Anthropic provider to enable image understanding:

- Add ImageSource struct for Anthropic's image content block format
- Add Image variant to NativeContentOut enum
- Implement capabilities() returning vision: true
- Update convert_messages() to parse [IMAGE:...] markers and convert
them to Anthropic's native image content blocks
- Support both data URIs and local file paths
- Add comprehensive tests for vision functionality

Fixes #3163

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-11 23:27:58 -04:00
Darren.Zeng
ab6846cb9f
feat(channel): make email subject configurable (#3190)
Add default_subject field to EmailConfig to allow users to customize
the default subject line for outgoing emails. Previously hardcoded as
"ZeroClaw Message".

- Add default_subject field with serde default
- Update send() method to use configured default
- Add tests for new functionality

Fixes #2878

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-11 23:27:52 -04:00
Darren.Zeng
ca683816a7
fix(config): fix web_fetch allowed_domains serde default (#3192)
When [web_fetch] section was specified in config without explicit
allowed_domains, serde used Vec::default() (empty vector) instead of
the wildcard ["*"] default. This caused all web fetch requests to be
rejected unexpectedly.

Fix by adding explicit serde default function that returns vec!["*"].

Fixes #2941

Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-11 23:27:49 -04:00
Darren.Zeng
8d7abb73e7
fix(config): accept openai-* aliases for wire_api config (#3191)
Add support for openai-responses, open-ai-responses, openai-chat-completions,
and open-ai-chat-completions as aliases for wire_api configuration values.
This aligns the parser with documented values that users expect to work.

Fixes #2735
2026-03-11 22:09:50 -04:00
Darren.Zeng
7ddf10f86d
feat(onboard): add --reinit flag to prevent accidental config overwrite (#3102)
* feat(onboard): add --reinit flag to prevent accidental config overwrite

Add --reinit flag to onboard command that:
- Backs up existing ~/.zeroclaw directory with timestamp
- Starts fresh initialization after backup
- Requires --interactive mode to work
- Prevents accidental configuration loss

This addresses issue #3013 where onboard could accidentally
overwrite all configuration without warning.

Closes #3013

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ci): SHA-pin all third-party GitHub Actions

Replace mutable version tags with immutable commit SHAs to prevent
tag-hijacking supply chain attacks (P1 finding).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: retrigger CI after startup_failure

* fix(onboard): address PR #3102 review issues for --reinit flag

- Use resolve_runtime_dirs_for_onboarding() instead of hardcoded ~/.zeroclaw
- Remove unsafe relative path fallback, bail instead
- Add user confirmation prompt before reinitializing config
- Update docs/reference/cli/commands-reference.md with --reinit docs

* style: fix cargo fmt and clippy violations

- Fix import ordering in src/config/mod.rs (rustfmt)
- Collapse single-arg encrypt/decrypt calls in src/config/schema.rs (rustfmt)
- Box::pin large onboard futures to fix clippy::large_futures in src/main.rs

These violations were blocking CI lint checks.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Simian Astronaut 7 <simianastronaut7@gmail.com>
2026-03-11 22:09:39 -04:00
Darren.Zeng
ef2f8e9808
fix(daemon): handle SIGTERM for graceful shutdown (#3193)
The daemon previously only handled SIGINT (Ctrl+C), ignoring SIGTERM
which is the standard termination signal used by Docker, Kubernetes,
and systemd. This caused containers to wait for the grace period
then be force-killed with SIGKILL.

Now the daemon handles both SIGINT and SIGTERM for graceful shutdown.

Fixes #2529
2026-03-11 22:09:34 -04:00
Argenis
5cf1c77531
style: fix cargo fmt violations blocking CI lint (#3244)
* style: fix cargo fmt in ollama.rs test

* style: cargo fmt dispatcher.rs

* style: cargo fmt loop_.rs

* style: cargo fmt schema.rs

* style: cargo fmt mod.rs

* style: cargo fmt ws.rs

* style: cargo fmt ollama.rs
2026-03-11 21:53:25 -04:00
Argenis
483d3c0853
fix(ollama): strip <think> tags from Qwen responses and validate tool calls (#3079) (#3241)
- Strip `<think>...</think>` blocks in parse_tool_calls(), XmlToolDispatcher,
  and OllamaProvider before processing tool-call XML
- Add effective_content() fallback: when content is empty after stripping
  think tags, check the thinking field for tool-call XML
- Add strip_think_tags() to ollama.rs, loop_.rs, and dispatcher.rs
- Add comprehensive tests for think-tag stripping and tool-call parsing

Fixes #3079

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 20:35:33 -04:00
Argenis
f5cd6baec3
fix(gateway): add /api/integrations/settings, echo WS protocol, persist session ID (#3242)
- #3009: Add handle_api_integrations_settings endpoint returning JSON with
  per-integration enabled/category/status so /api/integrations/settings no
  longer falls through to the SPA static handler.

- #3010: Extract Sec-WebSocket-Protocol header in handle_ws_chat and echo
  back "zeroclaw.v1" via ws.protocols() when the client requests it.

- #3038: Generate a persistent session_id (crypto.randomUUID stored in
  sessionStorage) in the web WS client and pass it as a query parameter.
  Add session_id: Option<String> to WsQuery on the server side so the
  backend can key conversations by session across reconnects.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 20:35:27 -04:00
Argenis
12cfe48047
fix(config): add channel secrets roundtrip test and gitignore entries (#3126) (#3240)
Add a test verifying Telegram bot_token is encrypted on save and
decrypted on load, and add .gitignore entries for local state backups.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 20:35:11 -04:00
Jaime Linares
0479bfca36
fix(channel): reconnect WhatsApp Web session and show QR on logout (#3045)
Wraps the WhatsApp Web listen() in a reconnect loop. When Event::LoggedOut
fires, the bot is torn down, stale session DB files are deleted, and after
exponential backoff (3s base, 300s cap, max 10 retries) the loop restarts
triggering fresh QR pairing.

- Broadcast channel for logout signaling from event handler to listen loop
- Session file cleanup (primary + WAL + SHM) only on explicit LoggedOut
- Proper resource ordering: client lock, abort handle, drop bot/device
- Tests for retry delay, counter, session purge, and file paths helpers
2026-03-11 20:30:28 -04:00
Argenis
195c7ba919
fix(agent): resolve display text for tool-only turns (#3054) 2026-03-11 20:08:16 -04:00
James Cowan
bb66df5276
fix(config): encrypt and decrypt all channel secrets on save/load (#3217)
Adds symmetric encrypt/decrypt calls for all channel secret fields in
Config::save() and Config::load_or_init(). Previously only nostr.private_key
was handled, leaving all other channel secrets (bot_token, app_token,
access_token, api_token, password, etc.) and gateway.paired_tokens stored
as plaintext when secrets.encrypt = true.

Closes #3175, closes #3173.

Co-authored-by: jameslcowan
2026-03-11 20:02:20 -04:00
Alan P John
74fe29d772
feat: Add Opencode-go provider (#3113)
Adds opencode-go as a first-class provider with dedicated API endpoint,
env var, onboarding wizard wiring, and test coverage.

CI failures are pre-existing on master (Rust 1.94 formatting/lint changes per #3207).
2026-03-11 19:35:43 -04:00
Argenis
86ca34ac1f
fix(tests): update assertions for live tool call notifications (#3232)
The live tool call notifications feature sends extra messages to the
channel when tools are invoked. Update 5 tests that assumed exactly
1 sent message to instead check the last message in the list.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:26:45 -04:00
Argenis
f0f0f80895
feat(matrix): add multi-room support (#3224)
Enable a single Matrix bot instance to respond in multiple rooms:
- Disable the room_id filter so messages from all joined rooms are processed
- Embed room_id in reply_target as "user||room_id" for routing replies
- Include room_id in channel field for per-room conversation isolation
- Extract room_id from recipient in send() for correct message routing

The configured room_id still serves as a fallback for direct sends
without a "||" separator in the recipient.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:13:18 -04:00
Argenis
01bcba83ad
feat(matrix): add file upload handling and voice message support (#3222) 2026-03-11 19:12:44 -04:00
Argenis
bdc0f325bf
feat(matrix): add pin and unpin message support (#3220)
Implement pin_message and unpin_message for the Matrix channel using
the m.room.pinned_events state event. Adds default no-op trait methods
to the Channel trait so other channel implementations are unaffected.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:10:08 -04:00
Abdullah Imad
248348bd80
feat(matrix): reaction and threading support (#3219)
Implement add_reaction/remove_reaction for Matrix channel using
ReactionEventContent and redaction. Add threading support via
Relation::Thread in send() and thread_ts extraction from incoming
messages, enabling threaded conversations.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-11 19:07:49 -04:00
Abdullah Imad
bd70c0f45b
feat(observer): live tool call notifications (#3221)
Add ChannelNotifyObserver that wraps the observer to forward tool-call
events as real-time threaded messages on messaging channels. Include
tool arguments (truncated) in ToolCallStart events for better
visibility into what tools are doing. Auto-thread final replies when
tools were used.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-11 19:07:34 -04:00
Abdullah Imad
d0edcec1f9
feat(prompt): refresh stale datetime (#3223)
The system prompt is built once at daemon startup and cached. The
"Current Date & Time" section becomes stale immediately. This patch
replaces it with a fresh timestamp every time build_channel_system_prompt
is called (i.e. per incoming message).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:04:23 -04:00
Aleksandr Prilipko
39353748fa
fix(memory): resolve embedding api_key from embedding_provider env var, not default_provider key (#3184)
When embedding_provider differs from default_provider (e.g. default=gemini,
embedding=openai), the caller-supplied api_key belongs to the chat provider.
Passing it to the embedding endpoint causes 401 Unauthorized (gemini key
sent to api.openai.com/v1/embeddings).

Add embedding_provider_env_key() which looks up OPENAI_API_KEY,
OPENROUTER_API_KEY, or COHERE_API_KEY before falling back to the
caller-supplied key. This matches the provider-specific env var resolution
in providers/mod.rs without introducing cross-module coupling.

Also add config_secrets_survive_save_load_roundtrip test: full save→load
cycle with channel credentials (telegram, discord, slack bot_token,
slack app_token) and gateway paired_tokens, verifying that enc2: values
are correctly decrypted by Config::load_or_init(). Regression guard for
issues #3173 and #3175.

Closes #3083

Co-authored-by: ZeroClaw Bot <zeroclaw_bot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Argenis <theonlyhennygod@gmail.com>
2026-03-11 15:39:54 -04:00
Argenis
50a65ea0e5
Merge branch 'master' into fix/channel-model-switch-resolve-routes 2026-03-11 13:32:15 -04:00
Argenis
9cfc88c38c
Merge pull request #3201 from whtiehack/pr/fix-channel-embedding-routes
fix(channels): pass embedding routes to channel memory init
2026-03-11 12:07:53 -04:00
smallwhite
7cacdef2d1 fix(channels): pass embedding routes to channel memory init 2026-03-11 23:57:46 +08:00
smallwhite
1c2a49459e fix(agent): strip prompt-guided tool artifacts from visible replies 2026-03-11 23:50:39 +08:00
panviktor
0ee3b6d617 fix(channels): resolve provider from model_routes on /model, preserve context
- `/model <name>` now auto-resolves provider from configured model_routes
  by matching model name or hint, fixing 404 when switching to models on
  different providers (e.g. `/model kimi-k2.5` with anthropic default)
- Conversation history is no longer cleared on `/model` or `/models` —
  users can explicitly reset via `/new`
- Matrix channel now supports `/model`, `/models`, and `/new` commands
- `/model` (no args) lists configured model routes with hints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:10:42 +01:00
Simian Astronaut 7
5901f70dc0 fix(clippy): box-pin large onboard wizard futures
The onboard wizard futures exceed clippy's large_futures threshold
(16KB+). Wrap in Box::pin to heap-allocate and fix the lint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:21:46 -04:00
Simian Astronaut 7
026158557c chore: fix rustfmt violations from TTS provider merge
Import ordering in config/mod.rs and line-wrapping in config/schema.rs
were left unformatted by PR #2994. Run cargo fmt to fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:09:06 -04:00
Simian Astronaut 7
162a2b65a5 fix(multi): harden Edge TTS path validation, add subprocess timeout, restore Slack symlink protection
Edge TTS: reject binary_path containing path separators to prevent
arbitrary executable paths bypassing the basename allowlist. Wrap
subprocess execution in tokio::time::timeout (60s) matching HTTP
providers.

Slack: restore symlink_metadata check before rename to prevent
symlink-following attacks on attachment output paths.

Docs: add TTS module misplacement note to refactor-candidates.md —
tts.rs belongs in src/tools/, not src/channels/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:05:52 -04:00
Simian Astronaut 7
e6bfb1ebee fix(slack): add HTTP timeouts, proper jitter, and cache bounds
HTTP clients had no timeouts and could hang forever; add 30s total and
10s connect timeouts. Replace clock-nanos-based jitter with rand::random
for proper randomness. Add a 1000-entry cap to the user display name
cache with expired-entry pruning. Fix truncate_text to avoid scanning
the full string twice when checking for truncation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 08:58:20 -04:00
Simian Astronaut 7
9a6de3b204 fix(tts): move Google API key to header and sanitize provider inputs
Google TTS was passing the API key as a URL query parameter, which can
appear in logs and proxy access records. Move it to the x-goog-api-key
header instead. Add input validation for ElevenLabs voice IDs (reject
non-alphanumeric/dash/underscore characters) and restrict Edge TTS
binary_path to allowed basenames (edge-tts, edge-playback).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 08:58:13 -04:00
Simian Astronaut 7
f894bc4140 fix(slack): only send bearer token on first redirect hop
Slack private file fetching was sending the bot token on every redirect
hop. Since Slack CDN redirects use pre-signed URLs, sending the bearer
token to CDN hosts is unnecessary credential exposure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 08:58:05 -04:00
Simian Astronaut 7
a1c65558d8 fix(gateway): restrict admin endpoints to localhost and replace process::exit with graceful shutdown
Admin endpoints (/admin/shutdown, /admin/paircode, /admin/paircode/new) were
completely unauthenticated, allowing any network client to shut down the gateway
or read/generate pairing codes. Add require_localhost() guard that returns 403
for non-loopback IPs.

Replace std::process::exit(0) in shutdown handler with a tokio watch channel
for graceful shutdown, allowing proper destructor cleanup and connection
draining. Replace the 500ms sleep race in the restart command with a poll loop
that waits for the port to actually become free.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 08:57:34 -04:00
Argenis
f2035819c2
Merge pull request #2994 from rareba/feature/tts-providers
feat(tts): add multi-provider TTS system
2026-03-11 05:23:50 -04:00
Kunal Karmakar
06926a189d Merge branch 'master' of https://github.com/kunalk16/zeroclaw into fix-honor-default-temperature-agent-command 2026-03-11 08:36:35 +00:00
Sid Jain
6016491985 fix(slack): harden redirect chain and auth health checks 2026-03-11 04:32:56 -04:00
Sid Jain
bf11b7b1a0 fix(slack): resolve clippy warnings in URL and tests 2026-03-11 04:32:56 -04:00
Sid Jain
436619b015 chore(slack): satisfy rustfmt wrap in MIME warning 2026-03-11 04:32:56 -04:00
Sid Jain
b33af6894e fix(slack): redact private URLs and harden attachment writes 2026-03-11 04:32:56 -04:00
Sid Jain
14de5e527e fix(slack): preserve auth and validate image bytes 2026-03-11 04:32:56 -04:00
Sid Jain
7b7e08dc21 fix(slack): align cron workspace dir and socket retry backoff 2026-03-11 04:32:56 -04:00
Sid Jain
526d63fd75 feat: add slack file reading capability to zeroclaw 2026-03-11 04:32:56 -04:00
曾文锋0668000834
e3cc40c64c feat(gateway): add --new flag to get-paircode for non-disruptive pairing code generation
- Add --new flag to GetPaircode command in src/lib.rs
- Update main.rs to handle GetPaircode { new } parameter
- Add /admin/paircode/new POST endpoint in gateway/mod.rs
- Enhance documentation for constant_time_eq security function

Refs: #3015
2026-03-11 04:30:58 -04:00
曾文锋0668000834
9d182c6dd8 fix(security): restore constant-time comparison bitwise operator
The bitwise & operator is intentional in constant_time_eq() to prevent
timing side-channel attacks. Both comparisons must always execute to
ensure constant-time behavior regardless of the first comparison result.

- Revert logical && back to bitwise &
- Add #[allow(clippy::needless_bitwise_bool)] annotation
- Add explanatory comment documenting the intentional use
2026-03-11 04:30:58 -04:00
曾文锋0668000834
b1dfa192b8 fix(gateway): address CodeRabbit review feedback
- Add security warning for 0.0.0.0 binding in help text
- Implement proper gateway shutdown before restart via /admin/shutdown endpoint
- Fetch live pairing code from running gateway via /admin/paircode endpoint
- Extract duplicate code into helper functions
- Fix clippy warnings
2026-03-11 04:30:58 -04:00
曾文锋0668000834
7ed1bdc104 fix(gateway): address CodeRabbit review issues
- Add security warning for 0.0.0.0 binding in GatewayCommands::Start help
- Implement proper gateway shutdown via /admin/shutdown endpoint for Restart command
- Add /admin/paircode endpoint and update GetPaircode to fetch live pairing code
- Extract helper functions: resolve_gateway_addr, log_gateway_start, shutdown_gateway, fetch_paircode

Fixes: #3101
2026-03-11 04:30:58 -04:00
曾文锋0668000834
7f6bd651f7 fix(gateway): address CodeRabbit review feedback for PR #3101
- Fix Critical: Split illegal or-pattern (Some(...) | None) into separate match arms
- Fix Major: Implement restart command with graceful shutdown check
- Fix Major: Improve get-paircode to check gateway status and provide clear instructions
- Fix Minor: Update help text to document public-bind precondition

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:30:58 -04:00
曾文锋0668000834
491ac601e6 feat(gateway): add restart and get-paircode subcommands
Add GatewayCommands enum with three subcommands:
- start: Start the gateway server (default behavior preserved)
- restart: Restart the gateway server
- get-paircode: Show current pairing status without restarting

This improves gateway management by allowing users to:
1. Restart gateway without manual stop/start
2. Check pairing status without disrupting running gateway

Closes #3014
Closes #3015

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:30:58 -04:00
Kunal Karmakar
e2a3907d35 Merge branch 'master' of https://github.com/kunalk16/zeroclaw into fix-honor-default-temperature-agent-command 2026-03-11 08:08:22 +00:00
Rick Morgans
da0a592163 fix(imessage): parse attributedBody when text column is NULL
Modern macOS (Ventura+) stores iMessage content in the attributedBody
column as a binary typedstream blob rather than the text column. The
existing SQL filter `AND m.text IS NOT NULL` silently dropped all
incoming messages on affected systems.

Add a length-prefix extractor for the typedstream format and fall back
to attributedBody when text is NULL or empty. Includes real captured
blob fixtures and 14 new parser/integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 03:25:05 -04:00
Kunal Karmakar
a6cd877ac8 Merge branch 'master' of https://github.com/kunalk16/zeroclaw into fix-honor-default-temperature-agent-command 2026-03-11 02:13:12 +00:00
simianastronaut
2e86d83ed7 feat(api): add API key prefix validation to prevent cross-provider mismatches
Introduced a new function `check_api_key_prefix` to validate API key prefixes against their associated providers. This helps catch mismatches early in the process. Added unit tests to ensure correct functionality for various scenarios, including known and unknown key formats. This enhancement improves error handling and user guidance when incorrect provider keys are used.
2026-03-10 18:00:38 -04:00
Kunal Karmakar
b2857cb836 Fix clippy linting 2026-03-10 08:29:45 +00:00
Simian Astronaut 7
7ef9d8a7b5 Addressed clippy lint issues 2026-03-10 01:48:19 -04:00
Simian Astronaut 7
ad8e1e65e0 fix(ci): resolve lint and build failures in PR checks
Apply cargo fmt to fix formatting diffs in openrouter.rs and serial.rs.
Add web/dist placeholder step to lint, test, and build jobs so
RustEmbed compiles without the gitignored frontend assets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 00:36:24 -04:00
Simian Astronaut 7
ea61491f2a chore: migrate GitHub URLs from theonlyhennygod to zeroclaw-labs
- Replace all occurrences of theonlyhennygod/zeroclaw with zeroclaw-labs/zeroclaw
- Affects root metadata, source code headers, hardware setup docs, and crate docs
2026-03-09 23:49:56 -04:00
Simian Astronaut 7
1a78dcf447 refactor: update firmware path references to match directory renames
- Align src/peripherals/ and docs/hardware/ with the firmware directory renames
- Covers both compiled references (include_str!, constants) and documentation
2026-03-09 23:49:20 -04:00
Kunal Karmakar
225edecc3d Reuse default temperature 2026-03-09 14:54:29 +00:00
Kunal Karmakar
465b2ea6af Fix temperature range 2026-03-09 14:31:47 +00:00
Kunal Karmakar
e70ca52dc4 Update wording as per review comment 2026-03-09 14:07:13 +00:00
Kunal Karmakar
45d2368730 Default of 0.7 2026-03-09 13:52:33 +00:00
Kunal Karmakar
026f2609f9 Support config without default_temperature 2026-03-09 13:28:08 +00:00
Kunal Karmakar
a6cf7d4015 Honor default_temperature from config.toml for zeroclaw agent calls 2026-03-09 10:31:43 +00:00
Giulio V
48c7414bb3 feat(tts): add multi-provider TTS system (OpenAI, ElevenLabs, Google, Edge TTS)
Adds pluggable Text-to-Speech subsystem with TtsProvider trait,
TtsManager for provider selection, and per-provider config structs.
Includes secret encryption for TTS API keys.
2026-03-09 09:03:44 +01:00
Anton Vice
e098c6d242 fix: resolve unused import warnings in channels and peripherals 2026-03-07 09:34:46 -05:00
jordanthejet
6eef5bafcb feat(ci): full matrix build in CI + fix flaky bedrock test
- CI now builds across all 5 targets (linux x86/arm64, macOS x86/arm64,
  Windows) matching the release matrix
- Fix chat_fails_without_credentials test to accept "builder error"
  which occurs in CI environments without native TLS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:40:45 -05:00
Chummy
07fdea528d
Harden config secret masking and web_fetch limits 2026-02-24 16:03:01 +08:00
Chummy
7305f6df59
fix(ci): address strict-delta clippy blockers 2026-02-24 16:03:01 +08:00
Chummy
46c98ffa26
fix(gateway): mask feishu/qdrant and route api keys in /api/config 2026-02-24 16:03:01 +08:00
Chummy
a3d5631757
fix(tests): use schema config types in gateway api tests 2026-02-24 16:03:01 +08:00
Chummy
c9d76780f0
fix(security): harden redirect/browser_open and restore masked secrets 2026-02-24 16:03:01 +08:00
Chummy
bfd56f2ba9
chore(fmt): fix loop_ test formatting after #1505 2026-02-24 16:03:01 +08:00
Chummy
921132575d
test: add regression coverage for provider parser cron and telegram 2026-02-24 16:03:01 +08:00
Chummy
05e88f81ea
fix: improve tool-call parsing and shell expansion checks 2026-02-24 16:03:01 +08:00
Preventnetworkhacking
12faff3aa9
fix(telegram): populate thread_ts for per-topic session isolation
When a Telegram message originates from a forum topic, the thread_id was
extracted and used for reply routing but never stored in ChannelMessage.thread_ts.
This caused all messages from the same sender to share conversation history
regardless of which topic they were posted in.

Changes:
- Set thread_ts to the extracted thread_id in parse_update_message,
  try_parse_voice_message, and try_parse_attachment_message
- Use 'ref' in if-let patterns to avoid moving thread_id before it's assigned
- Update conversation_history_key() to include thread_ts when present,
  producing keys like 'telegram_<thread_id>_<sender>' for forum topics
- Update conversation_memory_key() to also include thread_ts for memory isolation

This enables proper per-topic session isolation in Telegram forum groups while
preserving existing behavior for regular groups and DMs (where thread_ts is None).

Closes #1532
2026-02-24 16:03:01 +08:00
Chummy
668d8fb1fa
chore(fmt): format gateway api after dashboard-save fix 2026-02-24 16:03:01 +08:00
argenis de la rosa
52753cb05a
fix(gateway): preserve masked config values on dashboard save
Replace line-based TOML masking with structured config masking so secret fields keep their original types (including reliability.api_keys arrays).\nHydrate dashboard PUT payloads with runtime config_path/workspace_dir and restore masked secret placeholders from current config before validation/save.\nAlso allow GET on /api/doctor for dashboard/client compatibility to avoid 405 responses.
2026-02-24 16:03:01 +08:00
Chummy
3493afc068
test(file_read): align outside-workspace case with workspace_only=false policy 2026-02-24 16:03:01 +08:00
Chummy
63d9020d6a
fix(security): preserve workspace allowlist before forbidden-root checks 2026-02-24 16:03:01 +08:00
Allen Huang
9d681dc13b
fix: security, config, and provider hardening
- security: honor explicit command paths in allowed_commands list
- security: respect workspace_only=false in resolved path checks
- config: enforce 0600 permissions on every config save (unix)
- config: reject temp-directory paths in active workspace marker
- provider: preserve reasoning_content in tool-call conversation history
- provider: add allow_user_image_parts parameter for minimax compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-24 16:03:01 +08:00
Chummy
1b90a23eed
fix(ci): align codex tests with provider runtime API 2026-02-24 16:03:01 +08:00
Chummy
7fbf65304b
test(codex): align provider init with runtime option changes 2026-02-24 16:03:01 +08:00
Chummy
7e01f5d7fd
fix(security): tighten prompt-guard detection thresholds and phrases 2026-02-24 16:03:01 +08:00
argenis de la rosa
3d936a31b5
fix(providers): use native_tool_calling field in supports_native_tools
The supports_native_tools() method was hardcoded to return true,
but it should return the value of self.native_tool_calling to
properly disable native tool calling for providers like MiniMax.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:03:01 +08:00
Chummy
64d13c236e
fix(onboard): resolve rebase conflict in models command helpers 2026-02-24 16:03:01 +08:00
Chummy
0fc812f7db
fix: align codex provider runtime options with current interfaces 2026-02-24 16:03:01 +08:00
Chummy
e76d3e6312
feat: stabilize codex oauth and add provider model connectivity workflow 2026-02-24 16:03:01 +08:00
Chummy
987f8888b3
style: apply rustfmt normalization 2026-02-24 16:03:01 +08:00
Dominik Horváth
7310ba67c5
fix(channels,memory): Docker workspace path remapping, vision support, and Qdrant backend restore (#1)
* fix(channels,providers): remap Docker /workspace paths and enable vision for custom provider

Two fixes:

1. Telegram channel: when a Docker-containerised runtime writes a file to
   /workspace/<path>, the host-side sender couldn't find it because the
   container mount point differs from the host workspace dir. Remap
   /workspace/<rel> → <host_workspace_dir>/<rel> in send_attachment before
   the path-exists check so generated media is delivered correctly.

2. Provider factory: custom: provider was created with vision disabled,
   causing all image messages to be rejected with a capability error even
   though the underlying OpenAI-compatible endpoint supports vision. Switch
   to new_with_vision(..., true) so image inputs are forwarded correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(memory): restore Qdrant vector database backend

Re-adds the Qdrant memory backend that was removed from main in a
recent upstream merge. Restores:

- src/memory/qdrant.rs — full QdrantMemory implementation with lazy
  init, HTTP REST client, embeddings, and Memory trait
- src/memory/backend.rs — Qdrant variant in MemoryBackendKind, profile,
  classify and profile dispatch
- src/memory/mod.rs — module export, factory routing with build_qdrant_memory
- src/config/schema.rs — QdrantConfig struct and qdrant field on MemoryConfig
- src/config/mod.rs — re-export QdrantConfig
- src/onboard/wizard.rs — qdrant field in MemoryConfig initializer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 16:03:01 +08:00
Mike Johnson-Maxted
34baae91ff
fix(onboard): split device-flow hint — copilot auto-prompts, others use auth login
copilot is the only provider that performs a device-code flow automatically on
first run. openai-codex and gemini (when OAuth-backed) require an explicit
`zeroclaw auth login --provider <name>` step. Split the device-flow next-steps
block to reflect this distinction.

Addresses Copilot review comment on PR #1509.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 16:03:01 +08:00
Mike Johnson-Maxted
5fc8b673d8
fix(onboard): use provider-aware env var hint in quick setup next steps
Replace hardcoded OPENROUTER_API_KEY hint with provider-aware logic:
- keyless local providers (ollama, llamacpp, etc.) show chat/gateway/status hints
- device-flow providers (copilot, gemini, openai-codex) show OAuth/first-run hint
- all other providers show the correct provider-specific env var via provider_env_var()

Also adds canonical alias "github-copilot" -> "copilot" in canonical_provider_name(),
and a new provider_supports_device_flow() helper with accompanying test.

Additionally fixes pre-existing compile blockers that prevented CI from running:
- fix(security): correct raw string literals in leak_detector.rs that terminated
  early due to unescaped " inside r"..." (use r#"..."# instead)
- fix(gateway): add missing wati: None in two test AppState initializations
- fix(gateway): use serde::Deserialize path on WatiVerifyQuery struct
- fix(security): add #[allow(unused_imports)] on new pub use re-exports in mod.rs
- fix(security): remove unused serde::{Deserialize, Serialize} import
- chore: apply cargo fmt to files that had pending formatting diffs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 16:03:01 +08:00
NB😈
a22dc39ef6
fix(cron): enable delivery for crons created from external channels
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>
2026-02-24 16:03:01 +08:00
Adam Singer
9ecf9739ed
[bug] Regex build failure 2026-02-24 16:03:00 +08:00
Bojan Zivic
61050eace9
fix: always emit toolResult blocks for tool_use responses (#1476)
* ci(homebrew): prefer HOMEBREW_UPSTREAM_PR_TOKEN with fallback

* ci(homebrew): handle existing upstream remote and main base

* fix: always emit toolResult blocks for tool_use responses

The Bedrock Converse API requires that every toolUse block in an
assistant message has a corresponding toolResult block in the
subsequent user message. Two bugs caused violations of this contract:

1. When parse_tool_result_message failed (e.g. malformed JSON or
   missing tool_call_id), the fallback emitted a plain text user
   message instead of a toolResult block, causing Bedrock to reject
   the request with "Expected toolResult blocks at messages.N.content
   for the following Ids: ..."

2. When the assistant made multiple tool calls in a single turn, each
   tool result was pushed as a separate ConverseMessage with role
   "user". Bedrock expects all toolResult blocks for a turn to appear
   in a single user message.

Fix (1) by making the fallback construct a toolResult with status
"error" containing the raw content, and attempting to extract the
tool_use_id from the previous assistant message if JSON parsing fails.

Fix (2) by merging consecutive tool-result user messages into a single
ConverseMessage during convert_messages.

Also accept alternate field names (tool_use_id, toolUseId) in addition
to tool_call_id when parsing tool result messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Will Sarg <12886992+willsarg@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
Chummy
3f9f9c33bc
fix(provider): disable native tool calling for MiniMax (#1495)
MiniMax API does not support OpenAI-style native tool definitions
(`tools` parameter in chat completions). Sending them causes a 500
Internal Server Error with "unknown error (1000)" on every request.

Add a `native_tool_calling` field to `OpenAiCompatibleProvider` so each
constructor can declare its tool-calling capability independently.
MiniMax (via `new_merge_system_into_user`) now sets this to `false`,
causing the agent loop to inject tool instructions into the system
prompt as text instead of sending native JSON tool definitions.

Closes #1387


(cherry picked from commit 2b92a774fb)
(cherry picked from commit 1816e8a829)

Co-authored-by: keiten arch <tang.zhengliang@ivis-sh.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
Adam Makhlouf
f2abf9ac2f
fix(channel): replace invalid Telegram ACK reaction emojis (#1477)
Replace 🙌 and 💪 with 🔥 and 👍 in the TELEGRAM_ACK_REACTIONS pool.
The removed emojis are not in Telegram's allowed reaction set, causing
~40% of ACK reactions to fail with REACTION_INVALID (400 Bad Request).

All replacements verified against the Telegram Bot API setMessageReaction
endpoint in a live private chat.

Closes #1475
2026-02-24 16:03:00 +08:00
Chummy
52b05a7c34
[supersede #1354 v2] feat(composio): fix v3 compatibility with parameter discovery, NLP text execution, and error enrichment (#1493)
* 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>
2026-02-24 16:03:00 +08:00
Chummy
6ae134dd3c
supersede: file-replay changes from #1416 (#1494)
Automated conflict recovery via changed-file replay on latest dev.
2026-02-24 16:03:00 +08:00
Le Song
389ecf0499
fix(config): add test for 0600 permissions on config file save
(cherry picked from commit a50877dbd2)
2026-02-24 16:03:00 +08:00
Le Song
0910b394b8
fix(config): chmod 0600 on newly created config
Apply 0600 when saving a new config file so onboarding-created
configs are not world-readable.

(cherry picked from commit e51a596581)
2026-02-24 16:03:00 +08:00
Chummy
c43aaa10f3
fix(config): re-export Feishu/Estop/Otp configs 2026-02-24 16:03:00 +08:00
reidliu41
96700d7952
Summary
- 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)
2026-02-24 16:03:00 +08:00
Chummy
d78e3e253e
fix(onboard): use is_feishu when constructing lark config 2026-02-24 16:03:00 +08:00
aricredemption-ai
3b2009f15a
feat(lark): add mention_only group gating with bot open_id auto-discovery
(cherry picked from commit ef1f75640a)
2026-02-24 16:03:00 +08:00
Nils Fischer
e748e55feb
fix(channel): normalize WhatsApp allowlist matching for LID senders
(cherry picked from commit 9545709231)
2026-02-24 16:03:00 +08:00
Ken Simpson
2efe98da79
fix(tools): recover rust-native browser session on stale webdriver 2026-02-24 16:03:00 +08:00
Chummy
b9b97eeaef
test(pairing): satisfy strict clippy delta on lockout sweep assertions 2026-02-24 16:03:00 +08:00
fettpl
df54237a73
fix(security): harden per-client lockout eviction and sweep
Addresses the unbounded-map gap left by #951: entries below the lockout
threshold (count < MAX_PAIR_ATTEMPTS, lockout = None) were never evicted,
allowing distributed brute-force (>1024 unique IPs, <5 attempts each) to
permanently fill the tracking map and disable accounting for new attackers.

Hardening delta on top of #951:

- Replace raw tuple with typed FailedAttemptState (count, lockout_until,
  last_attempt) for clarity and to enable retention-based sweep.
- Bump MAX_TRACKED_CLIENTS from 1024 to 10_000.
- Add 15-min retention sweep (prune_failed_attempts) on 5-min interval.
- Switch lockout from relative (locked_at + elapsed) to absolute
  (lockout_until) for simpler and monotonic comparison.
- Add LRU eviction fallback when map is at capacity after pruning.
- Add normalize_client_key() to sanitize whitespace/empty client IDs.
- Add 3 focused tests: per-client reset isolation, bounded map capacity,
  and sweep pruning of stale entries.

Supersedes:
- #670 by @fettpl (original hardening branch, rebased as delta)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
Edvard
baa01dab66
feat(agent): inject current datetime into every user message
Prepends [YYYY-MM-DD HH:MM:SS TZ] to each user message before it
reaches the model. This gives the agent accurate temporal context
on every turn, not just session start.

Previously DateTimeSection only injected the time once when the
system prompt was built. Long conversations or cron jobs had
stale timestamps. Now every message carries the real time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
Nguyen Minh Thai
77a3b39ff7
feat(tools): Use system default browser instead of hard-coded Brave Browser (#1453)
* 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>
2026-02-24 16:03:00 +08:00
Edvard Schøyen
15061f9605
feat(channels): add /new command to clear conversation history (#1417)
Adds a `/new` runtime chat command for Telegram and Discord that clears
the sender's conversation history without changing provider or model.
Useful for starting a fresh session when stale context causes issues.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
Amit Kotlovski
f227a8f4d6
fix(providers): use /openai/v1 for Groq base URL 2026-02-24 16:03:00 +08:00
InuDial
35ecaaf435
Add hardware feature conditional compile for hardware mods 2026-02-24 16:03:00 +08:00
Chummy
daeee93f89
test(gemini): restore oauth env vars in unit test 2026-02-24 16:03:00 +08:00
reidliu41
2fb72438f8
fix(gateway): skip pairing dialog in web UI when require_pairing is false 2026-02-24 16:03:00 +08:00
Kevin Syong
ae3f348a15
fix(scheduler): include failure reason in job failure warning
- Return output string from 'execute_and_persist_job' alongside job id and success flag.
- Include failure reason in 'tracing::warn' when a scheduler job fails.
- Makes failed cron job errors visible in logs without inspecting the database.
2026-02-24 16:03:00 +08:00
argenis de la rosa
d193cf036f
fix(gemini): derive OAuth refresh client id from Gemini CLI tokens
Gemini CLI oauth_creds.json can omit client_id/client_secret, causing refresh requests to fail with HTTP 400 invalid_request (could not determine client ID).

Parse id_token claims (aud/azp) as a client_id fallback, preserve env/file overrides, and keep refresh form logic explicit. Also add camelCase deserialization aliases and regression tests for refresh-form and id_token parsing edge cases.

Refs #1424
2026-02-24 16:03:00 +08:00
Ray Azrin Karim
1455f08fbb
fix(channel): remove unsupported Telegram reaction emojis
The previous emoji set included unsupported reactions (🦀, 👣) that Telegram API
rejects with REACTION_INVALID error in some chat contexts. Remove these while
keeping the working emojis.

Before: ["️", "🦀", "🙌", "💪", "👌", "👀", "👣"]
After:  ["️", "🙌", "💪", "👌", "👀"]

Fixes warning: REACTION_INVALID 400 Bad Request
2026-02-24 16:03:00 +08:00
Robert McGinley
bbcbccf20c
fix(tool): treat max_response_size = 0 as unlimited
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.
2026-02-24 16:03:00 +08:00
Aleksandr Prilipko
2df4e902f6
feat(providers): normalize image paths to data URIs in OpenAI Codex
Fix OpenAI Codex vision support by converting file paths to data URIs
before sending requests to the API.

## Problem

OpenAI Codex API was rejecting vision requests with 400 error:
"Invalid 'input[0].content[1].image_url'. Expected a valid URL,
but got a value with an invalid format."

Root cause: provider was sending raw file paths (e.g. `/tmp/test.png`)
instead of data URIs (e.g. `data:image/png;base64,...`).

## Solution

Add image normalization in both `chat_with_system` and `chat_with_history`:
- Call `multimodal::prepare_messages_for_provider()` before building request
- Converts file paths to base64 data URIs
- Validates image size and MIME type
- Works with both local files and remote URLs

## Changes

- `src/providers/openai_codex.rs`:
  - Normalize images in `chat_with_system()`
  - Normalize images in `chat_with_history()`
  - Simplify `ResponsesInputContent.image_url` from nested object to String
  - Fix unit test assertion for flat image_url structure

- `tests/openai_codex_vision_e2e.rs`:
  - Add E2E test for second profile vision support
  - Validates capabilities, request success, and response content

## Verification

 Unit tests pass: `cargo test --lib openai_codex`
 E2E test passes: `cargo test openai_codex_second_vision -- --ignored`
 Second profile accepts vision requests (200 OK)
 Returns correct image descriptions

## Impact

- Enables vision support for all OpenAI Codex profiles
- Second profile works without rate limits
- Fallback chain: default → second → gemini
- No breaking changes to existing non-vision flows

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
Aleksandr Prilipko
c252ad474a
feat(providers): add vision support to OpenAI Codex provider
- Add vision capability declaration (vision: true)
- Extend ResponsesInputContent to support image_url field
- Update build_responses_input() to parse [IMAGE:...] markers
- Add ImageUrlContent structure for data URI images
- Maintain backward compatibility with text-only messages
- Add comprehensive unit tests for image handling

Enables multimodal input for gpt-5.3-codex and similar models.
Image markers are parsed and sent as separate input_image content items.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
Aleksandr Prilipko
229830ce17
feat(providers): auto-refresh expired Gemini OAuth tokens in warmup
Добавлен автоматический refresh протухших OAuth токенов Gemini при вызове warmup().

## Проблема

При использовании Gemini как fallback провайдера, OAuth токены могут протухнуть пока daemon работает. Это приводит к ошибкам при попытке переключения с OpenAI Codex на Gemini.

Сценарий:
1. Daemon работает, но не делает запросов к Gemini
2. OAuth токены Gemini истекают (TTL = 1 час)
3. Происходит ошибка на OpenAI Codex → fallback на Gemini
4. Gemini провайдер использует протухшие токены → запрос падает

## Решение

### Изменения в `GeminiProvider::warmup()`

Добавлена проверка и обновление токенов для `ManagedOAuth`:
- Вызывается `AuthService::get_valid_gemini_access_token()` который автоматически обновляет токены если нужно
- Для `OAuthToken` (CLI): пропускается (существующее поведение)
- Для API key: проверяется через публичный API (существующее поведение)

### Тесты

**Unit тесты** (`src/providers/gemini.rs`):
- `warmup_managed_oauth_requires_auth_service()` — проверка что ManagedOAuth требует auth_service
- `warmup_cli_oauth_skips_validation()` — проверка что CLI OAuth пропускает валидацию

**E2E тест** (`tests/gemini_fallback_oauth_refresh.rs`):
- `gemini_warmup_refreshes_expired_oauth_token()` — live тест с expired токеном и реальным refresh
- `gemini_warmup_with_valid_credentials()` — простой тест что warmup работает с валидными credentials

### Зависимости

Добавлена dev-зависимость `scopeguard = "1.2"` для безопасного восстановления файлов в тестах.

## Верификация

Проверено на live daemon с Telegram ботом:
- OpenAI Codex упал с 429 rate limit
- Fallback на Gemini сработал успешно
- Бот ответил через Gemini без ошибок

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-24 16:03:00 +08:00
NanFengCheong
02b1702a48
fix(telegram): send image attachments when finalizing draft messages
When using streaming mode with Telegram, the finalize_draft function
would only edit the message text and never send actual image attachments
marked with [IMAGE:path] syntax.

This fix:
- Parses attachment markers in finalize_draft
- Deletes the draft message when attachments are present
- Sends text and attachments as separate messages
- Maintains backward compatibility for text-only messages

Fixes: Telegram finalize_draft edit failed; falling back to sendMessage
2026-02-24 16:03:00 +08:00
reidliu41
ef47cf14c3
feat(models): add list, set, and status subcommands 2026-02-24 16:03:00 +08:00
argenis de la rosa
1a0e5547d7
fix(web): call doctor endpoint with authenticated POST 2026-02-24 16:02:59 +08:00
argenis de la rosa
055507bd18
feat(agent): log query classification route decisions 2026-02-24 16:02:59 +08:00
argenis de la rosa
731545e405
fix(ollama): handle blank responses without tool calls 2026-02-24 16:02:59 +08:00
argenis de la rosa
03328617c9
fix(provider): disable native tool calling for MiniMax 2026-02-24 16:02:59 +08:00
Argenis
46ef41ac65
fix(agent): parse tool <name> markdown fence format (#1438)
Issue: #1420

Some LLM providers (e.g., xAI grok) output tool calls in the format:
```tool file_write
{"path": "...", "content": "..."}
```

Previously, ZeroClaw only matched:
- ```tool_call
- ```tool-call
- ```toolcall
- ```invoke

This caused silent failures where:
1. Tool calls were not parsed
2. Agent reported success but no tools executed
3. LLM hallucinated tool execution results

Fix:
1. Added new regex `MD_TOOL_NAME_RE` to match ` ```tool <name>` format
2. Parse the tool name from the code block header
3. Parse JSON arguments from the block content
4. Updated `detect_tool_call_parse_issue()` to include this format

Added 3 tests:
- parse_tool_calls_handles_tool_name_fence_format
- parse_tool_calls_handles_tool_name_fence_shell
- parse_tool_calls_handles_multiple_tool_name_fences

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:02:59 +08:00
Chummy
fc8696b9b8
fix(provider): fallback native tools on parser-style 5xx 2026-02-24 16:02:59 +08:00
Argenis
d3c8ff6abe
feat(config): warn on unknown config keys to prevent silent misconfig (#1410)
* ci(homebrew): prefer HOMEBREW_UPSTREAM_PR_TOKEN with fallback

* ci(homebrew): handle existing upstream remote and main base

* fix(skills): allow cross-skill references in open-skills audit

Issue: #1391

The skill audit was too strict when validating markdown links in
open-skills, causing many skills to fail loading with errors like:
- "absolute markdown link paths are not allowed (../other-skill/SKILL.md)"
- "markdown link points to a missing file (skill-name.md)"

Root cause:
1. `looks_like_absolute_path()` rejected paths starting with ".."
   before canonicalization could validate they stay within root
2. Missing file errors were raised for cross-skill references that
   are valid but point to skills not installed locally

Fix:
1. Allow ".." paths to pass through to canonicalization check which
   properly validates they resolve within the skill root
2. Treat cross-skill references (parent dir traversal or bare .md
   filenames) as non-fatal when pointing to missing files

Cross-skill references are identified by:
- Parent directory traversal: `../other-skill/SKILL.md`
- Bare skill filename: `other-skill.md`
- Explicit relative: `./other-skill.md`

Added 6 new tests to cover edge cases for cross-skill references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(config): warn on unknown config keys to prevent silent misconfig

Issue: #1304

When users configure `[providers.ollama]` with `api_url`, the setting is
silently ignored because `[providers.*]` sections don't exist in the
config schema. This causes Ollama to always use localhost:11434 regardless
of the configured URL.

Fix: Use serde_ignored to detect and warn about unknown config keys at
load time. This helps users identify misconfigurations like:
- `[providers.ollama]` (should be top-level `api_url`)
- Typos in section names
- Deprecated/removed options

The warning is non-blocking - config still loads, but users see:
```
WARN Unknown config key ignored: "providers". Check config.toml...
```

This follows the fail-fast/explicit errors principle (CLAUDE.md §3.5).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Will Sarg <12886992+willsarg@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:02:59 +08:00
Chummy
920568625b
refactor(telegram): remove redundant else in startup probe 2026-02-24 16:02:59 +08:00
Chummy
83e14a27aa
style(telegram): format startup probe warning log 2026-02-24 16:02:59 +08:00
zeroclaw
79a2d992b0
fix(telegram): add debug log at startup probe success
Add a debug-level log line confirming when the startup probe succeeds
and the main long-poll loop is entered. Aids diagnostics when
troubleshooting persistent 409s (e.g. from an external competing poller).

Note: persistent 409 despite the startup probe and 35s backoff indicates
an external process is actively polling the same bot token from another
host. In that case, rotating the bot token via @BotFather is the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 16:02:59 +08:00
zeroclaw
7c6430126b
fix(telegram): add startup probe + extend 409 backoff to eliminate polling conflict
Every daemon restart produced a flood of 409 Telegram polling conflicts for
up to several minutes. Two changes fix this:

1. **Startup probe (retry loop):** Before entering the long-poll loop,
   repeatedly issue `getUpdates?timeout=0` until a 200 OK is received.
   This claims the Telegram getUpdates slot before the 30-second long-poll
   starts, preventing the first long-poll from racing a stale server-side
   session left by the previous daemon. The probe retries every 5 seconds
   until the slot is confirmed free.

2. **Extended 409 backoff:** Increased from 2 s → 35 s (> the 30-second
   poll timeout). If a 409 still occurs despite the probe (e.g. in a genuine
   dual-instance scenario), the retry now waits long enough for the competing
   session to expire naturally before the next attempt, instead of hammering
   Telegram with ~15 retries per minute.

Fixes #1281.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 16:02:59 +08:00
Chummy
24720c5dd5
fix(composio): harden v3 slug candidate and test coverage 2026-02-24 16:02:59 +08:00
Bogdan
f1a1f3fdc7
fix tests 2026-02-24 16:02:59 +08:00
Bogdan
a01a84c8fe
feat(tools): stabilize composio slug resolution and drop v2 fallback
- add cache + candidate builder for Composio action/tool slugs so execute runs without manual priming @src/tools/composio.rs#285-320
- remove unused v2 execute/connect code paths and rely on HTTPS-only v3 endpoints @src/tools/composio.rs#339-502
- extend tooling tests to cover slug candidate generation variants @src/tools/composio.rs#1317-1324
2026-02-24 16:02:59 +08:00
argenis de la rosa
6729d34cf1
fix(skills): allow cross-skill references in open-skills audit
Issue: #1391

The skill audit was too strict when validating markdown links in
open-skills, causing many skills to fail loading with errors like:
- "absolute markdown link paths are not allowed (../other-skill/SKILL.md)"
- "markdown link points to a missing file (skill-name.md)"

Root cause:
1. `looks_like_absolute_path()` rejected paths starting with ".."
   before canonicalization could validate they stay within root
2. Missing file errors were raised for cross-skill references that
   are valid but point to skills not installed locally

Fix:
1. Allow ".." paths to pass through to canonicalization check which
   properly validates they resolve within the skill root
2. Treat cross-skill references (parent dir traversal or bare .md
   filenames) as non-fatal when pointing to missing files

Cross-skill references are identified by:
- Parent directory traversal: `../other-skill/SKILL.md`
- Bare skill filename: `other-skill.md`
- Explicit relative: `./other-skill.md`

Added 6 new tests to cover edge cases for cross-skill references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:02:59 +08:00
Chummy
1b131b5256
fix: route heartbeat outputs to configured channels 2026-02-24 16:02:59 +08:00
zhzy0077
aba3a146c1
fix(channels): expand lark ack reactions with valid emoji_type ids
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-24 16:02:59 +08:00
zhzy0077
5e4bbd39a5
fix(channels): use valid Feishu emoji_type for lark ack
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-24 16:02:59 +08:00
cee ray
9d4c9b1af9
fix(providers): disable Responses API fallback for NVIDIA NIM
NVIDIA's NIM API (integrate.api.nvidia.com) does not support the
OpenAI Responses API endpoint. When chat completions returns a
non-success status, the fallback to /v1/responses also fails with
404, producing a confusing double-failure error.

Use `new_no_responses_fallback()` for the NVIDIA provider, matching
the approach already used for GLM and other chat-completions-only
providers.

Fixes #1282
2026-02-24 16:02:59 +08:00
Chummy
409a74c72b
fix(kimi-code): include empty reasoning_content in tool history 2026-02-24 16:02:59 +08:00
Chummy
4a2503605d
test(cron): add shell one-shot regression coverage 2026-02-24 16:02:59 +08:00
reidliu41
d6283d2bab
fix(cron): set delete_after_run for one-shot shell jobs 2026-02-24 16:02:59 +08:00
Chummy
ef8f2fed70
fix(discord): send attachment markers as files/urls 2026-02-24 16:02:59 +08:00
Chummy
ce53dcde46
fix(minimax): avoid parsing merged system image markers as vision parts 2026-02-24 16:02:59 +08:00
Chummy
c6eb44438b
fix(channels): render WhatsApp Web pairing QR in terminal 2026-02-24 16:02:59 +08:00
Chummy
f162eede13
fix(config): enforce 0600 on every config save 2026-02-24 16:02:59 +08:00
Chummy
123be02653
fix(slack): bootstrap poll cursor to avoid replay 2026-02-24 16:02:59 +08:00
Chummy
742aa0208f
fix(security): honor explicit command paths in allowed_commands 2026-02-24 16:02:59 +08:00
Ken Yeung
ecc8865cb7
feat: add WATI WhatsApp Business API channel (#1472)
Add a new WATI channel for WhatsApp Business API integration via the
WATI managed platform. WATI simplifies WhatsApp integration with its
own REST API and webhook system.

- New WatiChannel implementation (webhook mode, REST send)
- WatiConfig with api_token, api_url, tenant_id, allowed_numbers
- Gateway routes: GET/POST /wati for webhook verification and messages
- Flexible webhook parsing handles WATI's variable field names
- 15 unit tests covering parsing, allowlist, timestamps, phone normalization
2026-02-23 08:02:00 -05:00
Alex
10dd428de1
feat(providers): add Novita AI as OpenAI-compatible provider (#1496)
- Register Novita AI in provider factory with NOVITA_API_KEY env var
- Add to integrations registry with active/available status detection
- Configure onboarding wizard with default model and API endpoint
- Add to PR labeler provider keyword hints
- Update providers reference documentation

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-23 07:58:49 -05:00
Erica Stith
63f485e56a
feat(security): Add prompt injection defense and leak detection (#1433)
Contributed from RustyClaw (MIT licensed).

## PromptGuard (src/security/prompt_guard.rs)

Detects and blocks/warns about prompt injection attacks:
- System prompt override attempts ("ignore previous instructions")
- Role confusion attacks ("you are now...", "act as...")
- Tool call JSON injection
- Secret extraction attempts
- Command injection patterns in tool arguments
- Jailbreak attempts (DAN mode, developer mode, etc.)

Features:
- Configurable sensitivity (0.0-1.0)
- Configurable action (Warn/Block/Sanitize)
- Pattern-based detection with regex
- Normalized scoring across categories

## LeakDetector (src/security/leak_detector.rs)

Prevents credential exfiltration in outbound content:
- API key patterns (Stripe, OpenAI, Anthropic, Google, GitHub)
- AWS credentials (Access Key ID, Secret Access Key)
- Generic secrets (passwords, tokens in config)
- Private keys (RSA, EC, OpenSSH PEM blocks)
- JWT tokens
- Database connection URLs (PostgreSQL, MySQL, MongoDB, Redis)

Features:
- Automatic redaction of detected secrets
- Configurable sensitivity
- Returns both detection info and redacted content

## Integration

Both modules are exported from `security` module:
```rust
use zeroclaw::security::{PromptGuard, GuardResult, LeakDetector, LeakResult};
```

## Attribution

RustyClaw: https://github.com/rexlunae/RustyClaw
License: MIT
2026-02-23 07:48:18 -05:00
Vernon Stinebaker
7e6491142e fix(provider): preserve reasoning_content in tool-call conversation history
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
2026-02-22 17:40:48 +08:00
agorevski
8331c65bcc fix(telegram): prevent non-image files from getting [IMAGE:] markers
Add file extension validation before generating [IMAGE:] markers for
incoming Telegram attachments. Non-image files (e.g. .md, .txt, .pdf)
now always use [Document:] format regardless of how Telegram classifies
them, preventing false vision capability errors.

Extract format_attachment_content() and is_image_extension() helpers
to centralize the logic and make it testable.

Fixes #1274

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-22 17:29:53 +08:00
Chummy
8db161a8e0 feat(channel): split lark and feishu providers 2026-02-22 14:10:34 +08:00
Will Sarg
7864118c40 fix(onboard): serialize quick-setup env tests safely 2026-02-21 20:46:02 -05:00
EC2 Default User
8c71aaa791 fix(provider): clamp gpt-5-codex reasoning effort 2026-02-21 23:37:20 +08:00
Chummy
9735253484 fix(tool): harden content_search parsing and output safety 2026-02-21 23:26:11 +08:00
Chummy
a36b1466ff feat(security): add otp and estop phase-1 foundation 2026-02-21 23:19:36 +08:00
Chummy
9098b379dd feat(skills): enforce static security audit for skill install/load 2026-02-21 22:54:55 +08:00
Chummy
1b8370f74b feat(channels): add immediate ack reactions for telegram discord and lark 2026-02-21 22:47:54 +08:00
Chummy
e5bc9514a4 security: close shell path-policy bypasses 2026-02-21 22:35:52 +08:00
reidliu41
007a7e2ec6 feat(tool): add content_search tool for regex-based file content search 2026-02-21 22:24:03 +08:00
Chummy
cb62135887 fix(channel/qq): preserve image attachments in inbound messages 2026-02-21 22:06:27 +08:00
Chummy
38e27ff629 test(schedule): lock in rate-limit blocking for mutating actions 2026-02-21 21:20:53 +08:00
Chummy
a92f5c94cd test(cron): cover rate-limit policy gates across cron tools 2026-02-21 21:04:22 +08:00
Chummy
9b40130a53 fix(agent): set tool_call_id for glm shortened parser 2026-02-21 20:48:06 +08:00
Chummy
09861fc52d fix(agent): make tool_call_id field initialization explicit 2026-02-21 20:48:06 +08:00
Chummy
74a29ec096 fix(agent): stabilize tool-call loop dedupe and id propagation
Refs #1242
2026-02-21 20:48:06 +08:00
Chummy
85f218eb0f feat(tools): add natural-language model routing config tool 2026-02-21 20:45:43 +08:00
Chummy
ccc3d6759f security: block plain shell variable expansion and forbidden path args 2026-02-21 20:42:48 +08:00
chumyin0912@gmail.com
13429566b8 fix(agent): map shortened browser alias args to shell command 2026-02-21 20:02:36 +08:00