* fix: resolve claude-code test flakiness and update security policy
* fix: restrict `free` command to Linux-only in security policy
`free` is not available on macOS or other BSDs. Move it behind
a #[cfg(target_os = "linux")] gate so it is only included in the
default allowed commands on Linux systems.
---------
Co-authored-by: ninenox <nisit15@hotmail.com>
* feat(channels): add Gmail Pub/Sub push notifications for real-time email
Add GmailPushChannel that replaces IMAP polling with Google's Pub/Sub
push notification system for real-time email-driven automation.
- New channel at src/channels/gmail_push.rs implementing the Channel trait
- Registers Gmail watch subscription (POST /gmail/v1/users/me/watch)
with automatic renewal before the 7-day expiry
- Handles incoming Pub/Sub notifications at POST /webhook/gmail
- Fetches new messages via Gmail History API (startHistoryId-based)
- Dispatches email messages to the agent with full metadata
- Sends replies via Gmail messages.send API
- Config: gmail_push.enabled, topic, label_filter, oauth_token,
allowed_senders, webhook_url
- OAuth token encrypted at rest via existing secret store
- Webhook endpoint added to gateway router
- 30+ unit tests covering notification parsing, header extraction,
body decoding, sender allowlist, and config serialization
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(config): fix pre-existing test compilation errors in schema.rs
- Remove #[cfg(unix)] gate on `use tempfile::TempDir` import since
TempDir is used unconditionally in bootstrap file tests
- Add explicit type annotations on tokio::fs::* calls to resolve
type inference failures (create_dir_all, write, read_to_string)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(channels): fix extract_body_text_plain test
Gmail API sends base64url without padding. The decode_body function
converted URL-safe chars back to standard base64 but did not restore
the padding, causing STANDARD decoder to fail and falling back to
snippet. Add padding restoration before decoding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(channels): address critical security bugs in Gmail Pub/Sub push
- Add webhook authentication via shared secret (webhook_secret config
field or GMAIL_PUSH_WEBHOOK_SECRET env var), preventing unauthorized
message injection through the unauthenticated webhook endpoint
- Add 1MB body size limit on webhook endpoint to prevent memory exhaustion
- Fix race condition in handle_notification: hold history_id lock across
the read-fetch-update cycle to prevent duplicate message processing
when concurrent webhook notifications arrive
- Sanitize RFC 2822 headers (To/Subject) to prevent CRLF injection
attacks that could add arbitrary headers to outgoing emails
- Fix extract_email_from_header panic on malformed angle brackets by
using rfind('>') and validating bracket ordering
- Add 30s default HTTP client timeout for all Gmail API calls,
preventing indefinite hangs
- Clone tx sender before message processing loop to avoid holding
the mutex lock across network calls
---------
Co-authored-by: Giulio V <vannini.gv@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(gateway): add Live Canvas (A2UI) tool and real-time web viewer
Add a Live Canvas system that enables the agent to push rendered content
(HTML, SVG, Markdown, text) to a web-visible canvas in real time.
Backend:
- src/tools/canvas.rs: CanvasTool with render/snapshot/clear/eval actions,
backed by a shared CanvasStore (Arc<RwLock<HashMap>>) with per-canvas
broadcast channels for real-time updates
- src/gateway/canvas.rs: REST endpoints (GET/POST/DELETE /api/canvas/:id,
GET /api/canvas/:id/history, GET /api/canvas) and WebSocket endpoint
(WS /ws/canvas/:id) for real-time frame delivery
Frontend:
- web/src/pages/Canvas.tsx: Canvas viewer page with WebSocket connection,
iframe sandbox rendering, canvas switcher, frame history panel
Registration:
- CanvasTool registered in all_tools_with_runtime (always available)
- Canvas routes wired into gateway router
- CanvasStore added to AppState
- Canvas page added to App.tsx router and Sidebar navigation
- i18n keys added for en/zh/tr locales
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(config): fix pre-existing test compilation errors in schema.rs
- Remove #[cfg(unix)] gate on `use tempfile::TempDir` import since
TempDir is used unconditionally in bootstrap file tests
- Add explicit type annotations on tokio::fs::* calls to resolve
type inference failures (create_dir_all, write, read_to_string)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gateway): share CanvasStore between tool and REST API
The CanvasTool and gateway AppState each created their own CanvasStore,
so content rendered via the tool never appeared in the REST API.
Create the CanvasStore once in the gateway, pass it to
all_tools_with_runtime via a new optional parameter, and reuse the
same instance in AppState.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gateway): address critical security and reliability bugs in Live Canvas
- Validate content_type in REST POST endpoint against allowed set,
preventing injection of "eval" frames via the REST API
- Enforce MAX_CONTENT_SIZE (256KB) limit on REST POST endpoint,
matching tool-side validation to prevent memory exhaustion
- Add MAX_CANVAS_COUNT (100) limit to prevent unbounded canvas creation
and memory exhaustion from CanvasStore
- Handle broadcast RecvError::Lagged in WebSocket handler gracefully
instead of disconnecting the client
- Make MAX_CONTENT_SIZE and ALLOWED_CONTENT_TYPES pub for gateway reuse
- Update CanvasStore::render and subscribe to return Option for
canvas count enforcement
---------
Co-authored-by: Giulio V <vannini.gv@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: rareba <rareba@users.noreply.github.com>
* feat(channels): add voice wake word detection channel
Add VoiceWakeChannel behind the `voice-wake` feature flag that:
- Captures audio from the default microphone via cpal
- Uses energy-based VAD to detect speech activity
- Transcribes speech via the existing transcription API (Whisper)
- Checks for a configurable wake word in the transcription
- On detection, captures the following utterance and dispatches it
as a ChannelMessage
State machine: Listening -> Triggered -> Capturing -> Processing -> Listening
Config keys (under [channels_config.voice_wake]):
- wake_word (default: "hey zeroclaw")
- silence_timeout_ms (default: 2000)
- energy_threshold (default: 0.01)
- max_capture_secs (default: 30)
Includes tests for config parsing, state machine, RMS energy
computation, and WAV encoding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(config): fix pre-existing test compilation errors in schema.rs
- Remove #[cfg(unix)] gate on `use tempfile::TempDir` import since
TempDir is used unconditionally in bootstrap file tests
- Add explicit type annotations on tokio::fs::* calls to resolve
type inference failures (create_dir_all, write, read_to_string)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(channels): exclude voice-wake from all-features CI check
Add a `ci-all` meta-feature in Cargo.toml that includes every feature
except `voice-wake`, which requires `libasound2-dev` (ALSA) not present
on CI runners. Update the check-all-features CI job to use
`--features ci-all` instead of `--all-features`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(channels): address critical bugs in voice wake word detection
- Replace std::mem::forget(stream) with dedicated thread that holds the
cpal stream and shuts down cleanly via oneshot channel, preventing
microphone resource leaks on task cancellation
- Add config validation: energy_threshold must be positive+finite,
silence_timeout_ms >= 100ms, max_capture_secs clamped to 300
- Guard WAV encoding against u32 overflow for large audio buffers
- Add hard cap on capture_buf size to prevent unbounded memory growth
- Increase audio channel buffer from 4 to 64 slots to reduce chunk
drops during transcription API calls
- Remove dead WakeState::Processing variant that was never entered
---------
Co-authored-by: Giulio V <vannini.gv@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace manual can_act()/record_action() with enforce_tool_operation()
to match the codebase convention used by all other tools (notion,
memory_forget, claude_code, delegate, etc.), producing consistent
error messages and avoiding logic duplication.
- Add model parameter validation to prevent URL path traversal attacks
via crafted model identifiers (e.g. "../../evil-endpoint").
- Add tests for model traversal rejection and filename sanitization.
Add ImageGenTool that exposes fal.ai Flux model image generation as a
standalone tool, decoupled from the LinkedIn client. The tool accepts a
text prompt, optional filename/size/model parameters, calls the fal.ai
synchronous API, downloads the result, and saves to workspace/images/.
- New src/tools/image_gen.rs with full Tool trait implementation
- New ImageGenConfig in schema.rs (enabled, default_model, api_key_env)
- Config-gated registration in all_tools_with_runtime
- Security: checks can_act() and record_action() before execution
- Comprehensive unit tests (prompt validation, API key, size enum,
autonomy blocking, tool spec)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For models with small context windows (e.g. glm-4.5-air ~8K tokens),
the system prompt alone can exceed the limit. This adds:
- max_system_prompt_chars config option (default 0 = unlimited)
- compact_context now also compacts the system prompt: skips the
Channel Capabilities section and shows only tool names
- Truncation with marker when prompt exceeds the budget
Users can set `max_system_prompt_chars = 8000` in [agent] config
to cap the system prompt for small-context models.
Closes#4124
auto_approve = ["*"] was doing exact string matching, so only the
literal tool name "*" was matched. Users expecting wildcard semantics
had every tool blocked in supervised mode.
Also adds "prompt exceeds max length" to the context-window error
detection hints (fixes GLM/ZAI error 1261 detection).
Closes#4127
* fix(hardware): drain stdin in subprocess test to prevent broken pipe flake
The test script did not consume stdin, so SubprocessTool's stdin write
raced against the process exit, causing intermittent EPIPE failures.
Add `cat > /dev/null` to drain stdin before producing output.
* style: format subprocess test
The reaction tool was passing the channel adapter name (e.g. "discord",
"slack") as the first argument to Channel::add_reaction() and
Channel::remove_reaction(), but the trait signature expects a
platform-specific channel_id (e.g. Discord channel snowflake, Slack
channel ID like "C0123ABCD"). This would cause all reaction API calls
to fail at the platform level.
Fixes:
- Add required "channel_id" parameter to the tool schema
- Extract and pass channel_id (not channel_name) to trait methods
- Update tool description to mention the new parameter
- Add MockChannel channel_id capture for test verification
- Add test asserting channel_id (not name) reaches the trait
- Update all existing tests to supply channel_id
The output format used "{action}ed" which produced "removeed" for the
remove action. Use explicit past-tense mapping instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove #[cfg(unix)] gate on `use tempfile::TempDir` import since
TempDir is used unconditionally in bootstrap file tests
- Add explicit type annotations on tokio::fs::* calls to resolve
type inference failures (create_dir_all, write, read_to_string)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ReactionTool that exposes Channel::add_reaction and
Channel::remove_reaction as an agent-callable tool. Uses a
late-binding ChannelMapHandle (Arc<RwLock<HashMap>>) pattern
so the tool can be constructed during tool registry init and
populated once channels are available in start_channels.
Parameters: channel, message_id, emoji, action (add/remove).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(config): add configurable pacing controls for slow/local LLM workloads (#2963)
Add a new `[pacing]` config section with four opt-in parameters that
let users tune timeout and loop-detection behavior for local LLMs
(Ollama, llama.cpp, vLLM) without disabling safety features entirely:
- `step_timeout_secs`: per-step LLM inference timeout independent of
the overall message budget, catching hung model responses early.
- `loop_detection_min_elapsed_secs`: time-gated loop detection that
only activates after a configurable grace period, avoiding false
positives on long-running browser/research workflows.
- `loop_ignore_tools`: per-tool loop-detection exclusions so tools
like `browser_screenshot` that structurally resemble loops are not
counted toward identical-output detection.
- `message_timeout_scale_max`: overrides the hardcoded 4x ceiling in
the channel message timeout scaling formula.
All parameters are strictly optional with no effect when absent,
preserving full backwards compatibility.
Closes#2963
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(config): add missing pacing fields in tests and call sites
* fix(config): add pacing arg to remaining cost-tracking test call sites
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
Fixes E0382 borrow-after-move error: wait_with_output() consumed the
child handle, making child.kill() in the timeout branch invalid.
Use kill_on_drop(true) with cmd.output() instead.
Adopted from #3705 by @fangxueshun with fixes:
- Added input validation for date strings (RFC 3339)
- Used chrono DateTime comparison instead of string comparison
- Added since < until validation
- Updated mem0 backend
Supersedes #3705
* feat(channel): add per-channel proxy_url support for HTTP/SOCKS5 proxies
Allow each channel to optionally specify a `proxy_url` in its config,
enabling users behind restrictive networks to route channel traffic
through HTTP or SOCKS5 proxies. When set, the per-channel proxy takes
precedence over the global `[proxy]` config; when absent, the channel
falls back to the existing runtime proxy behavior.
Adds `proxy_url: Option<String>` to all 12 channel config structs
(Telegram, Discord, Slack, Mattermost, Signal, WhatsApp, Wati,
NextcloudTalk, DingTalk, QQ, Lark, Feishu) and introduces
`build_channel_proxy_client`, `build_channel_proxy_client_with_timeouts`,
and `apply_channel_proxy_to_builder` helpers that normalize proxy URLs
and integrate with the existing client cache.
Closes#3262
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(channel): add missing proxy_url fields in test initializers
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
* feat(tool): enrich delegate sub-agent system prompt and add skills_directory config key (#3046)
Sub-agents configured under [agents.<name>] previously received only the
bare system_prompt string. They now receive a structured system prompt
containing: tools section (allowed tools with parameters and invocation
protocol), skills section (from scoped or default directory), workspace
path, current date/time, safety constraints, and shell policy when shell
is in the effective tool list.
Add optional skills_directory field to DelegateAgentConfig for per-agent
scoped skill loading. When unset, falls back to default workspace
skills/ directory.
Closes#3046
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tools): add missing fields after rebase
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
Adds per-channel cost tracking via task-local context in the tool call
loop. Budget enforcement blocks further API calls when limits are
exceeded. Resolves merge conflicts with model-switch retry loop,
reply_target parameter, and autonomy level additions on master.
Supersedes #3758
Self-hosted Whisper-compatible STT provider that POSTs audio to a
configurable HTTP endpoint (e.g. faster-whisper over WireGuard). Audio
never leaves the platform perimeter.
Implemented via red/green TDD cycles:
Wave 1 — config schema: LocalWhisperConfig struct, local_whisper field
on TranscriptionConfig + Default impl, re-export in config/mod.rs
Wave 2 — from_config validation: url non-empty, url parseable, bearer_token
non-empty, max_audio_bytes > 0, timeout_secs > 0; returns Result<Self>
Wave 3 — manager integration: registration with ? propagation (not if let Ok
— credentials come directly from config, no env-var fallback; present
section with bad values is a hard error, not a silent skip)
Wave 4 — transcribe(): resolve_audio_format() extracted from validate_audio()
so LocalWhisperProvider can resolve MIME without the 25 MB cloud cap;
size check + format resolution before HTTP send
Wave 5 — HTTP mock tests: success response, bearer auth header, 503 error
33 tests (20 baseline + 13 new), all passing. Clippy clean.
Co-authored-by: Nim G <theredspoon@users.noreply.github.com>
Slack's Block Kit supports a native `markdown` block type that accepts
standard Markdown and handles rendering. This removes the need for a
custom Markdown-to-mrkdwn converter. Messages over 12,000 chars fall
back to plain text.
Co-authored-by: Joe Hoyle <joehoyle@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(auth): add import functionality for existing OpenAI Codex auth profiles
Introduces a new command-line option to import an existing `auth.json` file for OpenAI Codex, allowing users to bypass the login flow. The import feature reads and parses the specified JSON file, extracting authentication tokens and storing them in the user's profile. This change enhances user experience by simplifying the authentication process for existing users.
- Added `import` option to `AuthCommands` enum
- Implemented `import_openai_codex_auth_profile` function to handle the import logic
- Updated `handle_auth_command` to process the import option and validate provider compatibility
- Ensured that the import feature is exclusive to the `openai-codex` provider
* feat(auth): extract expiry from JWT in OpenAI Codex import
Enhances the `import_openai_codex_auth_profile` function by extracting the expiration date from the JWT access token. This change allows for more accurate management of token lifetimes by replacing the hardcoded expiration date with a dynamic value derived from the token itself.
- Added `extract_expiry_from_jwt` function to handle JWT expiration extraction
- Updated `TokenSet` to use the extracted expiration date instead of a static value
* fix(config): add missing WhatsApp Web policy config keys (mode, dm_policy, group_policy, self_chat_mode)
* fix(onboard): add missing WhatsApp policy fields to wizard struct literals
The new mode, dm_policy, group_policy, and self_chat_mode fields added
to WhatsAppConfig need default values in the onboard wizard's struct
initializers to avoid E0063 compilation errors.
The channel path in `src/channels/mod.rs` was passing `None` as the
`model_switch_callback` to `run_tool_call_loop()`, which meant model
switching via the `model_switch` tool was silently ignored in channel
mode.
Wire the callback in following the same pattern as the CLI path:
- Pass `Some(model_switch_callback.clone())` instead of `None`
- Wrap the tool call loop in a retry loop
- Handle `ModelSwitchRequested` errors by re-creating the provider
with the new model and retrying
Fixes#4107
Replace `tokio::task::spawn_blocking()` with plain `std:🧵:Builder`
OS threads in all PostgresMemory trait methods. The sync `postgres` crate
(v0.19.x) internally calls `Runtime::block_on()`, which panics when called
from Tokio's blocking pool threads in recent Tokio versions. Plain OS threads
have no runtime context, so the nested `block_on` succeeds.
This matches the pattern already used in `PostgresMemory::initialize_client()`,
which correctly used `std:🧵:Builder` and never exhibited this bug.
A new `run_on_os_thread` helper centralizes the pattern: spawn an OS thread,
run the closure, and bridge the result back via a `tokio::sync::oneshot` channel.
Fixes#4101
Implement start_typing/stop_typing for Slack using the Assistants API
assistant.threads.setStatus method. Tracks thread context from
assistant_thread_started events and inbound messages, then sets
"is thinking..." status during processing. Status auto-clears when
the bot sends a reply via chat.postMessage.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the Weather integration as a native Rust Tool trait
implementation, consistent with the existing first-party tool
architecture (no WASM/plugin layer required).
- Add src/tools/weather_tool.rs with full WeatherTool impl
- Fetches from wttr.in ?format=j1 (no API key, global coverage)
- Supports city names (any language/script), IATA airport codes,
GPS coordinates, postal/zip codes, domain-based geolocation
- Metric (°C, km/h, mm) and imperial (°F, mph, in) units
- Current conditions + 0-3 day forecast with hourly breakdown
- Graceful error messages for unknown/invalid locations
- Respects runtime proxy config via apply_runtime_proxy_to_builder
- 36 unit tests: schema, URL building, param validation, formatting
- Register WeatherTool unconditionally in all_tools_with_runtime
(no API key needed, no config gate — same pattern as CalculatorTool)
- Flip integrations registry Weather entry from ComingSoon to Available
Closes #<issue>
Register DeepMyst (https://deepmyst.com) as an OpenAI-compatible
provider with Bearer auth and DEEPMYST_API_KEY env var support.
Aliases: "deepmyst", "deep-myst".
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(config): prevent test suite from clobbering active_workspace.toml
Refactor persist_active_workspace_config_dir() to accept the default
config directory as an explicit parameter instead of reading HOME
internally. This eliminates a hidden dependency on process-wide
environment state that caused test-suite runs to overwrite the real
user's active_workspace.toml with a stale temp-directory path.
The temp-directory guard is now unconditional (previously gated behind
cfg(not(test))). It rejects writes only when a temp config_dir targets
a non-temp default location, so test-to-test writes within temp dirs
still succeed.
Closes#4117
* fix: remove needless borrow on default_config_dir parameter
---------
Co-authored-by: lamco-office <office@lamco.io>
The channel validation in `validate_announce_delivery` was missing `qq`,
causing API-created cron jobs with QQ delivery to be rejected.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(mcp): wire MCP tools into WebSocket chat path and gateway /api/tools
Agent::from_config() did not initialize MCP tools because it was
synchronous and MCP connection requires async. The gateway tool
registry built for /api/tools also missed MCP tools for the same
reason.
Changes:
- Make Agent::from_config() async so it can call McpRegistry::connect_all()
- Add MCP tool initialization (both eager and deferred modes) to
from_config(), following the same pattern used in loop_.rs CLI/webhook paths
- Add MCP tool initialization to the gateway's tool registry so
/api/tools reflects MCP tools
- Update all three call sites (run(), handle_socket, test) to await
Closes#4042
* fix: merge master and fix formatting
* fix: remove underscore prefix from used bindings (clippy)
* feat(hardware): add RPi GPIO, Aardvark I2C/SPI/GPIO, and hardware plugin system
Extends the hardware subsystem with three clusters of functionality,
all feature-gated (hardware / peripheral-rpi) with no impact on default builds.
Raspberry Pi native support:
- src/hardware/rpi.rs: board self-discovery (model, serial, revision),
sysfs GPIO pin read/write, and ACT LED control
- scripts/99-act-led.rules: udev rule for non-root ACT LED access
- scripts/deploy-rpi.sh, scripts/rpi-config.toml, scripts/zeroclaw.service:
one-shot deployment helper and systemd service template
Total Phase Aardvark USB adapter (I2C / SPI / GPIO):
- crates/aardvark-sys/: new workspace crate with FFI bindings loaded at
runtime via libloading; graceful stub fallback when .so is absent or
arch mismatches (Rosetta 2 detection)
- src/hardware/aardvark.rs: AardvarkTransport implementing Transport trait
- src/hardware/aardvark_tools.rs: agent tools i2c_scan, i2c_read,
i2c_write, spi_transfer, gpio_aardvark
- src/hardware/datasheet.rs: datasheet search/download for detected devices
- docs/aardvark-integration.md, examples/hardware/aardvark/: guide + examples
Hardware plugin / ToolRegistry system:
- src/hardware/tool_registry.rs: ToolRegistry for hardware module tool sets
- src/hardware/loader.rs, src/hardware/manifest.rs: manifest-driven loader
- src/hardware/subprocess.rs: subprocess execution helper for board I/O
- src/gateway/hardware_context.rs: POST /api/hardware/reload endpoint
- src/hardware/mod.rs: exports all new modules; merge_hardware_tools and
load_hardware_context_prompt helpers
Integration hooks (minimal surface):
- src/hardware/device.rs: DeviceKind::Aardvark, DeviceRuntime::Aardvark,
has_aardvark / resolve_aardvark_device on DeviceRegistry
- src/hardware/transport.rs: TransportKind::Aardvark
- src/peripherals/mod.rs: gate create_board_info_tools behind hardware feature
- src/agent/loop_.rs: TOOL_CHOICE_OVERRIDE task-local for Anthropic provider
- src/providers/anthropic.rs: read TOOL_CHOICE_OVERRIDE; add tool_choice field
- Cargo.toml: add aardvark-sys to workspace and as dependency
- firmware/zeroclaw-nucleo/: update Cargo.toml and Cargo.lock
Non-goals:
- No changes to agent orchestration, channels, providers, or security policy
- No new config keys outside existing [hardware] / [peripherals] sections
- No CI workflow changes
Risk: Low. All new paths are feature-gated; aardvark.so loads at runtime
only when present. No schema migrations or persistent state introduced.
Rollback: revert this single commit.
* fix(hardware): resolve clippy and rustfmt CI failures
- struct_excessive_bools: allow on DeviceCapabilities (7 bool fields needed)
- unnecessary_debug_formatting: use .display() instead of {:?} for paths
- stable_sort_primitive: replace .sort() with .sort_unstable() on &str slices
* fix(hardware): add missing serial/uf2/pico modules declared in mod.rs
cargo fmt was exiting with code 1 because mod.rs declared pub mod serial,
uf2, pico_flash, pico_code but those files were missing from the branch.
Also apply auto-formatting to loader.rs.
* fix(hardware): apply rustfmt 1.92.0 formatting (matches CI toolchain)
* docs(scripts): add RPi deployment and interaction guide
* push
* feat(firmware): add initial Pico firmware and serial device handling
- Introduced main.py for ZeroClaw Pico firmware with a placeholder for MicroPython implementation.
- Added binary UF2 file for Pico deployment.
- Implemented serial device enumeration and validation in the hardware module, enhancing security by restricting allowed serial paths.
- Updated related modules to integrate new serial device functionality.
---------
Co-authored-by: ehushubhamshaw <eshaw1@wpi.edu>
When the gateway security guard blocks a public bind address, the error
message now mentions the Docker use case and provides clear instructions
for connecting from Docker containers.
Closes#4086
* fix(approval): auto-approve read-only tools in non-interactive mode
Add web_search_tool, web_fetch, calculator, glob_search, content_search,
and image_info to the default auto_approve list. These are read-only tools
with no side effects that were being silently denied in channel mode
(Telegram, Slack, etc.) because the non-interactive ApprovalManager
auto-denies any tool not in auto_approve when autonomy != full.
Closes#4083
* fix: remove duplicate default_otp_challenge_max_attempts function
PR #3921 accidentally introduced a duplicate definition of
default_otp_challenge_max_attempts() in config/schema.rs, causing
compilation to fail on master (E0428: name defined multiple times).
* feat(memory): add mem0 (OpenMemory) backend integration
- Implement Mem0Memory struct with full Memory trait
- Add history() audit trail, recall_filtered() with time/metadata filters
- Add store_procedural() for conversation trace extraction
- Add ProceduralMessage type to Memory trait with default no-op
- Feature-gated behind `memory-mem0` flag
- 9 unit tests covering edge cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: apply cargo fmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(memory): add extraction_prompt config, deploy scripts, and timing instrumentation
- Add `extraction_prompt` field to `Mem0Config` for custom LLM fact
extraction prompts (e.g. Cantonese/Chinese content), with
`MEM0_EXTRACTION_PROMPT` env var fallback
- Pass `custom_instructions` in mem0 store requests so the server
uses the client-supplied prompt over its default
- Add timing instrumentation to channel message pipeline
(mem_recall_ms, elapsed_before_llm_ms, llm_call_ms, total_ms)
- Add `deploy/mem0/` with self-hosted mem0 + reranker GPU server
scripts, fully configurable via environment variables
- Update config reference docs (EN, zh-CN, VI) with `[memory.mem0]`
subsection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
# src/channels/mod.rs
* chore: remove accidentally staged worktree from index
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gemini): use default chat() for prompt-guided tool calling and add multimodal vision support
The Gemini provider's chat() override bypassed the default trait
implementation that injects tool definitions into the system prompt for
providers without native tool calling. This caused the agent loop to see
tool definitions in context but never actually invoke them, resulting in
hallucinated tool calls (e.g. claiming "Stored" without calling
memory_store).
Remove the broken chat() override so the default prompt-guided fallback
in the Provider trait handles tool injection correctly. Add an explicit
capabilities() declaration (native_tool_calling: false, vision: true).
Also add multimodal support: convert Part from a plain struct to an
untagged enum with Text and Inline variants, and add build_parts() to
extract [IMAGE:data:...] markers as Gemini inline_data parts.
Includes 14 new tests covering capabilities, Part serialization,
build_parts edge cases, and role-mapping behavior. Removes unused
ChatResponse import.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(gemini): move capabilities tests to component level and add tool conversion test
Move the 3 capabilities tests (native_tool_calling, vision,
supports_native_tools) from the inline module to
tests/component/gemini_capabilities.rs since they exercise the public
Provider trait contract through the factory. Add a new
convert_tools_returns_prompt_guided test verifying the agent loop will
receive PromptGuided payload for Gemini.
Private internals tests (Part serialization, build_parts, role mapping)
remain inline since those types are not publicly exported.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(gemini): fix cargo fmt formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(gemini): add prompt_caching field to capabilities declaration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: myclaw <myclaw@myclaws-MacBook-Air.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The `SafetySection` in `SystemPromptBuilder` always hardcoded
"Do not run destructive commands without asking" and "Do not bypass
oversight or approval mechanisms" regardless of the configured
autonomy level. This caused the gateway WebSocket path (web interface)
to instruct the LLM to simulate approval dialogs even when
`autonomy.level = "full"`.
PRs #3955/#3970/#3975 fixed the channel dispatch path
(`build_system_prompt_with_mode_and_autonomy`) but missed the
`Agent::from_config` → `SystemPromptBuilder` path used by
`gateway/ws.rs`.
Changes:
- Add `autonomy_level` field to `PromptContext`
- Rewrite `SafetySection::build()` to conditionally include/exclude
approval instructions based on autonomy level, matching the logic
already present in `build_system_prompt_with_mode_and_autonomy`
- Add `autonomy_level` field to `Agent` struct and `AgentBuilder`
- Pass `config.autonomy.level` through `Agent::from_config`
- Add tests for full/supervised autonomy safety section behavior
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>