Commit Graph

1228 Commits

Author SHA1 Message Date
Argenis
c4ac5ca1e8
fix(tool): include descriptions in deferred MCP tools system prompt (#4018)
Add tool descriptions alongside names in the deferred tools section so
the LLM can better identify which tool to activate via tool_search.

Original author: @mark-linyb
2026-03-24 15:30:34 +03:00
Octopus
c9f6e97334
feat: upgrade MiniMax default model to M2.7 (#3865)
Add MiniMax-M2.7 and M2.7-highspeed to model selection lists. Set M2.7 as the new default for MiniMax, Novita, and Ollama cloud providers. Retain all previous models (M2.5, M2.1, M2) as alternatives.
2026-03-24 15:30:34 +03:00
Eddie's AI Agent
7d2ae75fdd
fix(channel): remove dead AtomicU32 fallback in channels mod (#3521)
The conditional cfg branches for AtomicU32 (32-bit fallback) and
AtomicU64 (64-bit) became dead code after portable_atomic::AtomicU64
was adopted in bd757996. The AtomicU32 branch would fail to compile
on 32-bit targets because the import was removed but the usage remained.

Use portable_atomic::AtomicU64 unconditionally, which works on all
targets.

Fixes #3452

Co-authored-by: SpaceLobster <spacelobster@SpaceLobsters-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:30:34 +03:00
Eddie's AI Agent
5900b32b61
fix(channel): use uppercase IMAGE tag in Matrix channel for multimodal handling (#3523)
Closes #3486

Co-authored-by: SpaceLobster <spacelobster@SpaceLobsters-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:30:34 +03:00
Eddie's AI Agent
d4c585e71f
fix(packaging): ensure Homebrew var directory exists on first start (#3524)
* fix(packaging): ensure Homebrew var directory exists on first start

When zeroclaw is installed via Homebrew, the service plist references
/opt/homebrew/var/zeroclaw (or /usr/local/var/zeroclaw on Intel) for
runtime data, but this directory was never created. This caused
`brew services start zeroclaw` to fail on first use.

The fix detects Homebrew installations by checking if the binary lives
under a Homebrew prefix (Cellar path or symlinked bin/ with Cellar
sibling). When detected:

- install_macos() creates the var directory and sets
  ZEROCLAW_CONFIG_DIR + WorkingDirectory in the generated plist
- start() defensively ensures the var directory exists before
  invoking launchctl

Closes #3464

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(service): fix cargo fmt formatting in Homebrew var dir tests

Collapse multi-line assert_eq! macros to single-line form as
required by cargo fmt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: SpaceLobster <spacelobster@SpaceLobsters-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:30:33 +03:00
Darren.Zeng
fdd006b062
docs(service): add SAFETY comments to unsafe libc::getuid() calls (#3869)
Add proper SAFETY documentation comments to the two unsafe blocks using
libc::getuid() in src/service/mod.rs:

1. In is_root() function: documents why getuid() is safe to call
2. In is_root_matches_system_uid() test: documents the test's purpose

This addresses the best practices audit finding about unsafe code without
safety comments. While we could use the nix crate's safe wrapper, adding
safety comments is a minimal change that satisfies the audit requirement
without introducing new dependencies.

As noted in refactor-candidates.md, the nix crate alternative would also
be acceptable, but this change follows the principle of minimal intervention
for documentation-only improvements.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 15:30:33 +03:00
linyibin
24fd1b83a9
fix(config): handle tilde expansion in non-TTY environments (cron) (#3849)
When HOME environment variable is not set (common in cron jobs or
other non-TTY environments), `shellexpand::tilde` returns the literal
`~` unexpanded. This caused SOUL.md, IDENTITY.md, and other workspace
files to not be loaded because the workspace_dir path was invalid.

This fix adds `expand_tilde_path()` helper that falls back to
`directories::UserDirs` when shellexpand fails to expand tilde.
If both methods fail, a warning is logged advising users to use
absolute paths or set HOME explicitly in cron environments.

Closes #3819
2026-03-24 15:30:33 +03:00
Argenis
21f076e7d3
fix(provider): disable native tool calling for Venice (#4016)
Venice's API accepts the OpenAI-compatible tool format without error
but models ignore the tool specs and hallucinate tool usage in prose
instead. Disable native tool calling so Venice uses prompt-guided
tools, which work reliably.

Add without_native_tools() builder method to OpenAiCompatibleProvider
for providers that support system messages but not native function
calling.

Closes #4007
2026-03-24 15:30:32 +03:00
Argenis
7c14a3adf9
fix(channel): clear persisted JSONL session on /new command (#4014)
The /new command only cleared in-memory conversation history but left
the JSONL session file on disk. On daemon restart, stale history was
rehydrated, negating the user's session reset. This caused context
pollution and degraded tool calling reliability.

Add delete_session() to SessionStore and call it from the /new handler
so both in-memory and persisted state are cleared.

Closes #4009
2026-03-24 15:30:32 +03:00
Argenis
9859eb6e0e
feat(tools): add calculator tool with arithmetic and statistical functions (#4012)
Provides 25 functions (add, subtract, divide, multiply, pow, sqrt, abs,
modulo, round, log, ln, exp, factorial, sum, average, median, mode, min,
max, range, variance, stdev, percentile, count, percentage_change, clamp)
as a single always-enabled tool to avoid LLM miscalculations and reduce
token waste on numeric tasks.

Co-authored-by: Anatolii <anatolii@Anatoliis-MacBook.local>
2026-03-24 15:30:32 +03:00
Argenis
a198b26594
fix(channel): add interruption_scope_id for thread-aware cancellation scoping (#4017)
Add interruption_scope_id to ChannelMessage for thread-aware cancellation. Slack genuine thread replies and Matrix threads get scoped keys, preventing cross-thread cancellation. All other channels preserve existing behavior.

Supersedes #3900. Depends on #3891.
2026-03-24 15:30:31 +03:00
Nim G
2b8297dc31
feat(channel): add interrupt_on_new_message support for Discord (#3918)
* feat(channel): add /stop command to cancel in-flight tasks

Adds an explicit /stop slash command that allows users on any non-CLI
channel (Matrix, Telegram, Discord, Slack, etc.) to cancel an agent
task that is currently running.

Changes:
- is_stop_command(): new helper that detects /stop (case-insensitive,
  optional @botname suffix), not gated on channel type
- /stop fast path in run_message_dispatch_loop: intercepts /stop before
  semaphore acquisition so the target task is never replaced in the store;
  fires CancellationToken on the running task; sends reply via tokio::spawn
  using the established two-step channel lookup pattern
- register_in_flight separated from interrupt_enabled: all non-CLI tasks
  now enter the in_flight_by_sender store, enabling /stop to reach them;
  auto-cancel-on-new-message remains gated on interrupt_enabled (Telegram/
  Slack only) — this is a deliberate broadening, not a side effect

Deferred to follow-up (feat/matrix-interrupt-on-new-message):
- interrupt_on_new_message config field for Matrix
- thread-aware interruption_scope_key (requires per-channel thread_ts
  semantics analysis; Slack always sets thread_ts, Matrix only for replies)

Supersedes #2855

Tests: 7 new unit tests for is_stop_command; all 4075 tests pass.

* feat(channel): add interrupt_on_new_message support for Discord

---------

Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
2026-03-24 15:30:31 +03:00
argenis de la rosa
08851b0145
fix: add missing mattermost field to test InterruptOnNewMessageConfig 2026-03-24 15:30:30 +03:00
Nim G
2aaf916185
feat(channel): add interrupt_on_new_message support for Mattermost 2026-03-24 15:30:29 +03:00
Argenis
c1095de1ac
Fix delegate agent timeout config regression (#4004)
* feat(delegate): make delegate timeout configurable via config.toml

Add configurable timeout options for delegate tool:
- timeout_secs: for non-agentic sub-agent calls (default: 120s)
- agentic_timeout_secs: for agentic sub-agent runs (default: 300s)

Previously these were hardcoded constants (DELEGATE_TIMEOUT_SECS
and DELEGATE_AGENTIC_TIMEOUT_SECS). Users can now customize them
in config.toml under [[delegate.agents]] section.

Fixes #3898

* feat(config): make delegate tool timeouts configurable via config.toml

This change makes the hardcoded 120s/300s delegate tool timeouts
configurable through the config file:

- Add [delegate] section to Config with timeout_secs and agentic_timeout_secs
- Add DelegateToolConfig struct for global default timeout values
- Add DEFAULT_DELEGATE_TIMEOUT_SECS (120) and DEFAULT_DELEGATE_AGENTIC_TIMEOUT_SECS (300) constants
- Remove hardcoded constants from delegate.rs
- Update tests to use constant values instead of magic numbers
- Update examples/config.example.toml with documentation

Closes #3898

* fix: keep delegate timeout fields as Option<u64> with global fallback

- Change DelegateAgentConfig.timeout_secs and agentic_timeout_secs from
  u64 to Option<u64> so per-agent overrides are truly optional
- Implement manual Default for DelegateToolConfig with correct values
  (120s and 300s) instead of derive(Default)
- Add DelegateToolConfig to DelegateTool struct and wire through
  constructors so agent timeouts fall back to global [delegate] config
- Add validation for delegate timeout values in Config::validate()
- Fix example config to use [agents.name] table syntax matching the
  HashMap<String, DelegateAgentConfig> schema
- Add missing timeout fields to all DelegateAgentConfig struct literals
  across codebase (doctor, swarm, model_routing_config, tools/mod)

* chore: trigger CI

* chore: retrigger CI

* fix: cargo fmt line wrapping in config/mod.rs

* fix: import timeout constants in delegate tests

* style: cargo fmt

---------

Co-authored-by: vincent067 <vincent067@outlook.com>
2026-03-24 15:29:57 +03:00
Argenis
82d485874a
add configurable allow_scripts audit option (#4001)
Co-authored-by: wangyingtao.10 <wangyingtao.10@jd.com>
2026-03-24 15:29:57 +03:00
Nim G
da7a0a9474
feat(channel): add /stop command to cancel in-flight tasks
Adds an explicit /stop slash command that allows users on any non-CLI
channel (Matrix, Telegram, Discord, Slack, etc.) to cancel an agent
task that is currently running.

Changes:
- is_stop_command(): new helper that detects /stop (case-insensitive,
  optional @botname suffix), not gated on channel type
- /stop fast path in run_message_dispatch_loop: intercepts /stop before
  semaphore acquisition so the target task is never replaced in the store;
  fires CancellationToken on the running task; sends reply via tokio::spawn
  using the established two-step channel lookup pattern
- register_in_flight separated from interrupt_enabled: all non-CLI tasks
  now enter the in_flight_by_sender store, enabling /stop to reach them;
  auto-cancel-on-new-message remains gated on interrupt_enabled (Telegram/
  Slack only) — this is a deliberate broadening, not a side effect

Deferred to follow-up (feat/matrix-interrupt-on-new-message):
- interrupt_on_new_message config field for Matrix
- thread-aware interruption_scope_key (requires per-channel thread_ts
  semantics analysis; Slack always sets thread_ts, Matrix only for replies)

Supersedes #2855

Tests: 7 new unit tests for is_stop_command; all 4075 tests pass.
2026-03-24 15:29:57 +03:00
Argenis
3a19f1466d
Fix Jira tool panics and dedup bug (#4003)
* feat: add Jira tool with get_ticket, search_tickets, and comment_ticket

Implements a new `jira` tool following the existing zeroclaw tool
conventions (Tool trait, SecurityPolicy, config-gated registration).

- get_ticket: configurable detail level (basic/basic_search/full/changelog)
  with response shaping; always in the default allowed_actions list
- search_tickets: JQL-based search with cursor pagination (nextPageToken);
  always returns basic_search shape; gated by allowed_actions
- comment_ticket: posts ADF comments with inline markdown-like syntax —
  @email mentions resolved to Jira accountId, **bold**, bullet lists,
  newlines; gated by allowed_actions and SecurityPolicy Act operation

Config: [jira] section with base_url, email, api_token (encrypted at
rest, falls back to JIRA_API_TOKEN env var), allowed_actions (default:
["get_ticket"]), and timeout_secs. Validated on load.

Tool description in tool_descriptions/en.toml documents all three
actions and the full comment syntax for the AI system prompt.

* fix: address jira tool code review findings

High priority:
- Validate issue_key against ^[A-Z][A-Z0-9]+-\d+$ before URL interpolation
  to prevent path traversal in get_ticket and comment_ticket

Medium priority:
- Add email guard in tool registration (mod.rs) to skip with a warning
  instead of registering a broken tool when jira.email is empty
- Shape comment_ticket response to return only id, author, created —
  avoids exposing internal Jira metadata to the AI
- Replace O(n²) comment matching in shape_basic with a HashMap lookup
  keyed by comment ID for O(1) access
- Add api_token validation in Config::validate() checking both the
  config field and JIRA_API_TOKEN env var when jira.enabled = true

Low priority:
- Make shape_basic_search private (was accidentally pub)
- Extend clean_email to strip leading punctuation (( and [) so that
  @(john@co.com) resolves correctly; fix suffix computation via pointer
  arithmetic to handle the shifted offset
- Clarify tool_descriptions/en.toml: @prefix is required for mentions,
  bare emails without @ are treated as plain text
- Handle unmatched ** in parse_inline: emit as literal text instead of
  silently producing a bold node with no closing marker

* fix(jira): allow lowercase project keys in issue_key validation

Relax validate_issue_key to accept both PROJ-123 and proj-123, since
some Jira instances use lowercase custom project keys. Path traversal
protection is preserved via alphanumeric + digit-number requirement.

* feat(tools): add tool honesty instructions to system prompt

Prevent AI from fabricating tool results by injecting a CRITICAL:
Tool Honesty section into both channel and CLI/agent system prompts.

Rules: never fabricate or guess tool results, report errors as-is,
and ask the user when unsure if a tool call succeeded.

* style: sort JiraConfig import alphabetically in config/mod.rs

* style(jira): fix strict clippy lints in jira_tool

- Derive Default for LevelOfDetails instead of manual impl
- Use char arrays in trim_start_matches/trim_end_matches
- Allow cast_possible_truncation on search_tickets (usize->u32 bounded by max_results)
- Remove needless borrow on &email

* fix(ci): adapt to upstream autonomy_level additions in channels/mod.rs

- Add missing autonomy_level argument to build_system_prompt_with_mode call in test
- Add missing autonomy_level field in ChannelRuntimeContext test initializer
- Allow large_futures in load_or_init test (Config struct growth from JiraConfig)

* fix(ci): resolve duplicate and missing autonomy_level in test initializers

* fix(ci): use TelegramRecordingChannel in telegram-specific test

The test process_channel_message_executes_tool_calls_instead_of_sending_raw_json
sent messages on channel "telegram" but registered RecordingChannel (name:
"test-channel"), causing the channel lookup to return None and no messages to
be sent.

* fix(jira): prevent panics on short dates, fix dedup bug, normalize base_url

- Add date_prefix() helper to safely slice date strings instead of
  panicking on empty or short strings from the Jira API.
- Replace Vec::dedup() with HashSet-based retain in extract_emails()
  to correctly deduplicate non-adjacent duplicates.
- Strip trailing slashes from base_url during construction to prevent
  double-slash URLs.
- Add tests for date_prefix and non-adjacent email dedup.

---------

Co-authored-by: Anatolii <anatolii@Anatoliis-MacBook.local>
Co-authored-by: Anatolii <anatolii.fesiuk@gmail.com>
2026-03-24 15:29:57 +03:00
Argenis
c56e717ac4
fix(agent): preserve native tool-call text in draft updates (#4005)
Preserve assistant text from native tool-call turns in draft updates. Falls back to response_text when parsed_text is empty and native tool calls are present. Relays text through on_delta for draft-capable channels like Telegram.

Supersedes #3976. Closes #3974
2026-03-24 15:29:57 +03:00
Argenis
8288bc0b77
feat(security): inject security policy summary into LLM system prompt (#4002)
Inject a human-readable summary of the active SecurityPolicy into the system prompt Safety section. LLM sees allowed commands, forbidden paths, autonomy level, and rate limits.

Supersedes #3968. Closes #2404
2026-03-24 15:29:56 +03:00
Dmitrii Mukhutdinov
1e49af224e
fix(observability): handle missing OtelObserver match arms and add all-features CI check (#3981)
Add missing CacheHit/CacheMiss match arms to OtelObserver::record_event. Add check-all-features CI job to prevent --all-features compile regressions.
2026-03-24 15:29:56 +03:00
Martin
d550cbc5bf
feat(channels): add TTS voice reply support to Telegram channel (#3942)
Adds text-to-speech output to the Telegram channel, mirroring the
existing WhatsApp Web voice-chat implementation. When a user sends a
voice note (transcribed via STT), the channel enters voice-chat mode
and subsequent agent replies are synthesised into a Telegram voice note
via the configured TTS provider, in addition to the normal text reply.
Sending a text message exits voice-chat mode.

Implementation details:
- Add `tts_config`, `voice_chats`, and `pending_voice` fields to
  `TelegramChannel`
- Add `with_tts()` builder method, gated on `config.enabled`
- Track voice-chat state: enter on successful STT transcription, exit
  on incoming text message
- `synthesize_and_send_voice()` static method: synthesises audio via
  `TtsManager` and uploads to Telegram using `sendVoice` multipart API
- 10-second debounce in `send()` ensures only the final substantive
  reply in a tool chain gets a voice note (skips JSON, code blocks,
  URLs, short status messages)
- Wire `.with_tts(config.tts.clone())` into both Telegram construction
  sites in the channel factory

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 15:29:56 +03:00
argenis de la rosa
95d1474716
fix(docs): use absolute URLs for banner in all READMEs + update web dashboard logo
- Replace relative docs/assets/zeroclaw-banner.png paths with absolute
  raw.githubusercontent.com URLs in all 31 README files so the banner
  renders correctly regardless of where the README is viewed
- Switch web dashboard favicon and logos from logo.png to zeroclaw-trans.png
- Add zeroclaw-trans.png and zeroclaw-banner.png assets
- Update build.rs to track new dashboard asset
- Fix missing autonomy_level in new test + Box::pin large future

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 15:29:56 +03:00
argenis de la rosa
4d7853ca5f
fix: pass autonomy_level through to prompt builder in wrapper function
build_system_prompt_with_mode was discarding the autonomy_level
parameter, passing None to build_system_prompt_with_mode_and_autonomy.
This caused full-autonomy prompts to still include "ask before acting"
instructions. Convert the level to an AutonomyConfig and pass it through.
2026-03-24 15:29:56 +03:00
Alix-007
e256b39733
fix(prompt): respect autonomy level in channel prompts 2026-03-24 15:29:32 +03:00
argenis de la rosa
0b3e58a89a
fix: box-pin large future in config init test to satisfy clippy
Config::load_or_init() produces a future >16KB, triggering
clippy::large_futures. Wrap with Box::pin() as recommended.
2026-03-24 15:29:03 +03:00
argenis de la rosa
ca296e6150
fix: add missing autonomy_level arg to test after merge with master
The refresh-skills test was missing the autonomy_level parameter
added to build_system_prompt_with_mode and ChannelRuntimeContext
by a recently merged PR.
2026-03-24 15:29:03 +03:00
李龙 0668001470
496bd825bc
test(config): fix helper lint and swarms fixture 2026-03-24 15:29:03 +03:00
李龙 0668001470
d23abdb92d
test(config): centralize backward-compat fixtures 2026-03-24 15:29:03 +03:00
李龙 0668001470
a2fdf64a5e
fix(security): block runtime config state edits 2026-03-24 15:29:02 +03:00
Alix-007
d43ce63b4d
feat(skills): add read_skill for compact mode 2026-03-24 15:29:02 +03:00
Alix-007
40e2aaa318
Fix /new regression test lint scope 2026-03-24 15:29:02 +03:00
Alix-007
f9c7d18caf
Refresh skills after new channel sessions 2026-03-24 15:29:02 +03:00
Alix-007
68f4fbd550
fix(tools): normalize workspace-prefixed paths 2026-03-24 15:29:02 +03:00
Alix-007
b19b6822db
style(zai): satisfy rustfmt in tool_stream request 2026-03-24 15:29:02 +03:00
Alix-007
bf4c4d81a0
fix(zai): send tool_stream for tool-capable requests 2026-03-24 15:29:01 +03:00
Alix-007
8275649517
test(claude_code): isolate echo script per test run 2026-03-24 15:29:01 +03:00
李龙 0668001470
7283f3c6fe
test(config): move initialized log regression away from merge hotspot 2026-03-24 15:28:49 +03:00
李龙 0668001470
41f8773a3c
fix(config): avoid clippy used_underscore_binding 2026-03-24 15:28:48 +03:00
Alix-007
4544947a15
fix(config): log existing config as initialized 2026-03-24 15:28:47 +03:00
Argenis
296fff7af9
fix(config): enable compact_context by default (#3995)
* fix: change compact_context default to true

Local LLMs with limited context windows immediately run out of context
when compact_context defaults to false. The system prompt alone can
consume 25K+ tokens, exceeding even 55K context windows with history.

Setting compact_context=true by default limits system prompt injection
to 6000 chars and RAG results to 2 chunks, making the agent usable
with smaller models out of the box.

Fixes #3987

* docs: update compact_context default to true in config reference

Update all locale variants (en, zh-CN, vi) to reflect the new default.

* test: update tests to expect compact_context default of true

Update assertions in schema.rs unit tests and config_persistence.rs
component tests to match the new default value.
2026-03-24 15:27:55 +03:00
Argenis
c41009d29f
fix(cron): persist allowed_tools for agent jobs (#3993)
Persist allowed_tools in cron_jobs table, threading it through CLI add/update and cron_add/cron_update tool APIs. Add regression coverage for store, tool, and CLI roundtrip paths.

Fixups over original PR #3929: add allowed_tools to all_overdue_jobs SELECT (merge gap), resolve merge conflicts.

Closes #3920
Supersedes #3929
2026-03-24 15:26:29 +03:00
Alix-007
b6bc332b68
feat(slack): add thread_replies channel option (#3930)
Add a thread_replies option to Slack channel config (default true). When false, replies go to channel root instead of the originating thread.

Closes #3888
2026-03-24 15:26:28 +03:00
Argenis
53802d6d04
fix(anthropic): always apply cache_control to system prompts (#3990)
* fix: always use Blocks format for system prompts with cache_control

System prompts under 3KB were wrapped in SystemPrompt::String which
cannot carry cache_control headers, resulting in 0% cache hit rate
on Haiku 4.5. Always use SystemPrompt::Blocks with ephemeral
cache_control regardless of prompt size.

Fixes #3977

* fix: lower conversation caching threshold from >4 to >1 messages

The previous threshold of >4 non-system messages was too restrictive,
delaying cache benefits until 5+ turns. Lower to >1 so caching kicks
in after the first user+assistant exchange.

Fixes #3977

* test: update anthropic cache tests for new thresholds and Blocks format

- convert_messages_small_system_prompt now expects Blocks with
  cache_control instead of String variant
- should_cache_conversation tests updated for >1 threshold
- backward_compatibility test replaced with blocks-system test
2026-03-24 15:26:28 +03:00
Argenis
1ff411d8f3
fix(security): wire sandbox into shell command execution (#3989)
* fix: add sandbox field to ShellTool struct

Add `sandbox: Arc<dyn Sandbox>` field to `ShellTool` and a
`new_with_sandbox()` constructor so callers can inject the configured
sandbox backend. The existing `new()` constructor defaults to
`NoopSandbox` for backward compatibility.

Ref: #3983

* fix: apply sandbox wrapping in ShellTool::execute()

Call `self.sandbox.wrap_command()` on the underlying std::process::Command
(via `as_std_mut()`) after building the shell command and before clearing
the environment. This ensures every shell command passes through the
configured sandbox backend before execution.

Ref: #3983

* fix: wire up sandbox creation at ShellTool callsites

In `all_tools_with_runtime()`, create a sandbox from
`root_config.security` via `create_sandbox()` and pass it to
`ShellTool::new_with_sandbox()`. The `default_tools_with_runtime()`
path retains `ShellTool::new()` which defaults to `NoopSandbox`.

Ref: #3983

* test: add sandbox integration tests for ShellTool

Verify that ShellTool can be constructed with a sandbox via
`new_with_sandbox()`, that NoopSandbox leaves commands unmodified,
and that command execution works end-to-end with a sandbox attached.

Ref: #3983
2026-03-24 15:26:28 +03:00
Alix-007
6f08b15d8b
fix(cron): default channel delivery to active reply target 2026-03-24 15:26:28 +03:00
Alix-007
ffa1ee4d3b
fix(onboard): warn when Homebrew service uses another workspace 2026-03-24 15:26:28 +03:00
Alix-007
2ca9ca3285
fix(skills): narrow shell shebang detection (#3944)
Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com>
2026-03-24 15:26:27 +03:00
Giulio V
49d68e55f2
fix(cron): add startup catch-up and drop login shell flag (#3948)
* fix(cron): add startup catch-up and drop login shell flag

Problems:
1. When ZeroClaw started after downtime (late boot, daemon restart),
   overdue jobs were picked up via `due_jobs()` but limited by
   `max_tasks` per poll cycle — with many overdue jobs, catch-up
   could take many cycles.
2. Cron shell jobs used `sh -lc` (login shell), which loads the
   full user profile on every execution — slow and may cause
   unexpected side effects.

Fixes:
- Add `all_overdue_jobs()` store query without `max_tasks` limit
- Add `catch_up_overdue_jobs()` startup phase that runs ALL overdue
  jobs once before entering the normal polling loop
- Extract `build_cron_shell_command()` helper using `sh -c` (non-login)
- Add structured tracing for catch-up progress
- Add tests for all new functions

* feat(cron): make catch-up configurable via API and control panel

Add `catch_up_on_startup` boolean to `[cron]` config (default: true).
When enabled, the scheduler runs all overdue jobs at startup before
entering the normal polling loop. Users can toggle this from:

- The Cron page toggle switch in the control panel
- PATCH /api/cron/settings { "catch_up_on_startup": false }
- The `[cron]` section of the TOML config editor

Also adds GET /api/cron/settings endpoint to read cron subsystem
settings without parsing the full config.

* fix(config): add catch_up_on_startup to CronConfig test constructors

The CI Lint job failed because the `cron_config_serde_roundtrip` test
constructs CronConfig directly and was missing the new field.
2026-03-24 15:26:27 +03:00
Argenis
8db392de7c
fix(providers): exempt tool schema errors from non-retryable classification (#3978)
* fix: exempt tool schema validation errors from non-retryable classification

Groq returns 400 "tool call validation failed" which was classified as
non-retryable by is_non_retryable(), preventing the provider-level
fallback in compatible.rs from executing. Add is_tool_schema_error()
to detect these errors and return false from is_non_retryable(), allowing
the retry loop to pass control back to the provider's built-in fallback.

Fixes #3757

* test: add unit tests for tool schema error detection in reliable.rs

Verify is_tool_schema_error detects Groq-style validation failures and
that is_non_retryable returns false for tool schema 400s while still
returning true for other 400 errors like invalid API key.

* fix: escape format braces in test string literals for cargo check

The anyhow::anyhow! macro interprets curly braces as format
placeholders. Use explicit format argument to pass JSON-containing
strings in tests.
2026-03-24 15:26:26 +03:00