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>
- 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>
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.
The refresh-skills test was missing the autonomy_level parameter
added to build_system_prompt_with_mode and ChannelRuntimeContext
by a recently merged PR.
Resolve conflict in src/channels/mod.rs Safety section. Keeps the
PR's AutonomyConfig-based prompt construction (build_system_prompt_with_mode_and_autonomy)
while incorporating master's granular safety rules (conditional
destructive-command and ask-before-acting lines based on autonomy level).
Also fixes missing autonomy_level arg in refresh-skills test and removes
duplicate autonomy.level args from auto-merged call sites.
* 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.
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
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
* 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
* 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
* 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.
* 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.
* fix(openrouter): wire provider_timeout_secs through factory
Apply the configured provider_timeout_secs to OpenRouterProvider
in the provider factory, matching the pattern used for compatible
providers.
* fix(openrouter): add timeout_secs field to OpenRouterProvider
Add a configurable timeout_secs field (default 120s) and a
with_timeout_secs() builder method so the HTTP client timeout
can be overridden via provider config instead of being hardcoded.
* refactor(openrouter): improve response decode error messages
Read the response body as text first, then parse with
serde_json::from_str so that decode failures include a truncated
snippet of the raw body for easier debugging.
* test(openrouter): add timeout_secs configuration tests
Verify that the default timeout is 120s and that with_timeout_secs
correctly overrides it.
* style: run rustfmt on openrouter.rs
- Channel tool filtering (`non_cli_excluded_tools`) now respects
`autonomy.level = "full"` — full-autonomy agents keep all tools
available regardless of channel.
- Gateway `process_message` now creates and passes an `ApprovalManager`
to `agent_turn`, so `ReadOnly`/`Supervised` policies are enforced
instead of silently skipped.
- Gateway also applies `non_cli_excluded_tools` filtering with the same
full-autonomy bypass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The conversational_ai config section is parsed but not yet consumed by
any runtime code. Emit a startup warning so users know the setting is
ignored, and update the doc comment to mark it as reserved for future use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When autonomy.level is set to "full", the channel/web system prompt no
longer includes instructions telling the model to ask for permission
before executing tools. Previously these safety lines were hardcoded
regardless of autonomy config, causing the LLM to simulate approval
dialogs in channel and web-interface modes even though the
ApprovalManager correctly allowed execution.
The fix adds an autonomy_level parameter to build_system_prompt_with_mode
and conditionally omits the "ask before acting" instructions when the
level is Full. Core safety rules (no data exfiltration, prefer trash)
are always included.
The [conversational_ai] config section was serialized into every
freshly-generated config.toml despite the feature being experimental
and not yet wired into the agent runtime. This confused new users who
found an undocumented section in their config.
Add skip_serializing_if = "ConversationalAiConfig::is_disabled" so the
section is only written when a user has explicitly enabled it. Existing
configs that already contain the section continue to deserialize
correctly via #[serde(default)].
Fixes#3958
Lower the default heartbeat interval to 5 minutes to match the renewable
partial wake-lock cadence. Add `[heartbeat task` to the memory auto-save
skip filter so heartbeat prompts (both Phase 1 decision and Phase 2 task
execution) do not pollute persistent conversation memory.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace workspace_dir.join(path) with resolve_tool_path(path) in
file_write, file_edit, and pdf_read tools to correctly handle absolute
paths within the workspace directory, preventing path doubling.
Closes#3774
The OtpConfig struct uses deny_unknown_fields but was missing the
challenge_max_attempts field, causing zeroclaw config schema to fail
with a TOML parse error when the field appeared in config files.
Add challenge_max_attempts as an Option<u32>-style field with a default
of 3 and a validation check ensuring it is greater than 0.
Repositions the one-time pairing code display to appear directly below
the dashboard URL for cleaner terminal output, and removes the duplicate
display that was showing at the bottom of the route list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the workspace is created outside of `zeroclaw onboard` (e.g., via
cron, daemon, or `< /dev/null`), SOUL.md and IDENTITY.md were never
scaffolded, causing the agent to activate without identity files.
Added `ensure_bootstrap_files()` in `Config::load_or_init()` that
idempotently creates default SOUL.md and IDENTITY.md if missing.
Closes#3819.
Add `timeout_secs` and `agentic_timeout_secs` fields to
`DelegateAgentConfig` so users can tune per-agent timeouts instead
of relying on the hardcoded 120s / 300s defaults.
Validation rejects values of 0 or above 3600s, matching the pattern
used by MCP timeout validation.
Closes#3898
Add a locale-aware tool description system that loads translations from
TOML files in tool_descriptions/. This enables non-English users to see
tool descriptions in their language.
- Add src/i18n.rs module with ToolDescriptions loader, locale detection
(ZEROCLAW_LOCALE, LANG, LC_ALL env vars), and English fallback chain
- Add locale config field to Config struct for explicit locale override
- Create tool_descriptions/en.toml with all 47 tool descriptions
- Create tool_descriptions/zh-CN.toml with Chinese translations
- Integrate with ToolsSection::build() and build_tool_instructions()
to resolve descriptions from locale files before hardcoded fallback
- Add PromptContext.tool_descriptions field for prompt-time resolution
- Add AgentBuilder.tool_descriptions() setter for Agent construction
- Include tool_descriptions/ in Cargo.toml package include list
- Add 8 unit tests covering locale loading, fallback chains, env
detection, and config override
Closes#3901