Commit Graph

1250 Commits

Author SHA1 Message Date
Vernon Stinebaker
f0fa825e89 fix(agent): add cross-alias close tag resolution and GLM shortened body parsing
Models like GLM-4.7 emit malformed tool call formats that the existing
parser cannot handle: cross-alias close tags (e.g. <tool_call>...</invoke>),
shortened bodies (tool>value), YAML-style multi-line, and attribute-style
(tool key="value"). This adds defense-in-depth parsing for these formats
so tool calls are not silently dropped.

Changes:
- Add TOOL_CALL_CLOSE_TAGS constant for cross-alias close tag matching
- Add default_param_for_tool() for shortened body parameter inference
- Add parse_glm_shortened_body() for 3 GLM sub-formats inside tags
- Extend parse_tool_calls() with cross-alias resolution and GLM fallbacks
- Merge duplicate match arms in map_tool_name_alias() for clippy compliance
- Add 13 focused tests covering all new parsing paths
2026-02-21 20:02:36 +08:00
Chummy
7c7facc8cd fix: use Vercel AI Gateway base URL for vercel provider 2026-02-21 19:39:25 +08:00
chumyin0912@gmail.com
71b759f7c3 style: apply rustfmt for channel launchable helper 2026-02-21 19:38:19 +08:00
InuDial
92e6c6e5ae Implement ChannelConfig for ClawdTalk 2026-02-21 19:38:19 +08:00
InuDial
b658e76bfd fix format 2026-02-21 19:38:19 +08:00
InuDial
9f844173b8 Use channels(&self) and channels_except_webhook(&self)
to reduce repeat
2026-02-21 19:38:19 +08:00
InuDial
a41d9ce823 Add ConfigHandle helper trait and ConfigWrapper wrapper type 2026-02-21 19:38:19 +08:00
InuDial
052e9b8e85 add ChannelConfig implement for EmailConfig 2026-02-21 19:38:19 +08:00
InuDial
8c71e352cc Add helper function name_and_presence 2026-02-21 19:38:19 +08:00
InuDial
b6b17b43b0 feat(config): Add trait for name and desc 2026-02-21 19:38:19 +08:00
Chummy
2aacdfeed3 fix(channels): rollback failed vision turns from sender history 2026-02-21 19:26:03 +08:00
Chummy
7382966e87 fix(provider): add openrouter multimodal image_url support 2026-02-21 19:26:03 +08:00
Chummy
6cb23b67fe fix: preserve telnyx while adding sglang provider 2026-02-21 19:16:51 +08:00
reidliu41
160e0954c5 feat(provider): add first-class SGLang provider 2026-02-21 19:16:51 +08:00
chumyin0912@gmail.com
77262d1ed4 style: apply rustfmt in auth refresh handler 2026-02-21 19:06:45 +08:00
Chummy
63779d50b2 fix: restore nextcloud talk interactive onboarding path 2026-02-21 19:06:45 +08:00
Aleksandr Prilipko
38029c1e78 fix(auth): add Gemini OAuth refresh CLI support and fix ManagedOAuth bearer token
Fixes two related issues with Gemini OAuth:

1. CLI command `zeroclaw auth refresh --provider gemini` was hardcoded to
   only support OpenAI Codex, making manual token refresh impossible for
   Gemini profiles. Extended the CLI handler to support both providers.

2. GeminiProvider.build_generate_content_request() was missing bearer token
   for ManagedOAuth auth type. The method applied OAuth bearer token only
   for CLI OAuth (GeminiAuth::OAuthToken), but not for managed profiles
   (GeminiAuth::ManagedOAuth), causing 401 Unauthorized errors even after
   successful token refresh.

Changes:
- src/main.rs: AuthCommands::Refresh now handles both openai-codex and
  gemini providers via pattern match
- src/providers/gemini.rs: Extended OAuth bearer token handling to include
  GeminiAuth::ManagedOAuth case (line 837)

Verification:
- Manual test: zeroclaw auth refresh --provider gemini --profile second
- E2E test: echo "hello" | zeroclaw agent --provider gemini --model gemini-2.5-pro
- Unit tests: cargo test providers::gemini (38 passed)

Risk: Low (isolated auth flow changes, no API contract changes)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-21 18:53:11 +08:00
Aleksandr Prilipko
d56c061896 refactor(auth): add Gemini OAuth and consolidate OAuth utilities (DRY)
- Add src/auth/gemini_oauth.rs: Full Gemini/Google OAuth2 implementation
  - PKCE authorization code flow with loopback redirect
  - Device code flow for headless environments
  - Token refresh with automatic expiration handling
  - Stdin fallback for remote/headless OAuth callback capture

- Add src/auth/oauth_common.rs: Shared OAuth utilities
  - PkceState struct and generate_pkce_state()
  - url_encode/url_decode (RFC 3986)
  - parse_query_params for URL parameter parsing
  - random_base64url for cryptographic random generation

- Update src/auth/mod.rs: Add Gemini support to AuthService
  - store_gemini_tokens() for saving OAuth tokens
  - get_valid_gemini_access_token() with automatic refresh
  - get_gemini_profile() for provider initialization

- Update src/main.rs: Generic PendingOAuthLogin
  - Consolidate PendingOpenAiLogin and PendingGeminiLogin into generic struct
  - Reduce 10 functions to 4 generic functions
  - Support both openai-codex and gemini providers in auth commands

- Update src/providers/gemini.rs: ManagedOAuth authentication
  - GeminiAuth enum with ApiKey and ManagedOAuth variants
  - new_with_auth() constructor for OAuth-based authentication
  - Automatic token refresh via AuthService integration

- Update src/providers/mod.rs: Wire GeminiProvider with AuthService

Net reduction: ~290 lines of duplicated code

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-21 18:53:11 +08:00
chumyin
cd4bb8d10d fix(onboard,skills): align workspace defaults and open-skills discovery 2026-02-21 18:52:05 +08:00
chumyin
66c162c49f fix(onboard): add Nextcloud Talk interactive setup 2026-02-21 17:48:29 +08:00
Chummy
39997c7f37 Fix telegram channel prompt/test context regressions 2026-02-21 17:38:27 +08:00
Chummy
dbe01e9639 Fix gateway strict-delta and test regressions after rebase 2026-02-21 17:38:27 +08:00
Chummy
78196e027d Fix flaky regressions after main rebase 2026-02-21 17:38:27 +08:00
Chummy
1342b77e77 test(telnyx): silence unused provider binding in constructor test 2026-02-21 17:38:27 +08:00
Chummy
1682bd9b23 fix(clawdtalk): finalize rebase compatibility updates 2026-02-21 17:38:27 +08:00
Abhishek
20cd26fead feat: add Telnyx AI inference provider and ClawdTalk voice channel 2026-02-21 17:38:27 +08:00
Chummy
628654ebe5 fix: improve allowed_roots guidance for filesystem access 2026-02-21 17:33:11 +08:00
chumyin
0fd2c693a5 fix(tests): remove duplicate non_cli_excluded_tools init 2026-02-21 17:32:18 +08:00
BenedictKing
816cf74552 fix(cli): use Cargo.toml version instead of hardcoded string
Replace hardcoded `version = "0.1.0"` in clap command attribute with
`version` (no value), which makes clap read from CARGO_PKG_VERSION
automatically. This ensures `zeroclaw -V` always reflects the version
defined in Cargo.toml.
2026-02-21 17:26:38 +08:00
Allen Huang
7c4dc0982d feat(agent): add draft progress streaming for tool call execution
Port the progress streaming code from the fork's 75fdeb0 commit.
The upstream run_tool_call_loop only uses on_delta for final response
streaming, missing real-time feedback during tool execution.

Added progress sends at 4 points in the tool loop:
- "Thinking..." / "Thinking (round N)..." before each LLM call
- "Got N tool call(s) (Xs)" after LLM responds with tool calls
- Tool start: " tool_name: hint..." before each tool execution
- Tool complete: " tool_name (Xs)" or " tool_name (Xs)" after

Also added DRAFT_CLEAR_SENTINEL handling in the channel draft updater
so progress lines are cleared before the final answer streams in.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 17:22:32 +08:00
Chummy
3fb2f8bada fix(onboard): include Signal in interactive channel setup 2026-02-21 17:08:12 +08:00
Chummy
ccd0de36aa fix(tools): honor wildcard allowed_domains for browser and http_request 2026-02-21 17:08:08 +08:00
Chummy
635e6278c4 feat(onboard): support provider-only updates for existing config 2026-02-21 17:07:58 +08:00
Chummy
520bcdac4b fix(matrix): persist e2ee sdk store under zeroclaw state dir 2026-02-21 17:07:53 +08:00
Yingjie Shang
4803e0253d fix(observability): add OTLP paths to OTel endpoints
opentelemetry-otlp 0.31 does not automatically append /v1/traces
and /v1/metrics to the endpoint URL when configured via code,
causing telemetry data to be sent to / instead of correct paths.

Manually construct full endpoint URLs for both traces and metrics
exporters to ensure telemetry reaches the collector properly.
2026-02-21 17:07:31 +08:00
chumyin
ef9c12abf9 fix(telegram): preserve fenced code blocks and restore delivery instruction phrase 2026-02-21 17:00:38 +08:00
Chummy
61f98a8fd3 feat(observability): add runtime trace diagnostics and trace doctor query 2026-02-21 17:00:38 +08:00
Chummy
025f44050a fix(provider): support multimodal content in compatible vision flows 2026-02-21 16:34:45 +08:00
chumyin0912@gmail.com
f3beba1f24 test(channels): include new runtime excluded-tools field in fixture 2026-02-21 16:14:01 +08:00
chumyin0912@gmail.com
179e7949c2 fix(gateway): align dashboard API client and embed built web assets 2026-02-21 16:14:01 +08:00
Zeki Kocabıyık
79337c76e8 feat(gateway): add embedded web dashboard with React frontend
Add a complete web management panel for ZeroClaw, served directly from
the binary via rust-embed. The dashboard provides real-time monitoring,
agent chat, configuration editing, and system diagnostics — all
accessible at http://localhost:5555/ after pairing.

Backend (Rust):
- Add 15+ REST API endpoints under /api/* with bearer token auth
- Add WebSocket agent chat at /ws/chat with query param auth
- Add SSE event stream at /api/events via BroadcastObserver
- Add rust-embed static file serving at /_app/* with SPA fallback
- Extend AppState with tools_registry, cost_tracker, event_tx
- Extract doctor::diagnose() for structured diagnostic results
- Add Serialize derives to IntegrationStatus, CliCategory, DiscoveredCli

Frontend (React + Vite + Tailwind CSS):
- 10 dashboard pages: Dashboard, AgentChat, Tools, Cron, Integrations,
  Memory, Config, Cost, Logs, Doctor
- WebSocket client with auto-reconnect for agent chat
- SSE client (fetch-based, supports auth headers) for live events
- Full EN/TR internationalization (~190 translation keys)
- Dark theme with responsive layouts
- Auth flow via 6-digit pairing code, token stored in localStorage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 16:14:01 +08:00
xero7689
356d60f931 fix(config): HttpRequestConfig::default() zero-initializes numeric fields
#[derive(Default)] gives 0 for numeric types, bypassing
#[serde(default = "fn")] helpers. Onboarding wizard calls
::default() directly, writing timeout_secs=0 and
max_response_size=0 to config.toml — causing every
http_request tool call to fail immediately and silently.

- Replace derive Default with manual impl calling
  default_http_timeout_secs() / default_http_max_response_size()
- Add zero-guard in execute_request with tracing::warn!
- Add regression test for correct default values
2026-02-21 16:09:22 +08:00
Chummy
580cc52a0a
Merge pull request #1127 from ecschoye/fix/non-cli-tool-exclusion
feat(security): add non_cli_excluded_tools config for channel tool filtering
2026-02-21 15:33:16 +08:00
Will Sarg
89bff25c6d
fix(gateway): switch default port to 42617 across runtime and docs (#1179)
* fix(gateway): switch default port to 42617 across runtime and docs

* docs(changelog): record 42617 default port migration

* chore(release): bump crate version to 0.1.1

* fix(build): sync Cargo.lock with v0.1.1 manifest
2026-02-21 02:28:56 -05:00
chumyin
67942318c9 Merge origin/main into fix/non-cli-tool-exclusion 2026-02-21 15:28:53 +08:00
chumyin0912@gmail.com
81b05deb64 fix(test): include hooks field after rebase 2026-02-21 15:12:27 +08:00
chumyin
96c798df39 fix(provider): make reliable chat retries work for structured requests 2026-02-21 15:12:27 +08:00
chumyin
782bb0b483 fix: resolve multi-issue provider/channel/tool regressions 2026-02-21 15:12:27 +08:00
Chummy
2a291aec24 fix(slack): allow listening without explicit channel_id 2026-02-21 15:07:13 +08:00
Aleksandr Prilipko
c54286fec0 feat(security): add allowed_roots for file access beyond workspace
Add `autonomy.allowed_roots` config option that lets the agent
read/write files under additional directory roots outside the
workspace (e.g. shared skills directories, project repos).
Resolved (canonical) paths under any allowed root pass
`is_resolved_path_allowed` alongside the workspace itself.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:57:16 +08:00
chumyin0912@gmail.com
ae84112a22 chore(fmt): align formatting after main rebase 2026-02-21 14:54:19 +08:00
Vernon Stinebaker
158999f8bc feat(provider): add Osaurus as first-class local provider
Add Osaurus (https://github.com/dinoki-ai/osaurus) as a named provider,
following the established LM Studio / vLLM pattern with
OpenAiCompatibleProvider and Bearer auth.

Osaurus is a unified AI edge runtime for macOS (Apple Silicon) that goes
beyond traditional local inference servers:
- Local MLX inference (Llama, Qwen, Gemma, GLM, Phi, Nemotron, etc.)
- Cloud provider proxying through a single endpoint
- Multi-API: OpenAI, Anthropic, Ollama, and Open Responses simultaneously
- Built-in MCP (Model Context Protocol) support for tool/context servers

Provider wiring:
- Provider ID: "osaurus", default endpoint: http://localhost:1337/v1
- API key defaults to "osaurus" but is fully optional (keyless access)
- Credential env var: OSAURUS_API_KEY
- Registered as local provider in list_providers()

Onboard wizard:
- Added to all 10 wizard functions (auth, models, endpoints, env vars)
- Curated model list: qwen3-30b-a3b, gemma-3n-e4b, phi-4-mini-reasoning
- Tier 4 local provider with interactive endpoint/key prompts

Tests:
- factory_osaurus, factory_osaurus_uses_default_key_when_none
- factory_osaurus_custom_url, resolve_provider_credential_osaurus_env
- resilient_fallback_includes_osaurus
- Added to factory_all_providers_create_successfully array

Documentation:
- providers-reference.md: table row + Osaurus Server Notes section
- README.md: Osaurus Server Endpoint section
2026-02-21 14:54:19 +08:00
Allen Huang
7d81715b60 fix(agent): skip interactive approval in daemon/cron context
Daemon heartbeat and cron tasks called agent::run() which hardcoded
channel_name as "cli" and always created an ApprovalManager, causing
[Y]es / [N]o / [A]lways stdin prompts on the unattended daemon terminal.

Add interactive parameter to agent::run(): CLI passes true (preserving
approval flow), daemon/cron pass false (no ApprovalManager, channel
marked as "daemon").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 14:52:44 +08:00
Kevin Syong
3822778f40 fix(channels): include date and time in system prompt datetime section
- Fix 'Current Date & Time' section only emitting timezone string (e.g. 'Timezone: +08:00'), omitting actual date and time values.
- Caused AI to hallucinate incorrect dates when asked about current time.
- Emit full datetime in format 'YYYY-MM-DD HH:MM:SS (TZ)' instead.
2026-02-21 14:49:11 +08:00
Vernon Stinebaker
7ed40da280 fix(config): add missing "date" to AutonomyConfig default allowed_commands
SecurityPolicy::default() includes "date" in its allowed_commands list
(policy.rs:114), but AutonomyConfig::default() omits it (schema.rs:1809-1822).
Since SecurityPolicy::from_config() copies allowed_commands from AutonomyConfig,
the "date" command is effectively blocked at runtime despite appearing allowed
in the SecurityPolicy unit tests.

Add "date" to AutonomyConfig::default() to restore parity between the two
default lists.
2026-02-21 14:43:37 +08:00
Allen Huang
de85d53c73 fix(channel): close orphan user turn on error and timeout
When a channel message triggers an LLM error or idle timeout, the user
turn was already appended to conversation history (line 1517) but no
assistant turn was recorded. This orphan user turn caused the LLM to
treat the failed request as unfinished context on subsequent messages,
leading to unrelated replies (e.g., re-executing a timed-out GitHub
search when the user asked about WAL checkpoints).

Append a short assistant marker ("[Task failed — not continuing this
request]" or "[Task timed out — ...]") in the error and timeout
branches so the conversation history stays properly alternating and the
LLM sees the prior request as closed.

The cancel and context-overflow paths are intentionally left unchanged:
cancel is superseded by a newer message, and context-overflow prompts
the user to resend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 14:33:33 +08:00
chumyin
f74fd478b1 fix(telegram): harden html rendering and scope allowlist change 2026-02-21 14:32:02 +08:00
Shawn Zhang
7fed5cf56b feat(telegram): convert Markdown to Telegram HTML for proper formatting
- Add markdown_to_telegram_html() to TelegramChannel: converts **bold**,
  *italic*, `code`, ```blocks```, [text](url) links, and ## headers
  to Telegram HTML tags (<b>, <i>, <code>, <pre>, <a href>)
- Switch send_text_chunks() and finalize_draft() from parse_mode=Markdown
  to parse_mode=HTML — more reliable and supports richer formatting
- Update channel_delivery_instructions() for Telegram: guide model to use
  bold, emoji, and concise style (mirrors OpenClaw SOUL.md approach)
- Add wildcard support to http_request allowlist: allowed_domains=["*"]
  now bypasses domain filtering entirely
- Expand system prompt URL fetching guidance: jina.ai reader-mode proxy
  as fallback for paywalled/403 content
2026-02-21 14:32:02 +08:00
Alex Gorevski
959fbee782
Merge pull request #1187 from zeroclaw-labs/fix/update-tests-for-usage-and-hooks-fields
fix(tests): update test structs for new usage and hooks fields
2026-02-20 22:30:54 -08:00
agorevski
00a7510e91 fix(tests): update test structs for new usage and hooks fields
Add missing `usage: None` to ChatResponse literals in benchmarks,
agent loop tests, and file_read tests. Add missing `hooks: None` to
channel context structs in channel tests. Remove obsolete
`.map(|(m, _)| m)` calls in telegram tests to match updated
parse_update_message return type.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-20 22:30:23 -08:00
Alex Gorevski
4a1bacf960
Merge pull request #1186 from zeroclaw-labs/test/audit-07-coverage-remediation
test: add unit tests for audit-07 coverage gaps
2026-02-20 22:28:34 -08:00
agorevski
06e0632a09 test: add unit tests for audit-07 coverage gaps
Add 81 new tests addressing audit-07 findings across 4 areas:

Provider factory resolution (42 tests):
- Cover all 25+ untested providers and aliases in factory
- Test openrouter, gemini, bedrock, copilot, china region, local,
  cloud AI, and custom endpoint providers

Config schema boundaries (26 tests):
- Invalid value fail-fast (wrong types, overflow port)
- Gateway, security, autonomy config defaults and roundtrips
- Backward compatibility (unknown keys, partial sections)
- Nested optional section defaults

Gateway rate limiter boundaries (8 tests):
- Window expiry and re-allow after cooldown
- Independent key tracking
- Exact max_keys boundary eviction
- Pair vs webhook independence
- Concurrent access thread safety
- Rapid burst then cooldown pattern

Tool error paths (5 tests):
- Null byte in path rejection for file_read and file_edit
- Shell nonexistent command, stderr capture, action budget exhaustion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-20 22:23:55 -08:00
Alex Gorevski
bd31f77e8f
Merge pull request #1182 from zeroclaw-labs/fix/cleartext-logging-alerts
fix(security): remove sensitive fields from Debug impls
2026-02-20 22:14:04 -08:00
agorevski
52f72692ba fix(security): remove sensitive fields from Debug impls
Resolve 18 CodeQL cleartext-logging/cleartext-transmission alerts by
removing sensitive data from Debug output entirely rather than redacting.

Changes:
- memory/mod.rs: omit api_key from ResolvedEmbeddingConfig Debug
- tools/browser.rs: omit api_key from ComputerUseConfig Debug
- providers/mod.rs: omit access_token/refresh_token from
  QwenOauthCredentials Debug, credential from QwenOauthProviderContext
- memory/traits.rs: custom Debug for MemoryEntry omitting session_id
- auth/profiles.rs: custom Debug for AuthProfile omitting token,
  token_set, account_id
- channels/matrix.rs: add Debug impl for MatrixChannel omitting
  access_token
- channels/qq.rs: sanitize user_id before URL interpolation
- channels/whatsapp_storage.rs: document false-positive analysis

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-20 22:06:21 -08:00
chumyin0912@gmail.com
29aa400160 fix(hooks): harden dispatch and outbound message guardrails 2026-02-21 13:34:09 +08:00
xj
69f4b95f8e fix(hooks): add JsonSchema derive to HooksConfig and BuiltinHooksConfig
Upstream main now derives schemars::JsonSchema on all config structs.
Our HooksConfig and BuiltinHooksConfig were missing it, causing CI
Build (Smoke) failure when the merge commit was compiled.
2026-02-21 13:34:09 +08:00
xj
eb60d0fb81 fix(hooks): address code review findings
- C1: Use real tool success boolean instead of starts_with("Error")
  heuristic in after_tool_call hook
- C2: Wire HookRunner from config into ChannelRuntimeContext so hooks
  actually fire in daemon/channel mode (was hardcoded to None)
- I1: Suppress unused_imports warning on HookHandler public API re-export
- I3: Remove session_memory and boot_script config fields that had no
  backing implementation (YAGNI); keep only command_logger which is wired

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:34:09 +08:00
xj
6d4dca9a07 chore(hooks): fix formatting and clippy warnings
Apply cargo fmt and replace sort_by with sort_by_key per clippy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:34:09 +08:00
xj
d95b2af649 feat(hooks): add CommandLoggerHook builtin
Add a built-in hook that logs tool calls for auditing, recording
tool name, duration, and success status with timestamps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:34:09 +08:00
xj
0638266b63 feat(hooks): integrate HookRunner into agent loop, channels, and gateway
Thread Option<&HookRunner> into run_tool_call_loop with hook fire points
for LLM input, before/after tool calls. Add hooks field to
ChannelRuntimeContext for message received/sending interception.
Build HookRunner from config in run_gateway and fire gateway_start.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:34:09 +08:00
xj
fd4ada8694 feat(hooks): add HooksConfig to config schema
Add HooksConfig and BuiltinHooksConfig structs to src/config/schema.rs
with serde defaults for backward compatibility. Wire hooks field into
Config struct and all explicit Config constructors (Default impl,
wizard, test helpers).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:34:09 +08:00
xj
ff6027fce7 feat(hooks): add HookHandler trait, HookResult, and HookRunner dispatcher
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:34:09 +08:00
EC2 Default User
9ff86c372f fix(tools): reject empty old_string in file_edit 2026-02-21 13:32:59 +08:00
reidliu41
34ec788968 feat(tools): add file_edit tool for precise in-place text replacement 2026-02-21 13:32:59 +08:00
chumyin0912@gmail.com
6cee99c8ad fix(channel): keep nostr wiring compatible with channel collectors 2026-02-21 13:16:20 +08:00
Kieran
7d9b33cbbb fix: adjust wizard padding 2026-02-21 13:16:20 +08:00
Kieran
11edda3ed6 feat(channel): add Nostr channel with NIP-04 and NIP-17 support
Adds a full NostrChannel implementation enabling ZeroClaw to send and
receive private messages over the Nostr protocol via user-configured
relay WebSocket connections.

Key design decisions:
- Implements the Channel trait in src/channels/nostr.rs; registered via
  the existing factory in channels/mod.rs
- Supports both NIP-04 (legacy encrypted DMs) and NIP-17 (gift-wrapped
  private messages); replies automatically mirror the sender's protocol
- Deny-by-default allowlist (allowed_pubkeys = [] denies all)
- Private key encrypted at rest via SecretStore (ChaCha20-Poly1305 AEAD)
  when secrets.encrypt = true (the default)
- nostr-sdk added with default-features = false and only nip04 + nip59
  features to minimise binary size impact
- health_check() returns true if any relay reports is_connected()

Wiring:
- New NostrConfig struct and optional field in ChannelsConfig
- has_supervised_channels() in daemon updated to include nostr
- Onboarding wizard extended with a dedicated Nostr step (key
  validation, relay selection, allowlist configuration)

Docs compliance:
- channels-reference.md: channel matrix, delivery modes table, allowlist
  field names, numbered config section (4.12), log keyword table (7.2),
  and log filter command all updated
- config-reference.md: [channels_config.nostr] sub-section with key
  table and security notes added
- network-deployment.md and README.md updated
- .github/pull_request_template.md: resolved stale conflict markers from
  chore/labeler-spacing-trusted-tier
2026-02-21 13:16:20 +08:00
Aleksandr Prilipko
0a2609d538 fix(tools): file_read binary file support — PDF extraction + lossy fallback
Add cascading fallback to file_read tool: UTF-8 → PDF text extraction
(via pdf-extract) → lossy UTF-8 conversion. Binary files no longer
produce errors; PDFs return extracted text, other binaries get lossy
output with U+FFFD replacement characters.

Changes:
- Cargo.toml: add rag-pdf to default features
- file_read.rs: cascading fallback logic + try_extract_pdf_text helper
- file_read.rs: update tool description
- test_document.pdf: replace empty fixture with PDF containing "Hello PDF"
- Tests: remove file_read_rejects_binary_pdf, add unit + e2e tests for
  PDF extraction and lossy binary reads (including live OpenAI Codex e2e)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:03:13 +08:00
Jayson Reis
aff9ef91cf fix(onboard): make model refresh and doctor model checks async-safe 2026-02-21 13:03:10 +08:00
xero7689
9aaa305f66 feat(channel): inline text attachments from Discord messages
Extract d.attachments from MESSAGE_CREATE payloads and fetch text/*
content from Discord CDN URLs, appending it to ChannelMessage.content
before the agent loop receives the message.

- Add process_attachments() async helper: fetches text/* attachments,
  skips all other MIME types with debug log, warns on fetch errors
- Reuse existing build_runtime_proxy_client HTTP client (no new deps)
- Format inlined content as [filename]\n<content>, joined by ---
- Add unit tests: empty list, unsupported MIME type skip

Closes #1169
2026-02-21 13:00:28 +08:00
Edvard
1e8961ca17 fix(channel): strip tool_call XML tags from Discord outgoing messages
Move strip_tool_call_tags to channels/mod.rs as shared utility and
call it in Discord's send method. Telegram already stripped these tags
but Discord sent raw LLM output including <tool_call>...</tool_call>
XML, which leaked internal protocol to end users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:57:40 +08:00
Aleksandr Prilipko
930158b02f fix(channel): remove reply context truncation to preserve full context
Remove the 200-char truncation of quoted reply text in Telegram
channel. The agent benefits from seeing the complete original message
when replying to a conversation thread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:57:32 +08:00
Aleksandr Prilipko
01d6002d41 fix(channel): use [IMAGE:] marker for photo attachments, add e2e vision tests
Photos now use [IMAGE:/path] format instead of [Photo] /path, so the
existing multimodal pipeline validates vision capability and rejects
unsupported providers (Groq, OpenAI-compatible) with a user-facing
error before calling the LLM.

Tests added (all offline, no API keys required):
- attachment_photo_content_uses_image_marker
- attachment_document_content_uses_document_label
- photo_image_marker_detected_by_multimodal
- photo_image_marker_with_caption
- e2e_attachment_saves_file_and_formats_content
- groq_provider_rejects_photo_with_vision_error
- e2e_photo_attachment_rejected_by_non_vision_provider

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:57:32 +08:00
Aleksandr Prilipko
41619b8469 test(channel): add live e2e test for voice transcription + reply cache
Add an ignored integration test that exercises the full voice
transcription pipeline: load a pre-recorded MP3 fixture, transcribe via
Groq Whisper API, verify the result contains "hello", cache it in
TelegramChannel.voice_transcriptions, and assert extract_reply_context
returns "[Voice] <transcription>" instead of the fallback placeholder.

The test gracefully skips when GROQ_API_KEY is not set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:57:32 +08:00
Aleksandr Prilipko
3b29d0c90e feat(channel): surface Telegram reply context to the agent
When a user swipes to reply to a specific message, the agent now
receives the quoted original message as a blockquote prefix, e.g.:

  > @alice:
  > original message text

  translate this

This makes reply-to-voice ("translate this" → previous transcription)
and other reply-aware interactions work correctly.

Changes:
- Extract `extract_sender_info` helper (DRY: was duplicated in
  parse_update_message and try_parse_voice_message)
- Add `extract_reply_context` helper: parses reply_to_message,
  handles text/voice/photo/document/video/sticker, truncates >200
  chars, falls back from username to first_name
- Wire reply context into both parse_update_message and
  try_parse_voice_message
- Add 8 unit tests covering all branches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:57:32 +08:00
Aleksandr Prilipko
0646abfed9 feat(providers): Gemini OAuth credential rotation and token refresh 2026-02-21 12:56:18 +08:00
Chummy
5571852b7b fix(channel): remove duplicate telegram api_base initialization 2026-02-21 12:48:47 +08:00
Aleksandr Prilipko
21ccb9e13a feat(channel): add voice message transcription via Whisper API
Add voice-to-text transcription for Telegram voice/audio messages using
any Whisper-compatible API (Groq by default, configurable endpoint).

- New TranscriptionConfig in config schema (enabled, api_url, model,
  language, max_duration_secs) with serde defaults
- New transcription module: MIME detection, .oga→.ogg normalization,
  size/format validation, Whisper API client
- Telegram: voice download pipeline (getFile → CDN download → transcribe),
  listen loop fallback for voice messages, [Voice] prefix on transcribed text
- Proxy support via "transcription.groq" service key
- 18 new tests (MIME mapping, normalization, config roundtrip, voice
  metadata parsing, builder wiring, format/size rejection)

Disabled by default (enabled: false). Fail-fast validation order:
size → format → API key.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:48:47 +08:00
Aleksandr Prilipko
24460ec4f5 feat(providers): support provider:profile syntax in fallback_providers
Parse "provider:profile" entries (e.g. "openai-codex:second") in the
fallback chain so multiple OAuth profiles of the same provider can be
rotated on 429.  The profile override is propagated via
auth_profile_override in ProviderRuntimeOptions.

Entries prefixed with "custom:" or "anthropic-custom:" are left
untouched since the colon is part of the URL scheme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:47:22 +08:00
chumyin0912@gmail.com
192729f4cc test(provider): fix ChatResponse usage in reliable mocks 2026-02-21 12:46:22 +08:00
EC2 Default User
0b02720a48 fix(channels): handle reaction completion states after rebase 2026-02-21 12:46:22 +08:00
NB😈
11153b6a80 feat(channels): add reaction support to Channel trait and Discord implementation
Add `add_reaction` and `remove_reaction` methods to the Channel trait
with default no-op implementations, and implement them for Discord using
the REST API (PUT/DELETE reactions/@me endpoints).

Wire reactions into the channel message processing loop:
- React with 👀 when a message is received (acknowledgement)
- Swap to  on success or ⚠️ on error after processing completes

Includes emoji URL-encoding helper, unit tests for encoding, trait
defaults, and an integration test verifying the full reaction flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 12:46:22 +08:00
Aleksandr Prilipko
fff6ba6c13 fix(channel): merge consecutive user turns instead of dropping them
When a user sends multiple messages before the assistant replies,
normalize_cached_channel_turns now concatenates them with \n\n
instead of silently dropping later turns. Memory-context enrichment
is also fixed to replace only the current message suffix, preserving
earlier merged segments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:44:08 +08:00
Aleksandr Prilipko
2393b9a551 fix: resolve clippy warnings and rustfmt across codebase
Address clippy lints (redundant continue, as-cast, match arms, elided
lifetimes, format vs write!) and reformat long cfg attributes and assert
macros to pass `cargo fmt --check` and `cargo clippy -D warnings`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:39:34 +08:00
Kyle Lampa
3f88f14eb9 fix(agent): handle double closing braces in Perl-style tool calls
The format ends with }} before /TOOL_CALL, not a single }.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:36:28 +08:00
Kyle Lampa
0b31bdee61 fix(agent): map tool name aliases for MiniMax variations
Add comprehensive tool name alias mapping:
- fileread -> file_read
- filewrite -> file_write
- memoryrecall -> memory_recall
- bash/sh/cmd -> shell
- etc.

Apply to all new parsers (XML attribute, Perl, FunctionCall).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:36:28 +08:00
Kyle Lampa
4d08ae275d fix(agent): improve Perl-style tool call regex
The previous regex couldn't handle nested braces in:
{tool => "shell", args => { --command "ls" }}

Now uses multi-stage parsing: find TOOL_CALL block, extract
tool name, then extract args block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:36:28 +08:00
Kyle Lampa
ba1b231099 fix(agent): parse FunctionCall tool call format
Add parser for <FunctionCall> style that MiniMax also uses:
<FunctionCall>
file_read
<code>path>/Users/.../file.md</code>
</FunctionCall>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:36:28 +08:00
Kyle Lampa
8541041b23 fix(agent): parse MiniMax tool call formats for execution
Add parsers for two additional tool call formats that MiniMax LLM uses:
- XML attribute style: <minimax:toolcall><invoke name="shell"><parameter name="command">ls</parameter></invoke></minimax:toolcall>
- Perl/hash-ref style: {tool => "shell", args => { --command "ls" }}

Previously these were sent as plain text to Telegram channel instead of
being executed as tool calls.

Also fixes build warnings:
- Add #[allow(unused_imports)] to cost/mod.rs and onboard/mod.rs re-exports
- Change channels::handle_command visibility to pub(crate)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:36:28 +08:00
s04
3a4215aa78 fix(telegram): fall back to text link when media-by-URL fails
When the Telegram Bot API rejects a sendDocument/sendPhoto/etc by URL
(e.g. "wrong type of the web page content" or "failed to get HTTP URL
content"), the entire reply was lost because the error propagated
immediately via `?` with no fallback.

Now when any send-media-by-URL call fails, the channel logs a warning
and falls back to sending the URL as a plain text link. This ensures
the user always receives the agent's response, even when Telegram
can't fetch the linked content.

Also makes `api_base` configurable via `with_api_base()` for local
Bot API server support and testability.
2026-02-21 12:34:33 +08:00
Chummy
0c2d4b18a7 style(onboard): apply rustfmt after rebase 2026-02-21 12:33:00 +08:00
reidliu41
da453581c9 feat(provider): add first-class vLLM provider flow
Add native vLLM provider support to ZeroClaw
- First-class `vllm` provider with local endpoint defaults (`http://localhost:8000/v1`)
- Optional `VLLM_API_KEY` support
- Onboarding wizard integration (tier menu, endpoint prompt, model discovery, keyless local usage)
- Updated provider/docs references and command documentation
2026-02-21 12:33:00 +08:00
Chummy
d128c70c32 fix(provider): set usage on ollama chat fallback 2026-02-21 12:29:02 +08:00
s04
dce3c36053 fix: add usage field to ChatResponse constructors added upstream
Tests and mock providers added upstream after the branch point now
need the usage field that was introduced in the first commit.
2026-02-21 12:29:02 +08:00
s04
0fb6a91595 feat(observability): wire token usage through observer events
Add input_tokens and output_tokens fields to ObserverEvent::LlmResponse
so per-call token data flows through all observer backends. Prometheus
gains three new counters (llm_requests_total, tokens_input_total,
tokens_output_total) for granular token tracking by provider/model.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:29:02 +08:00
s04
dba9f82939 feat(provider): parse token usage from Anthropic, Gemini, Ollama, Bedrock
Parse provider-specific usage fields from API responses:
- Anthropic: input_tokens/output_tokens from usage object
- Gemini: promptTokenCount/candidatesTokenCount from usageMetadata
- Ollama: prompt_eval_count/eval_count from response root
- Bedrock: inputTokens/outputTokens from camelCase usage object

Gemini required refactoring send_generate_content to return
(String, Option<TokenUsage>) tuple, plus a chat() override to
thread usage into ChatResponse.
2026-02-21 12:29:02 +08:00
s04
cd7026a53c feat(provider): parse token usage from OpenAI-family responses
Add UsageInfo deserialization structs and wire usage data from API
responses through to ChatResponse for OpenRouter, OpenAI, Compatible,
and Copilot providers. All four share the OpenAI response format with
prompt_tokens/completion_tokens fields.
2026-02-21 12:29:02 +08:00
s04
6f1cf8bc81 feat(provider): add usage field to ChatResponse
Add a lightweight TokenUsage struct to providers::traits with
input_tokens and output_tokens fields. Add usage: Option<TokenUsage>
to ChatResponse and update all construction sites across providers
and agent modules with usage: None.

This is the first step toward capturing token usage data from LLM
API responses. Currently all sites set usage: None — subsequent
commits will parse actual usage from each provider's response format.
2026-02-21 12:29:02 +08:00
Chummy
08401845fa
Merge pull request #1065 from AllenHyang/fix/gemini-oauth-pr
fix(gemini): fix OAuth provider for cloudcode-pa internal API
2026-02-21 12:21:48 +08:00
Chummy
eeda57f5b1 chore(fmt): apply rustfmt after main rebase 2026-02-21 12:09:06 +08:00
Aleksandr Prilipko
2af6a25ac2 fix: resolve all compilation, test, and fmt errors on main
- Remove duplicate `chat` method in reliable.rs (E0201)
- Fix `futures` → `futures_util` imports in agent.rs and loop_.rs (E0433)
- Gate PostgresMemory behind `memory-postgres` feature in cli.rs (E0433)
- Fix regex backreference in XML tool parser (unsupported by regex crate)
- Add missing `skills_prompt_mode` argument in test
- Apply rustfmt to files with formatting issues on main
2026-02-21 12:09:06 +08:00
Chummy
253275d578 Merge origin/main into fix/gemini-oauth-pr and resolve gemini OAuth conflicts 2026-02-21 12:08:12 +08:00
Allen Huang
df7d458164 fix(tests): increase lucid memory test timeouts to prevent flakiness
Lucid memory tests used 500ms/400ms recall/store timeouts for shell
script execution. Under parallel test load, bash process spawning
often exceeded these limits, causing timeout kills before the script
could write to marker files — leading to consistent test failures
when run alongside other tests.

Widen test timeouts to 5s. The scripts themselves complete in <50ms;
the margin absorbs OS scheduling jitter under concurrent test load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 12:01:38 +08:00
xiaotianxt
bf08f1a1bb fix(cli): restore shared command enum wiring 2026-02-21 11:57:29 +08:00
xiaotianxt
b1694b63a9 refactor(cli): reuse shared command enums from lib 2026-02-21 11:57:29 +08:00
xiaotianxt
741d0cf32c fix(channel): keep configured channel shape for Nextcloud 2026-02-21 11:53:41 +08:00
xiaotianxt
9b003ea5be fix(channel): unify configured channel construction for doctor/start parity 2026-02-21 11:53:41 +08:00
T. Budiman
664625f5f6 fix(gateway): enable tool execution for WhatsApp, Linq, Nextcloud Talk channels
Gateway channels (WhatsApp, Linq, Nextcloud Talk) were returning raw
<tool> tags without executing tools or showing results. The CLI
correctly executed tools and returned results.

Root cause: gateway handlers used run_gateway_chat_with_multimodal which
explicitly disabled tools for simple chat-only mode.

Fix: Create run_gateway_chat_with_tools() which uses process_message()
for full tool support, while keeping run_gateway_chat_simple() for
the webhook endpoint to maintain backward compatibility with tests.

Changes:
- Add run_gateway_chat_with_tools() for channel handlers (uses process_message)
- Keep run_gateway_chat_simple() for webhook endpoint (uses state.provider)
- Remove unused provider_label variables from channel handlers
- Remove unused imports (ChatMessage, ProviderCapabilityError)
- Fix pre-existing test compilation issue (missing SkillsPromptInjectionMode)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 11:49:53 +08:00
Shawn Zhang
853fc319bd fix: add EC2 IMDSv2 credential fallback for Bedrock provider
Previously, BedrockProvider only read credentials from environment
variables (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY). When running
on EC2 with an IAM instance role, the env vars are not set, causing
all Bedrock calls to fail with 'credentials not set'.

Changes:
- Add AwsCredentials::from_imds(): fetches temporary credentials from
  EC2 IMDSv2 (PUT token → get role name → get credentials → get region)
- Add AwsCredentials::resolve(): tries env vars first, falls back to IMDS
- Add BedrockProvider::resolve_credentials(): async method called per
  request, so expired instance role tokens are automatically refreshed
- chat() and chat_with_system() now call resolve_credentials() instead
  of require_credentials(), enabling seamless EC2 instance role auth
2026-02-21 11:49:15 +08:00
mackenzieclark
43f7bfa418 fix(gemini): handle thinking model response parts correctly
Gemini thinking models (e.g. gemini-3-pro-preview) return response parts
with `thought: true` for internal reasoning and `thoughtSignature` for
opaque signatures. The previous extraction logic blindly took the first
part, which was the thinking part, returning reasoning text instead of the
actual answer.

- Add `thought` field to `ResponsePart` to detect reasoning parts
- Add `effective_text()` on `CandidateContent` to skip thinking/signature
  parts and extract only the real answer (falls back to thinking text if
  no non-thinking content is available)
- Make `Candidate.content` optional to guard against candidates with no
  content (e.g. safety-filtered responses)
- Add 7 focused tests covering thinking, non-thinking, fallback, empty,
  multi-part, signature-only, and internal API responses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 11:48:45 +08:00
Alex Gorevski
357a938174 fix: resolve three compilation errors breaking release-fast build
- Remove duplicate chat method in ReliableProvider impl (E0201)
  The second chat fn (lines 662-769) was an exact duplicate of the
  first (lines 540-647) in the same impl block.

- Gate PostgresMemory usage in memory CLI behind memory-postgres feature (E0433)
  super::PostgresMemory is only exported when the feature is enabled;
  the Postgres match arm now compiles to an explicit bail when the
  feature is off.

- Replace utures::future::join_all with utures_util::future::join_all (E0433)
  The crate depends on utures-util, not utures. Fixed in both
  agent.rs and loop_.rs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-20 11:38:00 -08:00
Edvard
1d3ae43b5b fix(channels): also filter tool descriptions from system prompt
The previous commit filtered tool_specs (native API tools) but the
system prompt still contained text descriptions like "shell: Execute
terminal commands" which caused the model to generate XML-based
<function_calls> tool invocations in its text response.

Filter tool_descs using the same non_cli_excluded_tools config so
excluded tools are not mentioned anywhere the LLM can see them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:03:14 -05:00
Shawn Zhang
7bf825eb34 feat: add vision/multimodal support for Telegram + Bedrock
- channels/telegram.rs: support photo messages in parse_update_message;
  add resolve_photo_data_uri() to fetch, download and resize images to
  512px via Telegram getFile API before base64 encoding
- providers/bedrock.rs: add parse_user_content_blocks() to extract
  [IMAGE:data:...] markers and build proper Bedrock image content blocks;
  apply to both chat() and chat_with_system() paths; set vision: true
  in provider capabilities
- Cargo.toml: add image crate v0.25 (jpeg/png) for server-side resize
2026-02-21 02:36:59 +08:00
Le Song
645515145e test(cron): add tests for job_type SQL reading and validation 2026-02-21 02:35:54 +08:00
Le Song
42cab231e6 test(crom): add tests for JobType::try_from to handle case-insensitive and invalid values 2026-02-21 02:35:54 +08:00
Le Song
b45afa15fd fix(cron): map job_type via FromSql and standardize persistence 2026-02-21 02:35:54 +08:00
Le Song
7faff05dae fix(cron): align JobType conversions: add JobType <-> &str conversion via From/TryFrom 2026-02-21 02:35:54 +08:00
César Pérez
9cfbf44f58 fix(providers): update synthetic provider base URL
Update the hardcoded synthetic provider base URL from https://api.synthetic.com
to https://api.synthetic.new/openai/v1 to match the actual API endpoint.

The user verified locally that the old URL doesn't work and confirmed the fix
works by using the custom provider syntax as a workaround:
  default_provider = "custom:https://api.synthetic.new/openai/v1"

This change makes the synthetic provider work out of the box without requiring
users to use the custom provider workaround.
2026-02-21 02:32:24 +08:00
xiaotianxt
719e369153 fix(provider): restore Gemini OAuth envelope after rebase 2026-02-21 02:28:22 +08:00
xiaotianxt
a0664b4681 fix(provider): forward oauth project id to Gemini internal API 2026-02-21 02:28:22 +08:00
xiaotianxt
30097d37e8 fix(provider): use wrapped Code Assist payload for Gemini OAuth 2026-02-21 02:28:22 +08:00
xiaotianxt
064f8d00d5 fix(provider): use snake_case payload for Gemini CLI internal API (#1007) 2026-02-21 02:28:22 +08:00
EC2 Default User
d32dd7f7c7 fix(tool): pass security policy to cron tools in registry 2026-02-21 02:27:54 +08:00
reidliu41
955ae92f9f feat(tool): add glob_search for workspace file pattern search
- Problem: Agent relies on `shell` + `find` for file search — fragile syntax, raw output, broad permissions
  - Why it matters: Structured tool reduces failed tool calls and tightens security boundary
  - What changed: New `glob_search` tool in `default_tools` and `all_tools`; searches workspace by glob pattern with
  full security checks
  - What did **not** change (scope boundary): No changes to security policy, config schema, providers, or agent loop
2026-02-21 02:27:54 +08:00
Edvard
0f8fc7257a fix(telegram): prevent message overflow from continuation markers and strip function_calls tags
Two bugs caused Telegram replies to fail with "message is too long":

1. split_message_for_telegram splits at exactly 4096 chars, but send_text_chunks
   then appends continuation markers ("(continued)\n\n" / "\n\n(continues...)"),
   pushing the actual sent text over Telegram's 4096 limit. Fixed by reserving
   30 chars of headroom in the split limit.

2. strip_tool_call_tags did not handle <function_calls> / <function_call> wrapper
   tags. When the LLM returns raw XML function calls, the unstripped angle brackets
   break Telegram's Markdown parser, and the full XML payload exceeds the length
   limit on the plain-text fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 02:26:17 +08:00
Jayson Reis
0523e655f8 refactor(auth): make auth profile store async 2026-02-21 02:25:23 +08:00
Chummy
78358c53b3 fix(onboard): require explicit overwrite confirmation for existing config 2026-02-21 02:25:21 +08:00
Chummy
96aa1eae0b feat: reduce release binary size with opt-in heavy features 2026-02-21 02:23:59 +08:00
reidliu41
7ed307b0ac fix(provider): add chat() override for Ollama and ReliableProvider to restore tool calling 2026-02-21 02:22:44 +08:00
reidliu41
3db0fa34f2 feat(tool): add line numbers and partial reading to file_read 2026-02-21 02:22:41 +08:00
xiaotianxt
8d1e87dea5 perf(channel): offload provider initialization from async workers 2026-02-21 02:22:31 +08:00
reidliu41
3eb084454e feat(memory): add zeroclaw memory CLI management commands
ZeroClaw's memory system powers context injection, auto-save, and long-term agent identity — but until now users had
**zero visibility** into what's stored. No way to list, inspect, audit, or clean up memory outside the agent loop.

`zeroclaw memory` closes this gap with four subcommands:

- **`list`** — browse all entries with `--category`/`--session` filters and `--limit`/`--offset` pagination
- **`get`** — inspect a single entry by key (supports prefix match — no need to copy full UUID)
- **`stats`** — backend health, total count, per-category breakdown at a glance
- **`clear`** — batch delete by `--category`, single delete by `--key`, with confirmation prompt (`--yes` to skip)

| Before | After |
|--------|-------|
| Memory is a black box | `memory stats` shows health + distribution |
| Can't see what auto-save stored | `memory list --category conversation` |
| Can't inspect a specific entry | `memory get <key-or-prefix>` |
| Can't clean stale data without `/clear` in agent | `memory clear --category daily --yes` |
| Must enter agent loop to manage memory | Direct CLI, no LLM invocation needed |

| File | Change |
|------|--------|
| `src/memory/cli.rs` | **New** — CLI handler with list/get/stats/clear + unit tests |
| `src/memory/mod.rs` | Add `pub mod cli` |
| `src/lib.rs` | Add `MemoryCommands` public enum |
| `src/main.rs` | Add private `MemoryCommands`, `Commands::Memory` variant, match arm |

- **Lightweight backend creation**: CLI uses `create_memory_for_migration` (no embedding provider) since
list/get/stats/clear don't need vector search. Postgres handled separately.
- **Prefix matching**: Both `get` and `clear --key` fall back to prefix search when exact match fails — essential
since keys are UUIDs.
- **Confirmation by default**: All destructive operations require `dialoguer::Confirm`; `--yes` for
scripts/automation.
- **Record-style list output**: Full key displayed (no truncation), one entry per block — keys are too long for
  tabular layout.
2026-02-21 02:22:16 +08:00
Allen Huang
71e6c25b6b feat(lark): add ack emoji reaction on receive 2026-02-21 02:22:01 +08:00
Chummy
723426c68e fix(tools): adapt pdf_read registration to Arc tool registry 2026-02-21 01:58:25 +08:00
Chummy
ac9e5cb03c fix(tools): surface pdf_read empty-text warning in output 2026-02-21 01:58:25 +08:00
reidliu41
a5521ab7bc feat(tools): add pdf_read tool for workspace PDF text extraction 2026-02-21 01:58:25 +08:00
Chummy
de2911083c fix: complete tool-call compatibility wiring in native conversion 2026-02-21 01:27:04 +08:00
Chummy
ad5f878e49 fix: tighten Chinese provider tool-call parsing and remove PR noise 2026-02-21 01:27:04 +08:00
Vernon Stinebaker
4fd41d5f2c fix(provider): add chat() override to ReliableProvider for native tool calling
ReliableProvider was missing a chat() override, causing it to fall through
to the default Provider::chat() trait implementation. The default
implementation delegates to chat_with_history() which returns a plain
String and wraps it in ChatResponse with tool_calls: Vec::new() — so
native tool calling was completely broken through the retry/failover
wrapper even though the underlying provider properly supports it.

Changes:
- Add chat() with full retry/backoff/failover logic matching existing
  chat_with_system(), chat_with_history(), and chat_with_tools() overrides
- Include context_window_exceeded early-exit matching other method patterns
- Add 7 focused tests: delegation with tool calls, retry recovery,
  supports_native_tools propagation, aggregated error reporting,
  model failover, non-retryable error skip, and system prompt zero-XML
  verification
2026-02-21 01:20:52 +08:00
Edvard
e5e7e1a409 feat(security): add non_cli_excluded_tools to filter tools on channel messages
On non-CLI channels (Telegram, Discord, etc.), tools like shell and
file_write cannot receive interactive approval and are auto-denied,
causing the LLM to see confusing error responses and fabricate answers.

Add a new config option `non_cli_excluded_tools` under `[autonomy]`
that removes specified tools from the tool specs sent to the LLM on
non-CLI channels. This prevents the model from attempting tool calls
that would fail, forcing it to use data already in the system prompt.

The change filters tool_specs in run_tool_call_loop when the
excluded_tools parameter is non-empty. CLI channels are unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:08:02 -05:00
reidliu41
04640a963e feat(provider): add Doubao (Volcengine Ark) provider support 2026-02-21 00:52:43 +08:00
Chummy
63d002f22a fix(ollama): stabilize cloud routing and onboarding model selection 2026-02-21 00:22:31 +08:00
Chummy
e081010983 feat(skills): add configurable compact skills prompt injection 2026-02-21 00:00:51 +08:00
Chummy
5f6a8cdfc2 fix(channels): suppress leaked tool json in channel replies 2026-02-20 23:31:57 +08:00
Chummy
6c32976075 fix(service): tighten OpenRC docs and stabilize root detection test 2026-02-20 23:30:55 +08:00
Jakub Buzuk
35c37cb217 fix: accept config dir cli arg 2026-02-20 23:30:55 +08:00
Jakub Buzuk
a1ed5e7e75 fix: permission issues during service creation 2026-02-20 23:30:55 +08:00
Jakub Buzuk
71acd1245c fix(service): harden OpenRC restart fallback and uninstall resilience
- Linux managed daemon now falls back to systemd when OpenRC restart probe fails, instead of returning early with no action.

- OpenRC uninstall no longer fails hard if rc-update del fails; it warns and continues to remove the init script.
2026-02-20 23:30:55 +08:00
Jakub Buzuk
076e9be9e5 fix(service): use explicit --config-dir args in OpenRC script
Switch OpenRC service generation from env exports
(ZEROCLAW_CONFIG_DIR/WORKSPACE) to explicit command_args with
--config-dir flag. Fixes startup crash with 'Permission denied (os error
13)' under OpenRC init system.
2026-02-20 23:30:55 +08:00
Jakub Buzuk
b2bf5531e4 feat(service): enable hands-off OpenRC installation on Alpine
Add automatic runtime-state migration to /etc/zeroclaw with secure ownership/permissions. Implement env-based config resolution for service startup, eliminating the need for manual --service-init flags in the happy path.
2026-02-20 23:30:55 +08:00
Jakub Buzuk
951076e026 feat(service): add --config-dir flag and improve OpenRC setup
- Add global --config-dir CLI flag that sets ZEROCLAW_CONFIG_DIR env
- Add ZEROCLAW_CONFIG_DIR override in config resolution (takes precedence)
- Update OpenRC script to use --config-dir and set env vars for config/workspace
- Prefer /usr/local/bin/zeroclaw for OpenRC executable
- Create /etc/zeroclaw/workspace directory with correct ownership on install
- Update docs to reflect --service-init flag order (service-level before subcommand)
2026-02-20 23:30:55 +08:00
Jakub Buzuk
4c85d7e47c fix(service): always chown log directory on OpenRC install
- Move chown_to_zeroclaw outside the if block
- Fixes permission denied when directory already exists
- Ensures correct ownership even on reinstall
2026-02-20 23:30:55 +08:00
Jakub Buzuk
52cb914a41 fix(service): create zeroclaw group on Alpine Linux
- Alpine adduser -S doesn't create a group automatically
- Explicitly create group with addgroup -S zeroclaw first
- Then add user with -G zeroclaw to join the group
- Update error message commands to include group handling

OpenRC service runs as zeroclaw:zeroclaw, so group must exist.
2026-02-20 23:30:55 +08:00
Jakub Buzuk
d26aa3de1c fix(service): use Alpine-compatible user commands for OpenRC
- Detect Alpine Linux via /etc/alpine-release
- Use adduser/deluser on Alpine instead of useradd/userdel
- Auto-create zeroclaw system user during install
- Provide correct commands in error messages

Alpine uses BusyBox which has different user management commands:
- adduser -S -s /sbin/nologin -H -D zeroclaw (Alpine)
- useradd -r -s /sbin/nologin zeroclaw (Debian/RHEL)
2026-02-20 23:30:55 +08:00
Jakub Buzuk
f110f129e0 fix(service): set correct ownership for OpenRC log directory
- Add chown_to_zeroclaw() helper to change directory ownership
- Log directory /var/log/zeroclaw now owned by zeroclaw:zeroclaw
- Fix docs: config file should be owned by zeroclaw:zeroclaw
  (service runs as zeroclaw user, needs read access)

Fixes permission denied error when service tries to write logs.
2026-02-20 23:30:55 +08:00
Jakub Buzuk
87fa033517 feat(service): add OpenRC support for Alpine Linux
- Add InitSystem enum with auto-detection (systemd/OpenRC)
- Add --service-init CLI flag to override init system detection
- Generate OpenRC init script with security hardening:
  - Runs as zeroclaw:zeroclaw user
  - umask 027 for file permissions
  - Logs to /var/log/zeroclaw/
  - Depends on net and firewall
- Require root for OpenRC install with clear error message
- Warn if binary is in home directory
- Add OpenRC auto-restart support in channels module
- Document OpenRC setup in README and network-deployment.md

Non-goals:
- No changes to systemd behavior
- No user-level OpenRC services
- No other init systems (SysV, runit, s6)

Security: OpenRC install requires root, validates user, creates
directories with proper permissions
2026-02-20 23:30:55 +08:00
Chummy
572cde695a feat(channel): add native nextcloud talk webhook integration 2026-02-20 23:28:18 +08:00
fettpl
90a565ac5a fix(security): enforce cron tool policy gates 2026-02-20 23:27:05 +08:00
Chummy
2d910e77a7 fix(security): enforce schedule cron and policy gates 2026-02-20 22:04:26 +08:00
Chummy
c611ffa43b fix(scheduler): harden idle health heartbeat behavior 2026-02-20 21:39:52 +08:00
Will Sarg
a9a35d50d1
fix(ci): restore containerized validation on main (#1096) 2026-02-20 07:48:58 -05:00
Chummy
1f86727a2a feat(provider): add first-class llama.cpp provider flow 2026-02-20 20:16:26 +08:00
Chummy
9f194130f7 fix(lark): refresh expired tenant access token on code 99991663 2026-02-20 20:10:46 +08:00
Chummy
e6961e0eed feat(delegate): add safe agentic sub-agent tool loop 2026-02-20 19:55:49 +08:00
Chummy
f7b2f7a7d7 feat(agent): run independent tool calls concurrently in runtime loop 2026-02-20 19:36:42 +08:00
Chummy
b26bf262b8 fix(doctor): prevent false scheduler/channel unhealthy states 2026-02-20 19:35:53 +08:00
Chummy
5dbb909bc3 feat(cli): add stdout-safe shell completions command 2026-02-20 19:20:14 +08:00
dependabot[bot]
b23c2e7ae6
chore(deps): bump rand from 0.9.2 to 0.10.0 (#1075)
* chore(deps): bump rand from 0.9.2 to 0.10.0

Bumps [rand](https://github.com/rust-random/rand) from 0.9.2 to 0.10.0.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/rand_core-0.9.2...0.10.0)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(security): keep token generation compatible with rand 0.10

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Will Sarg <12886992+willsarg@users.noreply.github.com>
2026-02-20 05:29:23 -05:00
fettpl
c649ced585
fix(security): enforce cron agent autonomy and rate gates (#626) 2026-02-20 05:23:20 -05:00
Edvard Schøyen
861137b2b3
fix(security): deny unapproved tool calls on non-CLI channels (#998)
When autonomy is set to "supervised", the approval gate only prompted
interactively on CLI. On Telegram and other channels, all tool calls
were silently auto-approved with ApprovalResponse::Yes, including
high-risk tools like shell — completely bypassing supervised mode.

On non-CLI channels where interactive prompting is not possible, deny
tool calls that require approval instead of auto-approving. Users can
expand the auto_approve list in config to explicitly allow specific
tools on non-interactive channels.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 05:22:56 -05:00
Alex Gorevski
2c407f6a55
refactor(lib): restrict internal module visibility to pub(crate) (#985)
Restrict 19 internal-only modules from pub to pub(crate) in lib.rs,
reducing the public API surface of the library crate.

Modules kept pub (used by integration tests, benchmarks, or are
documented extension points per AGENTS.md):
  agent, channels, config, gateway, memory, observability,
  peripherals, providers, rag, runtime, tools

Modules restricted to pub(crate) (not imported via zeroclaw:: by any
external consumer):
  approval, auth, cost, cron, daemon, doctor, hardware, health,
  heartbeat, identity, integrations, migration, multimodal, onboard,
  security, service, skills, tunnel, util

Also restrict 6 command enums (ServiceCommands, ChannelCommands,
SkillCommands, MigrateCommands, CronCommands, IntegrationCommands)
to pub(crate) — main.rs defines its own copies and does not import
these from the library crate. HardwareCommands and PeripheralCommands
remain pub as main.rs imports them via zeroclaw::.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-20 05:06:41 -05:00
Edvard Schøyen
f35a365d83
fix(agent): implement actual concurrent tool execution (#1001)
When parallel_tools is enabled, both code branches in execute_tools()
ran the same sequential for loop. The parallel path was a no-op.

Use futures::future::join_all to execute tool calls concurrently when
parallel_tools is true. The futures crate is already a dependency.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 05:05:33 -05:00
Edvard Schøyen
2ae12578f0
fix(channel): use per-recipient typing handles in Discord (#1005)
Replace the single shared typing_handle with a HashMap keyed by
recipient channel ID. Previously, concurrent messages would fight
over one handle — starting typing for message B would cancel message
A's indicator, and stopping one would kill the other's.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 05:02:39 -05:00
Edvard Schøyen
e2c507664c
fix(provider): surface API key rotation as ineffective warning (#1000)
rotate_key() selects the next key in the round-robin but never applies
it to the underlying provider (Provider trait has no set_api_key
method). The previous info-level log implied rotation was working.

Change to warn-level and explicitly state the key is not applied,
making the limitation visible to operators instead of silently
pretending rotation works.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 05:00:26 -05:00
Alex Gorevski
1a3be5e54f
fix(config): change web_search.enabled default to false for explicit opt-in (#986)
Network access (web search via DuckDuckGo) should require explicit user
consent rather than being enabled by default. This aligns with the
least-surprise principle and the project's secure-by-default policy:
users must opt in to external network requests.

Changes:
- WebSearchConfig::default() now sets enabled: false
- Serde default for enabled field changed from default_true to default
  (bool defaults to false)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-20 04:58:19 -05:00
Allen Huang
0d667752f7 fix(gemini): fix OAuth provider for cloudcode-pa internal API
Three issues prevented the Gemini OAuth path from working end-to-end:

1. Missing `project` field — the internal API returns 500 without it.
   Added project field to InternalGenerateContentRequest and
   resolve_oauth_project() to fetch it via loadCodeAssist endpoint.

2. No token refresh — stale access_token was read at construction time
   and never refreshed. Google OAuth tokens expire after ~1 hour,
   breaking long-lived daemon processes. Added runtime token refresh
   with OAuthTokenState (Arc<Mutex>) that checks expiry before each
   request and refreshes proactively (60s buffer).

3. Wrong response format — internal API nests candidates under a
   `response` field. Added InternalGenerateContentResponse wrapper
   and conditional deserialization in send_generate_content().

Also fixes OAuth warmup to call resolve_oauth_project() instead of
listing models on the public endpoint (which rejects OAuth tokens).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-20 18:29:23 +09:00
Chummy
a2e9c0d1e1 fix(skills): make open-skills sync opt-in and configurable 2026-02-20 16:45:50 +08:00
Chummy
d0674c4b98 fix(channels): harden whatsapp web mode and document dual backend 2026-02-20 16:45:16 +08:00
Chummy
70f12e5df9 test(onboard): add regression coverage for quick setup model override 2026-02-20 16:22:03 +08:00
Chummy
bbaf55eb3b fix(config): harden sync_directory async signature across platforms 2026-02-20 16:21:47 +08:00
Chummy
654f822430 fix(memory): avoid tokio runtime panic when initializing postgres backend 2026-02-20 16:21:25 +08:00
Chummy
7c2c370180 fix(channel): preserve interrupted user context in cached turn normalization 2026-02-20 16:21:24 +08:00
xj
2d6205ee58 fix(channel): use native tool calling to preserve conversation context
AnthropicProvider declared supports_native_tools() = true but did not
override chat_with_tools(). The default trait implementation drops all
conversation history (sends only system + last user message), breaking
multi-turn conversations on Telegram and other channels.

Changes:
- Override chat_with_tools() in AnthropicProvider: converts OpenAI-format
  tool JSON to ToolSpec and delegates to chat() which preserves full
  message history
- Skip build_tool_instructions() XML protocol when provider supports
  native tools (saves ~12k chars in system prompt)
- Remove duplicate Tool Use Protocol section from build_system_prompt()
  for native-tool providers
- Update Your Task section to encourage conversational follow-ups
  instead of XML tool_call tags when using native tools
- Add tracing::warn for malformed tool definitions in chat_with_tools
2026-02-20 13:58:27 +08:00
xj
8c826e581c fix(channel): store raw user message and skip memory recall with history
Two fixes for conversation history quality:

1. Store raw msg.content in ConversationHistoryMap instead of
   enriched_message — memory context is ephemeral per-request and
   pollutes future turns when persisted.

2. Skip memory recall when conversation history exists — prior turns
   already provide context. Memory recall adds noise and can mislead
   the model (e.g. old 'seen' entries overshadowing a code variable
   named seen in the current conversation).
2026-02-20 13:58:27 +08:00
Chummy
8cafeb02e8
fix(composio): request latest v3 tool versions by default (#1039) 2026-02-19 23:29:09 -05:00
Chummy
f274fd5757
fix(channel): prevent false timeout during multi-turn tool loops (#1037) 2026-02-19 23:28:05 -05:00
Chummy
178bb108da
fix(gemini): correct Gemini CLI OAuth cloudcode payload/response handling (#1040)
* fix(gemini): align OAuth cloudcode payload and response parsing

* docs(gemini): document OAuth vs API key endpoint behavior
2026-02-19 23:27:00 -05:00
Chummy
db2d9acd22
fix(skills): support SSH git remotes for skills install (#1035) 2026-02-19 23:25:47 -05:00
Chummy
5c1d6fcba6 fix(channel): align runtime defaults with current model id and test context 2026-02-20 11:05:41 +08:00
Chummy
740eb17d76 fix(channel): hot-apply runtime config updates for running channel service 2026-02-20 11:05:41 +08:00
Chummy
95ec5922d1 fix(channel): robust tool context summary extraction 2026-02-20 10:59:18 +08:00
Edvard
61530520b3 fix(channel): preserve tool context in conversation history
After run_tool_call_loop, only the final text response was saved to
per-sender conversation history. All intermediate tool calls and results
were discarded, so on the next turn the LLM had no awareness of what
tools it used or what it discovered — causing poor follow-up ability.

Record the history length before the tool loop, then scan new messages
for tool names after it completes. Prepend a compact [Used tools: ...]
annotation to the assistant message saved in history, giving the LLM
context about its own actions on subsequent turns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:59:18 +08:00
Chummy
b2c5d611be fix(channel): preserve memory enrichment for current call while storing raw user turn 2026-02-20 10:48:18 +08:00
Edvard
6cbdef8c16 fix(channel): save original user text to conversation history
Previously, the memory-enriched message (with [Memory context] block
prepended) was saved to per-sender conversation history. On subsequent
turns the LLM saw stale memory fragments with raw keys baked into
prior "user" messages, creating compounding noise.

Save the original msg.content instead. Memory context is still injected
for the current LLM call but no longer persists across turns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:48:18 +08:00
Edvard
ea2ff7c53b fix(memory): add minimum-length filter for auto-save messages
Every user message was auto-saved to memory regardless of length,
flooding the store with trivial entries like "ok", "thanks", "hi".
These noise entries competed with real memories during recall, degrading
relevance — especially with keyword-only search.

Skip auto-saving messages shorter than 20 characters. Applied to both
the channel path (channels/mod.rs) and CLI agent path (agent/loop_.rs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:26:31 +08:00
Chummy
63a59e3735 test(channels): assert single tool protocol block in final prompt 2026-02-20 10:25:48 +08:00
Edvard
35a3520621 fix(channel): remove duplicated tool protocol from system prompt
build_system_prompt() included a "## Tool Use Protocol" section with
the tag format and usage instructions. build_tool_instructions() then
appended another identical "## Tool Use Protocol" section with full
JSON schemas. This wasted ~1-2K tokens on every API call.

Remove the duplicate protocol block from build_system_prompt(), keeping
only the compact tool name/description list. The complete protocol with
schemas is provided by build_tool_instructions().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:25:48 +08:00
Edvard
3a8a1754ef fix(channel): replace hardcoded Discord bot text with generic channel text
The Channel Capabilities section in build_system_prompt() was hardcoded
to say "You are running as a Discord bot" for ALL channels, including
Telegram. This caused the LLM to misidentify itself and reference
Discord-specific features regardless of the actual channel.

Replace with generic "messaging bot" text. Per-channel delivery
instructions already exist via channel_delivery_instructions().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:25:07 +08:00
Alex Gorevski
36f971a3d0 fix(security): address CodeQL code-scanning alerts
- Extract hard-coded test vector keys into named constants in bedrock.rs
  and linq.rs to resolve rust/hard-coded-cryptographic-value alerts
- Replace derived Debug impls with manual impls that redact sensitive
  fields (access_token, refresh_token, credential, api_key) on
  QwenOauthCredentials, QwenOauthProviderContext, and
  ResolvedEmbeddingConfig to resolve rust/cleartext-logging alerts
- Redact Matrix user_id and device_id hints in tracing::warn! diagnostic
  messages via crate::security::redact() to resolve cleartext-logging
  alert in matrix.rs

Addresses CodeQL alerts: #77, #95-106

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 16:31:03 -08:00
Alex Gorevski
7d945aea6a
Merge pull request #1017 from zeroclaw-labs/test/peripherals-unit-tests
test(peripherals): add unit tests for peripheral module configuration and listing
2026-02-19 16:17:07 -08:00
Alex Gorevski
9d0ff54037
Merge pull request #1016 from zeroclaw-labs/test/improve-test-assertions
test(quality): replace bare .unwrap() with .expect() in agent and shell tests
2026-02-19 16:16:42 -08:00
Alex Gorevski
1708243470
Merge pull request #1015 from zeroclaw-labs/test/gateway-idempotency-tests
test(gateway): add edge-case idempotency store tests
2026-02-19 16:16:28 -08:00
Alex Gorevski
2a106d051a
Merge pull request #1013 from zeroclaw-labs/fix/docs-inline-code-comments
docs(code): add decision-point comments to agent loop, security policy, and reliable provider
2026-02-19 16:01:19 -08:00
Alex Gorevski
7d7362439e
Merge pull request #1011 from zeroclaw-labs/fix/docs-config-struct-fields
docs(code): add comprehensive doc comments to config schema public fields
2026-02-19 16:00:34 -08:00
Alex Gorevski
200ce0d6fd
Merge pull request #1010 from zeroclaw-labs/fix/docs-trait-doc-comments
docs(code): expand doc comments on security, observability, runtime, and peripheral traits
2026-02-19 15:59:56 -08:00
Alex Gorevski
c6de02b93b
Merge pull request #1008 from zeroclaw-labs/fix/docs-module-level-docs
docs(code): add module-level doc blocks to providers, channels, tools, and security
2026-02-19 15:58:56 -08:00
Argenis
96d5ae0c43
fix(composio): pick first usable account when multiple exist, add connected_accounts alias (#1003)
Root cause of #959: resolve_connected_account_ref returned None when the entity had more than one connected account for an app, silently dropping auto-resolve and causing every execute call to fail with 'cannot find connected account'. The LLM then looped re-issuing the OAuth URL even though the account was already connected.

- resolve_connected_account_ref now picks the first usable account (ordered by updated_at DESC from the API) instead of returning None when multiple accounts exist
- Add 'connected_accounts' as a dispatch alias for 'list_accounts' in handler, schema enum, and description
- 8 new regression tests

Closes #959
2026-02-19 17:19:04 -05:00
Alex Gorevski
867a7a5cbd test(gateway): add edge-case idempotency store tests
Add five new idempotency store tests covering: different-key acceptance,
max_keys clamping to minimum of 1, rapid duplicate rejection, TTL-based
key expiry and re-acceptance, and eviction preserving newest entries.
Addresses audit finding on weak gateway idempotency test coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 13:28:24 -08:00
Alex Gorevski
673697a43e test(peripherals): add unit tests for peripheral module configuration and listing
Add tests for list_configured_boards() covering enabled/disabled states and
empty/non-empty board configurations. Add test verifying create_peripheral_tools()
returns empty when peripherals are disabled. Addresses audit finding CRITICAL-1
for the untested peripherals module — covers all non-hardware-gated logic paths.

Fix pre-existing Windows build errors in config/schema.rs: make non-unix
sync_directory async and gate unix-only imports behind #[cfg(unix)].

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 13:28:22 -08:00
Alex Gorevski
22bd03c65a test(quality): replace bare .unwrap() with .expect() in agent and shell tests
Replace bare .unwrap() calls with descriptive .expect() messages in
src/agent/agent.rs and src/tools/shell.rs test modules. Adds meaningful
failure context for memory creation, agent builder, and tool execution
assertions. Addresses audit finding on test assertion quality (§5.2).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 13:23:33 -08:00
Alex Gorevski
dd541bd7e4 docs(code): add decision-point comments to agent loop, security policy, and reliable provider
Adds section markers and decision-point comments to the three most complex
control-flow modules. Comments explain loop invariants, retry/fallback
strategy, security policy precedence rules, and error handling rationale.

This improves maintainability by making the reasoning behind complex
branches explicit for reviewers and future contributors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 13:19:53 -08:00
Alex Gorevski
eae8a99584 docs(code): add comprehensive doc comments to config schema public fields
Every public field in the Config struct hierarchy now has a /// doc comment
explaining its purpose, default value, and usage context. This ensures
operators and extension developers can understand config options directly
from rustdoc without cross-referencing the config reference documentation.

Comments are consistent with docs/config-reference.md descriptions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 13:19:52 -08:00
Alex Gorevski
25fd10a538 docs(code): expand doc comments on security, observability, runtime, and peripheral traits
The four underdocumented core trait files now include trait-level doc blocks
explaining purpose and architecture role, method-level documentation with
parameter/return/error descriptions, and public struct/enum documentation.

This brings parity with the well-documented provider, channel, tool, and
memory traits, giving extension developers clear guidance for implementing
these core extension points.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 13:19:46 -08:00
Alex Gorevski
4a7dff6ef1 docs(code): add module-level doc blocks to providers, channels, tools, and security
Each major subsystem mod.rs now includes a //! doc block explaining the
subsystem purpose, trait-driven architecture, factory registration pattern,
and extension guidance. This improves the generated rustdoc experience for
developers navigating ZeroClaw's modular architecture.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 13:19:46 -08:00
Alex Gorevski
141d483aa4
Merge pull request #987 from ecschoye/fix/openrouter-embedding-provider
fix(memory): add openrouter as recognized embedding provider
2026-02-19 12:47:25 -08:00
Edvard
832facf5ef fix(memory): add openrouter as recognized embedding provider
The embedding provider factory only recognized "openai" and "custom:*",
causing "openrouter" to silently fall through to NoopEmbedding. This
made vector/semantic search completely non-functional — memory recall
fell back to BM25 keyword-only matching, with 70% of the hybrid score
always returning zero.

Route "openrouter" through OpenAiEmbedding with the OpenRouter API base
URL (https://openrouter.ai/api/v1), which is OpenAI-compatible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:10:25 -05:00
Alex Gorevski
007e9fa7ea
Merge pull request #984 from zeroclaw-labs/fix/improve-config-error-messages
fix(errors): improve config error messages with section paths and remediation hints
2026-02-19 11:56:45 -08:00
Alex Gorevski
b6f99c31d1
Merge pull request #982 from zeroclaw-labs/fix/cli-help-text-improvements
docs(cli): add detailed help text and examples to complex subcommands
2026-02-19 11:54:38 -08:00
Alex Gorevski
f308353ab2
Merge pull request #981 from zeroclaw-labs/fix/config-validation-on-load
fix(config): add startup validation to catch invalid config values early
2026-02-19 11:52:57 -08:00
Alex Gorevski
39a09f007b fix(cli): add range validation for temperature argument
Add a custom value_parser for the --temperature CLI argument to enforce
the documented 0.0-2.0 range at parse time. Previously, the comment
stated the valid range but clap did not reject out-of-range values,
allowing invalid temperatures to propagate to provider API calls.

- Add parse_temperature() validator that rejects values outside 0.0..=2.0
- Wire it into the Agent subcommand's temperature arg via value_parser

Addresses API surface audit §2.3 (CLI argument range validation).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 11:45:12 -08:00
Alex Gorevski
cc07cb66c3 fix(errors): improve config error messages with section paths and remediation hints
Improve vague error messages in channel initialization and tool setup
to include specific config key paths and remediation steps, matching
the quality standard set by proxy validation errors.

Changes:
- telegram.rs: Include [channels.telegram] section path and required
  fields (bot_token, allowed_users) in missing-config error; add
  onboard hint; specify channels.telegram.allowed_users in pairing
  message; improve parse error context
- whatsapp.rs: Specify channels.whatsapp.allowed_numbers key path
  in unauthorized-number warning
- linq.rs: Specify channels.linq.allowed_senders key path in
  unauthorized-sender warning; add onboard hint
- web_search_tool.rs: Include tools.web_search.provider config path
  and valid values in unknown-provider error

Addresses API surface audit §8.2 (config context in error messages).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 11:44:04 -08:00
Alex Gorevski
9f1a306962 docs(cli): add detailed help text and examples to complex subcommands
Add long_about attributes with usage examples to the following commands:

src/main.rs (binary CLI):
- Agent: interactive/single-message modes, provider/peripheral options
- Gateway: port/host binding with examples
- Daemon: full runtime explanation with service install reference
- Cron: cron expression format, timezone handling, all scheduling modes
- Channel: supported types, JSON config format, bind-telegram
- Hardware: discover, introspect, info subcommands
- Peripheral: add, flash, board types
- Config: schema export

src/lib.rs (library enums):
- CronCommands::Add: cron syntax and timezone examples
- CronCommands::AddAt: RFC 3339 timestamp format
- CronCommands::AddEvery: interval in milliseconds
- CronCommands::Once: human-readable duration syntax
- CronCommands::Update: partial field update
- ChannelCommands::Add: JSON config and supported types
- ChannelCommands::BindTelegram: username/numeric ID format
- HardwareCommands::Discover, Introspect, Info: device paths and chip names
- PeripheralCommands::Add: board types and transport paths
- PeripheralCommands::Flash: serial port options

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 11:42:31 -08:00
Alex Gorevski
99cf2fdfee fix(config): add startup validation to catch invalid config values early
Add Config::validate() called from load_or_init() after env overrides
are applied. This catches obviously invalid configuration values at
startup instead of allowing them to silently cause runtime failures.

Validated fields:
- gateway.host: must not be empty
- autonomy.max_actions_per_hour: must be > 0
- scheduler.max_concurrent: must be > 0
- scheduler.max_tasks: must be > 0
- model_routes[*]: hint, provider, model must not be empty
- embedding_routes[*]: hint, provider, model must not be empty
- proxy: delegates to existing ProxyConfig::validate()

Previously, ProxyConfig::validate() was only called during
apply_env_overrides() and only warned/disabled on failure. The new
Config::validate() runs it as a hard error after all overrides are
resolved, ensuring proxy misconfiguration is surfaced early.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 11:37:30 -08:00
Alex Gorevski
77609777ab
Merge pull request #951 from zeroclaw-labs/fix/per-client-pairing-lockout
fix(security): change pairing lockout to per-client accounting
2026-02-19 11:26:46 -08:00
Chummy
3733856093 Fix skill instruction/tool injection in system prompts 2026-02-20 02:16:41 +08:00
Chummy
f2ffd653de fix(channel): preserve trailing user turn in normalization 2026-02-20 02:01:42 +08:00
Chummy
c5834b1077 fix(channel): normalize telegram history for MiniMax 2026-02-20 02:01:42 +08:00
Chummy
4531c342f5 fix(onboard): remove fragile numeric channel dispatch
Use enum-backed channel menu dispatch to prevent duplicated match-arm indices and unreachable-pattern warnings (issue #913).

Also switch OpenAI native tool spec parsing to owned serde structs so tool-schema validation compiles.
2026-02-20 01:56:41 +08:00
Chummy
ef82c7dbcd fix(channels): interrupt in-flight telegram requests on newer sender messages 2026-02-20 01:54:07 +08:00
Chummy
d9a94fc763 fix(skills): escape inlined skill XML content 2026-02-20 01:28:49 +08:00
Edvard
8a4da141d6 fix(skills): inject skill prompts and tools into agent system prompt
Skill prompts and tool definitions from SKILL.toml were parsed and stored
correctly but never included in the agent's system prompt. Both prompt-building
paths (channels/mod.rs and agent/prompt.rs) only emitted skill metadata (name,
description, location), telling the LLM to "read" the SKILL.toml on demand.
This caused the agent to attempt manual file reads that often failed, leaving
skills effectively ignored.

Now both paths inline <instructions> and <tools> blocks inside each <skill>
XML element, so the agent receives full skill context without extra tool calls.

Closes #877

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 01:28:49 +08:00
Chummy
14fb3fbcae fix(composio): resolve connected account refs after OAuth 2026-02-20 01:28:19 +08:00
Chummy
d714d3984e fix(memory): stop autosaving assistant summaries and filter legacy entries 2026-02-20 01:14:08 +08:00
Chummy
6d745e9cb3 fix(openai): deserialize native tool specs with owned fields 2026-02-20 00:07:28 +08:00
Chummy
4c249c579f fix(composio): repair v3 execute path and enable alias 2026-02-20 00:07:28 +08:00
argenis de la rosa
a03ddc3ace fix: gate nusb/hardware discovery to Linux/macOS/Windows only
Android (Termux) reports target_os="android" which is not supported
by nusb::list_devices(). This caused E0425 and E0282 compile errors
when building on Termux.

Changes:
- Cargo.toml: move nusb to a target-gated dependency block so it is
  only compiled on linux/macos/windows
- src/hardware/discover.rs: add #![cfg(...)] file-level gate matching
  the nusb platform support matrix
- src/hardware/mod.rs: gate discover/introspect module declarations,
  discover_hardware() call, handle_command() dispatch, and all helper
  fns on the same platform set; add a clear user-facing message on
  unsupported platforms
- src/security/pairing.rs: replace deprecated rand::thread_rng() with
  rand::rng() to keep clippy -D warnings clean

Fixes #880
2026-02-20 00:02:01 +08:00
Alex Gorevski
56af0d169e fix(security): change pairing lockout to per-client accounting
Replace global failed-attempt counter with per-client HashMap keyed by
client identity (IP address for gateway, chat_id for Telegram).  This
prevents a single attacker from locking out all legitimate clients.

Bounded state: entries are evicted after lockout expiry, and the map is
capped at 1024 tracked clients.

Closes #603

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 07:33:11 -08:00
Alex Gorevski
8f8641d9fb fix(onboard): correct channel selector default to 'Done' item
The hardcoded .default(11) became stale when Lark/Feishu was
added at index 11, shifting 'Done — finish setup' to index 12.
The wizard now pre-selects the wrong channel instead of 'Done'.

Use options.len() - 1 so the default always tracks the last
item regardless of how many channels exist.

Fixes #913

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 07:19:20 -08:00
Alex Gorevski
a4b27d2afe perf: eliminate unnecessary heap allocations across agent loop, memory, and channels
- Replace clone()+clear() with std::mem::take() in chunker (items 1, 6)
- Add Vec::with_capacity() hints in chunker split functions (item 2)
- Replace collect::<Vec<_>>().join() with direct iteration in IRC and
  email channels (item 3)
- Share heading strings via Rc<str> instead of cloning per chunk (item 5)
- Use borrowed references in provider tool spec types to avoid cloning
  name/description/parameters per tool per request (item 7)

Closes #712

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 07:06:27 -08:00
Alex Gorevski
dce7280812
Merge pull request #865 from agorevski/feat/systematic-test-coverage-852
test: add systematic test coverage for 7 bug pattern groups (#852)
2026-02-19 07:02:20 -08:00
Chummy
7b4fe96c8a fix(provider): align qwen oauth alias with qwen base-url mapping 2026-02-19 21:46:48 +08:00
Chummy
05404c6e7a perf(build): gate Matrix channel for faster iteration 2026-02-19 21:29:53 +08:00
Chummy
87dcda638c fix: resolve post-rebase config and ollama test regressions 2026-02-19 21:25:21 +08:00
Chummy
ce6ba36f4e test: account for ellipsis when compacting channel history 2026-02-19 21:25:21 +08:00
Chummy
3d068c21be fix: correct Lark/Feishu channel selection index in wizard 2026-02-19 21:25:21 +08:00
Chummy
dcd0bf641d feat: add multimodal image marker support with Ollama vision 2026-02-19 21:25:21 +08:00
Chummy
63aacb09ff fix(provider): preserve full history in responses fallback 2026-02-19 21:16:55 +08:00
Chummy
48b51e7152 test(config): make tokio::test schema cases async 2026-02-19 21:05:19 +08:00
Chummy
a5d7911923 feat(runtime): add reasoning toggle for ollama 2026-02-19 21:05:19 +08:00
Chummy
8f13fee4a6 test: stabilize qwen oauth env tests and gateway fixtures 2026-02-19 20:54:20 +08:00
Chummy
bca58acdcb feat(provider): add qwen-code oauth credential support 2026-02-19 20:54:20 +08:00
Chummy
e9c280324f test(config): make schema export test async 2026-02-19 20:49:53 +08:00
Chummy
c57f3f51a0 fix(config): derive JsonSchema for embedding routes 2026-02-19 20:49:53 +08:00
Chummy
572aa77c2a feat(memory): add embedding hint routes and upgrade guidance 2026-02-19 20:49:53 +08:00
T. Budiman
2b8547b386 feat(gateway): enrich webhook and WhatsApp with workspace system prompt
Add workspace context (IDENTITY.md, AGENTS.md, etc.) to gateway webhook
and WhatsApp message handlers by using chat_with_system() with a
build_system_prompt()-generated system prompt instead of simple_chat().

This aligns gateway behavior with other channels (Telegram, Discord, etc.)
and the agent loop, which all pass system prompts via structured
ChatMessage::system() or chat_with_system().

Changes:
- handle_webhook: build system prompt and use chat_with_system()
- handle_whatsapp_message: build system prompt and use chat_with_system()

Risk: Low - uses existing build_system_prompt() function, no new dependencies
Rollback: Revert commit removes system prompt enrichment
2026-02-19 20:30:02 +08:00
Chummy
2016382f42 fix(channels): compact sender history and filter oversized memory context 2026-02-19 20:05:35 +08:00
Chummy
2c07fb1792 fix: fail fast on context-window overflow and reset channel history 2026-02-19 19:38:28 +08:00
Chummy
772bb15ed9 fix(tests): stabilize issue #868 model refresh regression 2026-02-19 19:15:08 +08:00
Aleksandr Prilipko
5dd11e6b0f fix(provider): use output_text content type for assistant messages in Codex history
The OpenAI Responses API requires assistant messages to use content type
"output_text" while user messages use "input_text". The prior implementation
used "input_text" for both roles, causing 400 errors on multi-turn history.

Extract build_responses_input() helper for testability and add 3 unit tests
covering role→content-type mapping, default instructions, and unknown roles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:04:02 +08:00
Aleksandr Prilipko
1b57be7223 fix(provider): implement chat_with_history for OpenAI Codex and Gemini
Both providers only implemented chat_with_system, so the default
chat_with_history trait method was discarding all conversation history
except the last user message. This caused the Telegram bot to lose
context between messages.

Changes:
- OpenAiCodexProvider: extract send_responses_request helper, add
  chat_with_history that maps full ChatMessage history to ResponsesInput
- GeminiProvider: extract send_generate_content helper, add
  chat_with_history that maps ChatMessage history to Gemini Content
  (with assistant→model role mapping)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:04:02 +08:00
Chummy
67466254f0 fix(security): parse shell separators only when unquoted 2026-02-19 19:03:20 +08:00
Chummy
a0098de28c fix(bedrock): normalize aws-bedrock alias and harden docs/tests 2026-02-19 19:01:45 +08:00
KevinZhao
0e4e0d590d feat(provider): add dedicated AWS Bedrock Converse API provider
Replace the non-functional OpenAI-compatible stub with a purpose-built
Bedrock provider that implements AWS SigV4 signing from first principles
using hmac/sha2/hex crates — no AWS SDK dependency.

Key capabilities:
- SigV4 authentication (AKSK + optional session token)
- Converse API with native tool calling support
- Prompt caching via cachePoint heuristics
- Proper URI encoding for model IDs containing colons
- Resilient response parsing with unknown block type fallback

Also updates:
- Factory wiring and credential resolution bypass for AKSK auth
- Onboard wizard with Bedrock-specific model selection and guidance
- Provider reference docs with auth, region, and model ID details

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:01:45 +08:00
Chummy
9f94ad6db4 fix(config): log resolved config path source at startup 2026-02-19 18:58:41 +08:00
Chummy
e83e017062 fix(channels): preserve slack thread root ids 2026-02-19 18:52:30 +08:00
Daniel Willitzer
9afe4f28e7 feat(channels): add threading support to message channels
Add optional thread_ts field to ChannelMessage and SendMessage for
platform-specific threading (e.g. Slack threads, Discord threads).

- ChannelMessage.thread_ts captures incoming thread context
- SendMessage.thread_ts propagates thread context to replies
- SendMessage::in_thread() builder for fluent API
- Slack: send with thread_ts, capture ts from incoming messages
- All reply paths in runtime preserve thread context via in_thread()
- All other channels initialize thread_ts: None (forward-compatible)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 18:52:30 +08:00
Chummy
adc998429e test(channel): harden Lark WS heartbeat activity handling 2026-02-19 18:43:49 +08:00
wonder_land
3108ffe3e7 fix(channel): update last_recv on WS Ping/Pong frames in Lark channel
Feishu WebSocket server sends native WS Ping frames as keep-alive probes.
ZeroClaw correctly replied with Pong but did not update last_recv, so the
heartbeat watchdog (WS_HEARTBEAT_TIMEOUT = 300s) triggered a forced
reconnect every 5 minutes even when the connection was healthy.

Two fixes:
- WsMsg::Ping: update last_recv before sending Pong
- WsMsg::Pong: handle explicitly and update last_recv (was silently
  swallowed by the wildcard arm)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 18:43:49 +08:00
Chummy
ba018a38ef chore(provider): normalize fallback test comments to ASCII punctuation 2026-02-19 18:43:45 +08:00
Chummy
435c33d408 fix(provider): preserve fallback runtime options when resolving credentials 2026-02-19 18:43:45 +08:00
Vernon Stinebaker
bb22bdc8fb fix(provider): resolve fallback provider credentials independently
Fallback providers in create_resilient_provider_with_options() were
created via create_provider_with_options() which passed the primary
provider's api_key as credential_override.  This caused
resolve_provider_credential() to short-circuit on the override and
never check the fallback provider's own env var (e.g. DEEPSEEK_API_KEY
for a deepseek fallback), resulting in auth failures (401) when the
primary and fallback use different API services.

Switch to create_provider_with_url(fallback, None, None) so each
fallback resolves its own credential via provider-specific env vars.
This also enables custom: URL prefixes (e.g.
custom:http://host.docker.internal:1234/v1) to work as fallback
entries, which was previously impossible through the options path.

Add three focused tests covering independent credential resolution,
custom URL fallbacks, and mixed fallback chains.
2026-02-19 18:43:45 +08:00
Chummy
f9e1ffe634 style: format schema provider override logic 2026-02-19 18:04:55 +08:00
Chummy
916c0c823b fix: sync gateway pairing persistence and proxy null clears 2026-02-19 18:04:55 +08:00
Jayson Reis
f1ca73d3d2 chore: Remove more blocking io calls 2026-02-19 18:04:55 +08:00
Chummy
1aec9ad9c0 fix(rebase): resolve duplicate tests and gateway AppState fields 2026-02-19 18:03:09 +08:00
Chummy
268a1dee09 style: apply rustfmt after rebase 2026-02-19 18:03:09 +08:00
Chummy
b1ebd4b579 fix(whatsapp): complete wa-rs channel behavior and storage correctness 2026-02-19 18:03:09 +08:00
mmacedoeu
c2a1eb1088 feat(channels): implement WhatsApp Web channel with wa-rs integration
- Add wa-rs dependencies with custom rusqlite storage backend
- Implement functional WhatsApp Web channel using wa-rs Bot
- Integrate TokioWebSocketTransportFactory and UreqHttpClient
- Add message handling via Bot event loop with proper shutdown
- Create WhatsApp storage trait implementations for wa-rs
- Add WhatsApp config schema and onboarding support
- Implement Meta webhook verification for WhatsApp Cloud API
- Add webhook signature verification for security
- Generate unique message keys for WhatsApp conversations
- Remove unused Node.js whatsapp-web-bridge stub

Supersedes: baileys-based bridge approach in favor of native Rust wa-rs
2026-02-19 18:03:09 +08:00
Chummy
9381e4451a fix(config): preserve explicit custom provider against legacy PROVIDER override 2026-02-19 17:54:25 +08:00
Chummy
d6dca4b890 fix(provider): align native tool system-flattening and add regressions 2026-02-19 17:44:07 +08:00
YubinghanBai
48eb1d1f30 fix(agent): inject full datetime into system prompt and allow date command
Three related agent UX issues found during MiniMax channel testing:

1. DateTimeSection injected only timezone, not the actual date/time.
   Models have no reliable way to know the current date from training
   data alone, causing wrong or hallucinated dates in responses.
   Fix: include full timestamp (YYYY-MM-DD HH:MM:SS TZ) in the prompt.

2. The `date` shell command was absent from the security policy
   allowed_commands default list. When a model tried to call
   shell("date") to get the current time, it received a policy
   rejection and told the user it was "blocked by security policy".
   Fix: add "date" to the default allowed_commands list. The command
   is read-only, side-effect-free, and carries no security risk.

3. (Context) The datetime prompt fix makes the date command fallback
   largely unnecessary, but the allowlist addition ensures the tool
   works correctly if models choose to call it anyway.

Non-goals:
- Not changing the autonomy model or risk classification
- Not adding new config keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 17:44:07 +08:00
cbigger
3c60b6bc2d feat(onboard): add optional --model flag to quick setup and channels-only guard 2026-02-19 17:36:20 +08:00
Chummy
ff254b4bb3 fix(provider): harden think-tag fallback and add edge-case tests 2026-02-19 16:54:52 +08:00
YubinghanBai
db7b24b319 fix(provider): strip <think> tags and merge system messages for MiniMax
MiniMax API rejects role: system in the messages array with error
2013 (invalid message role: system). In channel mode, the history
builder prepends a system message and optionally appends a second
one for delivery instructions, causing 400 errors on every channel
turn.

Additionally, MiniMax reasoning models embed chain-of-thought in
the content field as <think>...</think> blocks rather than using
the separate reasoning_content field, causing raw thinking output
to leak into user-visible responses.

Changes:
- Add merge_system_into_user flag to OpenAiCompatibleProvider;
  when set, all system messages are concatenated and prepended to
  the first user message before sending to the API
- Add new_merge_system_into_user() constructor used by MiniMax
- Add strip_think_tags() helper that removes <think>...</think>
  blocks from response content before returning to the caller
- Apply strip_think_tags in effective_content() and
  effective_content_optional() so all non-streaming paths are covered
- Update MiniMax factory registration to use new_merge_system_into_user
- Fix pre-existing rustfmt violation on apply_auth_header call

All other providers continue to use the default path unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 16:54:52 +08:00
Chummy
d33eadea75 docs(config): document schema command and add schema test 2026-02-19 16:41:21 +08:00
s04
282fbe0e95 style: fix cargo fmt formatting in config schema handler 2026-02-19 16:41:21 +08:00
s04
996f66b6a7 feat: add zeroclaw config schema for JSON Schema export
Add a `config schema` subcommand that dumps the full configuration
schema as JSON Schema (draft 2020-12) to stdout. This enables
downstream consumers (like PankoAgent) to programmatically validate
configs, generate forms, and stay in sync with zeroclaw's evolving
config surface without hand-maintaining copies of the schema.

- Add schemars 1.2 dependency and derive JsonSchema on all config
  structs/enums (schema.rs, policy.rs, email_channel.rs)
- Add `Config` subcommand group with `Schema` sub-command
- Output is valid JSON Schema with $defs for all 56 config types
2026-02-19 16:41:21 +08:00
Chummy
1461b00ad1 fix(provider): fallback to responses on chat transport errors 2026-02-19 15:42:38 +08:00
Devin AI
44fa7f3d3d fix(agent): include workspace files when AIEOS identity is configured
Remove early return in IdentitySection::build() that caused AGENTS.md,
SOUL.md, and other workspace files to be silently skipped when AIEOS
identity loaded successfully. Both AIEOS identity and workspace files
now coexist in the system prompt.

Closes zeroclaw-labs/zeroclaw#856

Co-Authored-By: Kristofer Mondlane <kmondlane@gmail.com>
2026-02-19 15:24:58 +08:00
bhagwan
c405cdf19a fix(channel/signal): route UUID senders as direct recipients
Privacy-enabled Signal users have no sourceNumber, so sender()
falls back to their UUID from the source field.  Previously
parse_recipient_target() treated non-E.164 strings without the
group: prefix as group IDs, causing signal-cli to reject the
UUID as an invalid base64 group ID.

Add is_uuid() helper using the already-imported uuid crate and
recognise valid UUIDs as Direct targets alongside E.164 numbers.
2026-02-19 15:19:41 +08:00
Edvard
8b4607a1ef feat(cron): add cron update CLI subcommand for in-place job updates
Add Update variant to CronCommands in both main.rs and lib.rs, with
handler in cron/mod.rs that constructs a CronJobPatch and calls
update_job(). Includes security policy check for command changes.

Fixes from review feedback:
- --tz alone now correctly updates timezone (fetches existing schedule)
- --expression alone preserves existing timezone instead of clearing it
- All-None patch (no flags) now returns an error
- Output uses consistent emoji prefix

Tests exercise handle_command directly to cover schedule construction.

Closes #809

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:11:37 +08:00
Chummy
0910f2a710 fix(config): ignore future channel fields in channel destructuring 2026-02-19 15:11:18 +08:00
Chummy
78e0594e5f fix(openai): align chat_with_tools with http client and strict tool parsing 2026-02-19 15:11:18 +08:00
Lucien Loiseau
f76c1226f1 fix(providers): implement chat_with_tools for OpenAiProvider
The OpenAiProvider overrode chat() with native tool support but never
overrode chat_with_tools(), which is the method called by
run_tool_call_loop in channel mode (IRC/Discord/etc). The trait default
for chat_with_tools() silently drops the tools parameter, sending plain
ChatRequest with no tools — causing the model to never use native tool
calls in channel mode.

Add chat_with_tools() override that deserializes tool specs, uses
convert_messages() for proper tool_call_id handling, and sends
NativeChatRequest with tools and tool_choice.

Also add Deserialize derive to NativeToolSpec and NativeToolFunctionSpec
to support deserialization from OpenAI-format JSON.
2026-02-19 15:11:18 +08:00
Chummy
d8409b0878 fix(channels): include mattermost in launch/list checks 2026-02-19 14:53:58 +08:00
Inu-Dial
af2510879e fix(daemon): add missing items and turn to let binding 2026-02-19 14:53:58 +08:00
Chummy
275d3e7791 style: apply rustfmt to async fs updates 2026-02-19 14:52:29 +08:00
Jayson Reis
b9af601943 chore: Remove blocking read strings 2026-02-19 14:52:29 +08:00
Chummy
bc0be9a3c1 fix(linq): accept prefixed and uppercase webhook signatures 2026-02-19 14:49:52 +08:00
George McCain
361e750576 feat(channels): add Linq channel for iMessage/RCS/SMS support
The existing iMessage channel relies on AppleScript and only works on macOS.
Linq provides a REST API for iMessage, RCS, and SMS — this gives ZeroClaw
native iMessage support on any platform via webhooks.

Implements LinqChannel following the same patterns as WhatsAppChannel:
- Channel trait impl (send, listen, health_check, typing indicators)
- Webhook handler with HMAC-SHA256 signature verification
- Sender allowlist filtering
- Onboarding wizard step with connection testing
- 18 unit tests covering parsing, auth, and signature verification

Resolves #656 — the prior issue was closed without a merged PR, so this
is the actual implementation.
2026-02-19 14:49:52 +08:00
Chummy
cf476a81c1 fix(provider): preserve native Ollama tool history structure 2026-02-19 14:32:43 +08:00
reidliu41
cd59dc65c4 fix(provider): enable native tool calling for OllamaProvider 2026-02-19 14:32:43 +08:00
Chummy
d548caa5f3 fix(channel): clamp configurable timeout to minimum 30s 2026-02-19 14:19:49 +08:00
ZeroClaw Contributor
41a6ed30dd feat(channel): make message timeout configurable via channels_config.message_timeout_secs
Add configurable timeout for processing channel messages (LLM + tools).
Default: 300s (optimized for on-device LLMs like Ollama).
Can be overridden in config.toml:

[channels_config]
message_timeout_secs = 600
2026-02-19 14:19:49 +08:00
wonder_land
4ecaf6070c fix(tools): remove non-string enum from pushover priority for Gemini compat
The pushover tool priority parameter schema used integer enum values
[-2, -1, 0, 1, 2]. OpenAI-compatible APIs accept this, but the Gemini
API (and Gemini-relay proxies) strictly require all enum values to be
strings, rejecting the request with 400 Bad Request.

This causes every agent turn to fail with a non_retryable error when
using Gemini models, regardless of user message content, because tool
schemas are included in every request.

Fix: remove the enum constraint, keeping integer type and description
documenting the valid range. This is valid for both OpenAI and Gemini
providers and requires no changes to execute() which already uses
as_i64() with range validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 13:24:23 +08:00
Alex Gorevski
52dc9fd9e9
Merge pull request #883 from agorevski/fix/cleartext-logging-sensitive-data
fix(security): prevent cleartext logging of sensitive data
2026-02-18 21:11:31 -08:00
Alex Gorevski
bbbcd06cca
Merge pull request #882 from agorevski/fix/hardcoded-crypto-test-values-v2
fix(security): replace hard-coded crypto test values with runtime-generate secrets
2026-02-18 21:11:23 -08:00
Alex Gorevski
4a9fc9b6cc fix(security): prevent cleartext logging of sensitive data
Address CodeQL rust/cleartext-logging alerts by breaking data-flow taint
chains from sensitive variables (api_key, credential, session_id, user_id)
to log/print sinks. Changes include:

- Replace tainted profile IDs in println! with untainted local variables
- Add redact() helper for safe logging of sensitive values
- Redact account identifiers in auth status output
- Rename session_id locals in memory backends to break name-based taint
- Rename user_id/user_id_hint in channels to break name-based taint
- Custom Debug impl for ComputerUseConfig to redact api_key field
- Break taint chain in provider credential factory via string reconstruction
- Remove client IP from gateway rate-limit log messages
- Break taint on auth token extraction and wizard credential flow
- Rename composio account ref variable to break name-based taint

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 20:12:45 -08:00
Alex Gorevski
9a784954f6 fix(security): replace hard-coded crypto test values with runtime-generated secrets
Replace hard-coded string literals used as cryptographic keys/secrets in
gateway webhook and WhatsApp signature verification tests with runtime-
generated random values. This resolves CodeQL rust/hard-coded-cryptographic-value
alerts while maintaining identical test coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 20:03:38 -08:00
Alex Gorevski
925a352454 fix(security): enforce HTTPS for sensitive data transmission
Add URL scheme validation before HTTP requests that transmit sensitive
data (account IDs, phone numbers, user IDs). All endpoints already use
HTTPS URLs, but this explicit check satisfies CodeQL rust/cleartext-
transmission analysis and prevents future regressions if URLs are
changed.

Affected files: composio.rs, whatsapp.rs, qq.rs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 20:03:02 -08:00
Chummy
8f7d879fd5 feat(onboard): add and harden Lark/Feishu wizard support
- add interactive Lark/Feishu setup in onboarding
- validate credentials with timeouts and clearer diagnostics
- add webhook/allowlist safety warnings for insecure defaults
- document interactive onboarding workflow in channels reference

Co-authored-by: HalcyonAzure <53591299+HalcyonAzure@users.noreply.github.com>
2026-02-19 10:37:47 +08:00
Chummy
606f2860a0 fix(matrix): send markdown replies and improve e2ee diagnostics
Enable matrix-sdk markdown support and send Matrix messages with text_markdown so clients can render formatted_body.

Add listener startup diagnostics for device verification and backup state to reduce confusion around matrix_sdk_crypto backup warnings.

Expand Matrix docs with backup-warning interpretation, unverified-device guidance, markdown formatting expectations, and updated log keyword appendix.
2026-02-19 10:23:10 +08:00
Alex Gorevski
7f03ab77a9 test: add systematic test coverage for 7 bug pattern groups (#852)
Add ~105 test cases across 7 test groups identified in issue #852:

TG1 - Provider resolution (27 tests): Factory resolution, alias mapping,
      custom URLs, auth styles, credential wiring
TG2 - Config persistence (18 tests): Config defaults, TOML roundtrip,
      agent/memory config, workspace dirs
TG3 - Channel routing (14 tests): ChannelMessage identity contracts,
      SendMessage construction, Channel trait send/listen roundtrip
TG4 - Agent loop robustness (12 integration + 14 inline tests): Malformed
      tool calls, failing tools, iteration limits, empty responses, unicode
TG5 - Memory restart (14 tests): Dedup on same key, restart persistence,
      session scoping, recall, concurrent stores, categories
TG6 - Channel message splitting (8+8 inline tests): Code blocks at boundary,
      long words, emoji, CJK chars, whitespace edge cases
TG7 - Provider schema (21 tests): ChatMessage/ToolCall/ChatResponse
      serialization, tool_call_id preservation, auth style variants

Also fixes a bug in split_message_for_telegram() where byte-based indexing
could panic on multi-byte characters (emoji, CJK). Now uses char_indices()
consistent with the Discord split implementation.

Closes #852

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 15:28:34 -08:00
Youhana Sheriff
b43e9eb325 fix(provider): polish kimi-code wiring and onboarding parity 2026-02-19 01:15:02 +08:00
Youhana Sheriff
cb91a2f914 feat(provider): add dedicated kimi-code provider support 2026-02-19 01:15:02 +08:00
Chummy
e8e9c0ea6c Revert "feat(provider): add dedicated kimi-code provider support"
This reverts commit 88dcd17a30.
2026-02-19 01:15:02 +08:00
Chummy
5563b755dc Revert "fix(provider): polish kimi-code wiring and onboarding parity"
This reverts commit 0b66ed026c.
2026-02-19 01:15:02 +08:00
Chummy
dea5dcad36 fix(onboard): refine nvidia nim onboarding catalogs and docs 2026-02-18 23:13:18 +08:00
Chummy
e1aeabdb5f fix(providers): align compatible chat client and response test 2026-02-18 22:50:02 +08:00
Chummy
b4b379e3e7 fix(providers): harden tool fallback and refresh model catalogs 2026-02-18 22:50:02 +08:00
Chummy
43494f8331 fix(observability): remove duplicate no-op observer event arms 2026-02-18 22:47:22 +08:00
Chummy
18b6ea1e79 feat(matrix): enable e2ee flow and add channel operations docs 2026-02-18 22:45:11 +08:00
Chummy
e6029e8cec
test(channels): guard max_tool_iterations wiring for channel runtime (#817)
* test(channels): add regression coverage for configured tool iteration limits

* chore(ci): refresh checks after first-interaction workflow fix

* test(channels): reconcile merged runtime-route and iteration tests
2026-02-18 22:40:22 +08:00
Chummy
0bd2fbba2a feat(providers): add MiniMax OAuth credential flow 2026-02-18 22:31:20 +08:00
Chummy
8988a069a6 feat(channels): add runtime provider/model switching for telegram and discord 2026-02-18 22:23:13 +08:00
Chummy
0b66ed026c fix(provider): polish kimi-code wiring and onboarding parity 2026-02-18 22:22:10 +08:00
Chummy
88dcd17a30 feat(provider): add dedicated kimi-code provider support 2026-02-18 22:22:10 +08:00
Chummy
ce104bed45 feat(proxy): add scoped proxy configuration and docs runbooks
- add scope-aware proxy schema and runtime wiring for providers/channels/tools

- add agent callable proxy_config tool for fast proxy setup

- standardize docs system with index, template, and playbooks
2026-02-18 22:10:42 +08:00
Chummy
13ee9e6398 test: cover deterministic HashMap ordering paths 2026-02-18 21:55:40 +08:00
Syeda Anshrah Gillani
58bb9fa9a7 refactor: sort HashMap keys for deterministic output in identity and doctor 2026-02-18 21:55:40 +08:00
Chummy
58acf1efd3 fix(provider): surface actionable custom-provider failure diagnostics 2026-02-18 21:50:14 +08:00
Chummy
fed8ba21b8 fix(mattermost): handle mention boundary scanning correctly 2026-02-18 21:25:28 +08:00
Vernon Stinebaker
d97866a640 feat(mattermost): add mention_only config for @-mention filtering
Add mention_only support for the Mattermost channel, matching the existing
Discord implementation. When enabled, the bot only processes messages that
contain an @-mention of the bot username, reducing noise in busy channels.

- Add mention_only field to MattermostConfig schema (Option<bool>, default false)
- Rename get_bot_user_id() to get_bot_identity() returning (user_id, username)
- Add contains_bot_mention_mm() with case-insensitive word-boundary matching
  and metadata.mentions array support
- Add normalize_mattermost_content() to strip @-mentions from processed text
- Wire mention_only through channel and cron factory constructors
- Add 23 new tests covering mention detection, stripping, case-insensitivity,
  word boundaries, metadata mentions, empty-after-strip, and disabled passthrough
2026-02-18 21:25:28 +08:00
xj
65a12dd611 fix: resolve all clippy warnings and fix test compilation errors
Address clippy pedantic/all lints: format_push_string in sqlite memory,
match_same_arms and match_wildcard_for_single_variants in anthropic
provider and prometheus observer, option_as_ref_cloned in main. Fix
pre-existing test compilation errors in gateway (missing max_keys arg
and trust_forwarded_headers field) and memory_store (missing security
arg). Add .worktrees/ to gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:06:54 +08:00
Lucien Loiseau
6062888d1b feat(providers): add OVHcloud AI Endpoints as native provider
Route OVHcloud through OpenAiProvider (with proper tool_call_id
serialization) instead of OpenAiCompatibleProvider, fixing tool-call
round-trips against vLLM-based endpoints.

- Add base_url field and with_base_url() constructor to OpenAiProvider
- Replace all hardcoded api.openai.com URLs with self.base_url
- Pass api_url through for the openai provider arm
- Register ovhcloud/ovh provider with env var OVH_AI_ENDPOINTS_ACCESS_TOKEN
2026-02-18 20:54:49 +08:00
Chummy
50fd5b81e1 fix(test): stabilize cron output capture and clippy cleanups 2026-02-18 20:29:26 +08:00
Chummy
483acccdb7 feat(memory): add configurable postgres storage backend 2026-02-18 20:29:26 +08:00
Chummy
e10d359cf9 fix(email): preserve legacy poll_interval alias and avoid lock across await 2026-02-18 20:18:39 +08:00
Kieran
b3d5284be1 refactor(channel): remove dead poll_interval_secs from EmailConfig
Field is unused since the IMAP polling loop was replaced with IDLE.
Serde ignores unknown fields on deserialization, so existing configs
with the key set will continue to work without error.

Also add two focused tests for idle_timeout_secs: explicit
deserialization and propagation into EmailChannel.
2026-02-18 20:18:39 +08:00
Kieran
5d9e8705ac refactor(channel): replace hand-rolled IMAP with async-imap IDLE
Replace the blocking, poll-based IMAP client with async-imap and
IMAP IDLE (RFC 2177) for instant push delivery. Key changes:

- Add async-imap dependency with tokio runtime feature
- Rewrite connect/fetch/listen paths to fully async using tokio TLS
- Implement IDLE loop with exponential backoff reconnection (1s–60s cap)
- Add idle_timeout_secs config field (default 1740s per RFC 2177)
- Convert health_check to async connect-and-logout with 10s timeout
- Update affected tests from sync to #[tokio::test]

SMTP send path, allowlist enforcement, and Channel trait contract
are unchanged.
2026-02-18 20:18:39 +08:00
Chummy
cad7fb8f22 fix(channels): enforce reply_target naming consistency 2026-02-18 19:56:31 +08:00
Chummy
cfa7215688 fix(telegram): harden mention-only matching and retry cache 2026-02-18 19:51:42 +08:00
ZeroClaw Contributor
c0a80ad656 feat(channel): add mention_only option for Telegram groups
Adds mention_only config option to Telegram channel, allowing the bot
to only respond to messages that @-mention the bot in group chats.
Direct messages are always processed regardless of this setting.

Behavior:
- When mention_only = true: Bot only responds to group messages containing @botname
- When mention_only = false (default): Bot responds to all allowed messages
- DM/private chats always work regardless of mention_only setting

Implementation:
- Fetch and cache bot username from Telegram API on startup
- Check for @botname mention in group messages
- Strip mention from message content before processing

Config example:
[channels.telegram]
bot_token = "your_token"
mention_only = true

Changes:
- src/config/schema.rs: Add mention_only to TelegramConfig
- src/channels/telegram.rs: Implement mention_only logic + 6 new tests
- src/channels/mod.rs: Update factory calls
- src/cron/scheduler.rs: Update constructor call
- src/onboard/wizard.rs: Update wizard config
- src/daemon/mod.rs: Update test config
- src/integrations/registry.rs: Update test config
- TESTING_TELEGRAM.md: Add mention_only test section
- CHANGELOG.md: Document feature

Risk: medium
Backward compatible: Yes (default: false)
2026-02-18 19:51:42 +08:00
xj
3b75c6cc42 fix(channel): remove HEARTBEAT.md from channel system prompt
HEARTBEAT.md is only relevant to the heartbeat worker, which reads it
directly from disk. Including it in channel system prompts caused LLMs
to emit spurious 'HEARTBEAT_OK' acknowledgments at the start of
channel responses.

The agent prompt (src/agent/prompt.rs) still includes HEARTBEAT.md,
which is correct for agent and heartbeat contexts.
2026-02-18 19:36:46 +08:00
Chummy
c70d9b181d test: stabilize cron shell output capture and gemini warmup noop 2026-02-18 19:26:07 +08:00
Chummy
ecad19d512 fix(identity): normalize canonical AIEOS schema payloads 2026-02-18 19:25:12 +08:00
Vernon Stinebaker
3b0133596c feat(providers): add native tool calling for OpenAI-compatible providers
Implement chat_with_tools() on CompatibleProvider so OpenAI-compatible
endpoints (OpenRouter, local LLMs, etc.) can use structured tool calling
instead of prompt-injected tool descriptions.

Changes:
- CompatibleProvider: capabilities() reports native_tool_calling, new
  chat_with_tools() sends tools in API request and parses tool_calls
  from response, chat() bridges to chat_with_tools() when ToolSpecs
  are provided
- RouterProvider: chat_with_tools() delegation with model hint resolution
- loop_.rs: expose tools_to_openai_format as pub(crate), add
  tools_to_openai_format_from_specs for ToolSpec-based conversion

Adds 9 new tests and updates 1 existing test.
2026-02-18 18:06:36 +08:00
Chummy
5e800c38f1 fix(channel): cancel and join scoped typing task safely 2026-02-18 18:01:29 +08:00
Jayson Reis
12c5473083 fix: Keep typing status on telegram while message is being processed
# Conflicts:
#	src/channels/mod.rs
2026-02-18 18:01:29 +08:00
Chummy
1bfd50bce9 fix(mattermost): preserve threaded default and docs 2026-02-18 17:46:19 +08:00
Vernon Stinebaker
58120b1c69 feat(mattermost): add thread_replies config and typing indicator
Add two Mattermost channel enhancements:

1. thread_replies config option (default: false)
   - When false, replies go to the channel root instead of threading.
   - When true, replies thread on the original post.
   - Existing thread replies always stay in-thread regardless of setting.

2. Typing indicator (start_typing/stop_typing)
   - Implements the Channel trait's typing methods for Mattermost.
   - Fires POST /api/v4/users/me/typing every 4s in a background task.
   - Supports parent_id for threaded typing indicators.
   - Aborts cleanly on stop_typing via JoinHandle.

Updated all MattermostChannel::new call sites (start_channels, scheduler)
and added 9 unit tests covering thread routing and edge cases.
2026-02-18 17:46:19 +08:00
Chummy
bc5b1a7841 fix(providers): harden reasoning_content fallback behavior 2026-02-18 17:07:38 +08:00
Vernon Stinebaker
dd4f5271d1 feat(providers): support reasoning_content fallback for thinking models
Reasoning/thinking models (Qwen3, GLM-4, DeepSeek, etc.) may return
output in `reasoning_content` instead of `content`. Add automatic
fallback for both OpenAI and OpenAI-compatible providers, including
streaming SSE support.

Changes:
- Add `reasoning_content` field to response structs in both providers
- Add `effective_content()` helper that prefers `content` but falls
  back to `reasoning_content` when content is empty/null/missing
- Update all extraction sites to use `effective_content()`
- Add streaming SSE fallback for `reasoning_content` chunks
- Add 16 focused unit tests covering all edge cases

Tested end-to-end against GLM-4.7-flash via local LLM server.
2026-02-18 17:07:38 +08:00
Chummy
219764d4d8 fix(channels): recover malformed invoke/tool_call output in daemon mode 2026-02-18 17:01:36 +08:00
Chummy
75a9eb383c test(security): enforce lowercase token hex assertion 2026-02-18 16:56:45 +08:00
Chummy
918be53a30 test(security): harden token format regression coverage 2026-02-18 16:56:45 +08:00
hayoial
58958d9991 fix: add per-sender conversation history for channel messages
Channel messages (Telegram, Discord, etc.) previously had no multi-turn
context — each incoming message was processed with a fresh history
containing only the system prompt and the current user message.

This patch:
- Maintains a per-sender conversation history map (Arc<Mutex<HashMap>>)
- Restores prior turns when processing each new message
- Saves user + assistant turns after successful LLM response
- Caps history at 50 messages per sender to bound memory usage

Fixes the channel context continuity issue where the bot would respond
with 'I have no context' to every follow-up question.
2026-02-18 16:35:38 +08:00
Xiangjun Ma
f1db63219c refactor(telegram): address code review findings
- Add strip_tool_call_tags() to finalize_draft to prevent Markdown
  parse failures from tool-call tags reaching Telegram API
- Deduplicate parse_reply_target() call in update_draft (was called
  twice, discarding thread_id both times)
- Replace body.as_object_mut().unwrap() mutation with separate
  plain_body JSON literal (eliminates unwrap in runtime path)
- Clean up per-chat rate-limit HashMap entry in finalize_draft to
  prevent unbounded growth over long uptimes
- Extract magic number 80 to STREAM_CHUNK_MIN_CHARS constant in
  agent loop
2026-02-18 16:33:33 +08:00
Chummy
e326e12039 test(telegram): cover draft streaming paths and simplify stream modes 2026-02-18 16:33:33 +08:00
Xiangjun Ma
e21fe1ff55 fix(telegram): address Copilot review feedback
- Fix silent parse failures: message_id.parse().unwrap_or(0) replaced
  with match + tracing::warn on parse error (update_draft, finalize_draft)
- Fix UTF-8 panic: byte-based truncation replaced with char_indices()
  safe boundary detection for TELEGRAM_MAX_MESSAGE_LENGTH
- Fix global rate limiter: Mutex<Option<Instant>> replaced with
  Mutex<HashMap<String, Instant>> for per-chat rate limiting so
  concurrent conversations don't interfere with each other
- Document Block variant: clarify it's reserved for future use and
  currently behaves the same as Partial
2026-02-18 16:33:33 +08:00
Xiangjun Ma
93538a70e3 fix(agent): relay final response as progressive chunks via on_delta
Previously on_delta sent the entire completed response as a single
message, defeating the purpose of the streaming draft updates. Now
the text is split into ~80-char chunks on whitespace boundaries
(UTF-8 safe via split_inclusive) and sent progressively through the
channel, so Telegram draft edits show text arriving incrementally.

The consumer in process_channel_message already accumulates chunks
and calls update_draft with the full text so far, and Telegram's
rate-limiting (draft_update_interval_ms) throttles editMessageText
calls to avoid API spam.
2026-02-18 16:33:33 +08:00
Xiangjun Ma
118cd53922 feat(channel): stream LLM responses to Telegram via draft message edits
Wire the existing provider-layer streaming infrastructure through the
channel trait and agent loop so Telegram users see tokens arrive
progressively via editMessageText, instead of waiting for the full
response.

Changes:
- Add StreamMode enum (off/partial/block) and draft_update_interval_ms
  to TelegramConfig (backward-compatible defaults: off, 1000ms)
- Add supports_draft_updates/send_draft/update_draft/finalize_draft to
  Channel trait with no-op defaults (zero impact on existing channels)
- Implement draft methods on TelegramChannel using sendMessage +
  editMessageText with rate limiting and Markdown fallback
- Add on_delta mpsc::Sender<String> parameter to run_tool_call_loop
  (None preserves existing behavior)
- Wire streaming in process_channel_message: when channel supports
  drafts, send initial draft, spawn updater task, finalize on completion

Edge cases handled:
- 4096-char limit: finalize draft and fall back to chunked send
- Broken Markdown: use no parse_mode during streaming, apply on finalize
- Edit failures: fall back to sending complete response as new message
- Rate limiting: configurable draft_update_interval_ms (default 1s)
2026-02-18 16:33:33 +08:00
Chummy
a0b277b21e fix(web-search): harden config handling and trim unrelated CI edit 2026-02-18 15:24:21 +08:00
adisusilayasa
1757add64a feat(tools): add web_search_tool for internet search
Add native web search capability that works regardless of LLM tool-calling
support. This is particularly useful for GLM models via Z.AI that don't
reliably support standard tool calling formats.

Features:
- DuckDuckGo provider (free, no API key required)
- Brave Search provider (optional, requires API key)
- Configurable max results and timeout
- Enabled by default

Configuration (config.toml):
  [web_search]
  enabled = true
  provider = "duckduckgo"
  max_results = 5

The tool allows agents to search the web for current information without
requiring proper tool calling support from the LLM.

Also includes CI workflow fix for first-interaction action inputs.
2026-02-18 15:24:21 +08:00
Chummy
f3bdff1d69 fix(agent): harden glm tool-call parsing and scope PR 2026-02-18 15:23:35 +08:00
adisusilayasa
58c81aa258 feat(agent): add GLM-style tool call parsing
GLM models output tool calls in proprietary formats that ZeroClaw
doesn't natively support. This adds parsing for GLM-specific formats:

- browser_open/url>https://... -> shell tool with curl command
- shell/command>ls -> shell tool with command arg
- http_request/url>... -> http_request tool
- Plain URLs -> shell tool with curl command

Also adds:
- find_json_end() helper for parsing JSON objects
- Unclosed <toolcall> tag handling
- Unit tests for GLM-style parsing

The parsing is deliberately placed after XML and markdown code block
parsing, so it acts as a fallback for models that don't use standard
tool calling formats.

This enables GLM models (via Z.AI or other providers) to successfully
execute tools in ZeroClaw.
2026-02-18 15:23:35 +08:00
Mike Boensel
0166f2d4de fix(token): update token generation to use rand::rng() to resolve deprecation warnings 2026-02-18 02:11:51 -05:00
Chummy
dd454178ed perf(memory): fold recall/vector/list optimizations into spawn_blocking refactor 2026-02-18 14:46:51 +08:00
Alex Gorevski
4e528dde7d perf(memory): wrap blocking SQLite calls in tokio::task::spawn_blocking
Problem:
Every async fn in SqliteMemory acquired self.conn.lock() and ran
synchronous rusqlite queries directly on the Tokio runtime thread.
This blocks the async executor, preventing other tasks from making
progress — especially harmful under concurrent recall/store load.

Fix:
- Change conn from Mutex<Connection> to Arc<Mutex<Connection>> so
  the connection handle can be cloned into spawn_blocking closures.
- Wrap all synchronous database operations (store, recall, get, list,
  forget, count, health_check) in tokio::task::spawn_blocking.
- Split get_or_compute_embedding into three phases: cache check
  (blocking), embedding computation (async I/O), cache store
  (blocking) — ensuring no lock is held across await points.
- Apply the same pattern to the reindex method.

The async I/O (embedding computation) remains on the Tokio runtime
while all SQLite access runs on the blocking thread pool, preventing
executor starvation.

Ref: zeroclaw-labs/zeroclaw#710 (Item 4)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 14:46:51 +08:00
Chummy
83b098d7ac fix(imessage): preserve sqlite conn across polling safely 2026-02-18 14:45:05 +08:00
Alex Gorevski
1ddcb0a573 perf(imessage): reuse persistent SQLite connection across poll cycles
Problem:
The iMessage listener opened a new SQLite connection to the Messages
database on every ~3-second poll cycle via get_max_rowid() and
fetch_new_messages(), creating ~40 connection open/close cycles per
minute. Each cycle incurs filesystem syscalls, WAL header reads,
and potential page cache cold starts.

Fix:
Open a single read-only connection before the poll loop and reuse it
across iterations using the 'shuttle' pattern: the connection is moved
into each spawn_blocking closure and returned alongside the results,
then reassigned for the next iteration. This eliminates per-poll
connection overhead while preserving the spawn_blocking pattern that
keeps SQLite I/O off the Tokio runtime thread.

The standalone get_max_rowid() and fetch_new_messages() helper
functions are retained for use by tests and other callers.

Ref: zeroclaw-labs/zeroclaw#710 (Item 9)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 14:45:05 +08:00
Chummy
14066d094f test(runtime): stabilize docker root mount assertion 2026-02-18 14:42:39 +08:00
Alex Gorevski
9a6fa76825 readd tests, remove markdown files 2026-02-18 14:42:39 +08:00
Chummy
e2634c72c2 test(config): include query_classification in config fixtures 2026-02-18 14:41:58 +08:00
Edvard
6e53341bb1 feat(agent): add rule-based query classification for automatic model routing
Classify incoming user messages by keyword/pattern and route to the
appropriate model hint automatically, feeding into the existing
RouterProvider. Disabled by default; opt-in via [query_classification]
config section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:41:58 +08:00
Edvard
1336c2f03e feat(providers): add warmup() for OpenAI, Anthropic, Gemini, Compatible, GLM
All five providers have HTTP clients but did not implement warmup(),
relying on the trait default no-op. This adds lightweight warmup calls
to establish TLS + HTTP/2 connection pools on startup, reducing
first-request latency. Each warmup is skipped when credentials are
absent, matching the OpenRouter pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:35:03 +08:00
Chummy
a85a4a8194 fix(config): resolve ZEROCLAW_WORKSPACE root/workspace paths safely 2026-02-18 14:30:53 +08:00
bhagwan
b2976eb474 fix(config): support both legacy and new ZEROCLAW_WORKSPACE structure
ZEROCLAW_WORKSPACE can now be either:
- Legacy path: /path/to/workspace (config at /path/to/.zeroclaw/config.toml)
- Parent path: /path/to (config at /path/to/config.toml, workspace at /path/to/workspace)

This maintains backward compatibility with Docker's legacy folder structure
while also supporting the new parent-dir layout.
2026-02-18 14:30:53 +08:00
Chummy
da7c21f469 style(anthropic): format cache conversation test block 2026-02-18 14:29:50 +08:00
tercerapersona
455eb3b847 feat: add prompt caching support to Anthropic provider
Implements Anthropic's prompt caching API to enable significant cost
reduction (up to 90%) and latency improvements (up to 85%) for
requests with repeated content.

Key features:
- Auto-caching heuristics: large system prompts (>3KB), tool
  definitions, and long conversations (>4 messages)
- Full backward compatibility: cache_control fields are optional
- Supports both string and block-array system prompt formats
- Cache control on all content types (text, tool_use, tool_result)

Implementation details:
- Added CacheControl, SystemPrompt, and SystemBlock structures
- Updated NativeContentOut and NativeToolSpec with cache_control
- Strategic cache breakpoint placement (last tool, last message)
- Comprehensive test coverage for serialization and heuristics

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
(cherry picked from commit fff04f4edb5e4cb7e581b1b16035da8cc2e55cef)
2026-02-18 14:29:50 +08:00
Maya Walcher
63bc4721e3 feat(onboard): add signup URL, model catalog, and live fetch for Astrai
Add three onboarding improvements for the Astrai provider:

- Signup URL: users now see "Get your API key at: https://as-trai.com"
  during onboarding instead of a blank prompt
- Curated model list: auto (best execution), GPT-4o, Claude Sonnet 4.5,
  DeepSeek V3, Llama 3.3 70B
- Live model fetch: Astrai's OpenAI-compatible /v1/models endpoint is
  now queried when an API key is present, matching other providers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:19:21 +08:00
Chummy
431287184b style(tests): apply rustfmt to brittle-test hardening changes 2026-02-18 14:17:58 +08:00
Alex Gorevski
45cdd25b3d fix(tests): harden brittle tests for cross-platform stability and refactoring resilience
## Problem

The test suite contained several categories of latent brittleness
identified in docs/testing-brittle-tests.md that would surface during
refactoring or cross-platform (Windows) CI execution:

1. Hardcoded Unix paths: \Path::new("/tmp")\ and \PathBuf::from("/tmp")\
   used as workspace directories in agent tests, which fail on Windows
   where /tmp does not exist.

2. Exact string match assertions: ~20 \ssert_eq!(response, "exact text")\
   assertions in agent unit and e2e tests that break on any mock wording
   change, even when the underlying orchestration behavior is correct.

3. Fragile error message string matching: \.contains("specific message")\
   assertions coupled to internal error wording rather than testing the
   error category or behavioral outcome.

## What Changed

### Hardcoded paths → platform-agnostic temp dirs (4 files, 7 locations)
- \src/agent/tests.rs\: Replaced all 4 instances of \Path::new("/tmp")\
  and \PathBuf::from("/tmp")\ with \std::env::temp_dir()\ in
  \make_memory()\, \uild_agent_with()\, \uild_agent_with_memory()\,
  and \uild_agent_with_config()\ helpers.
- \	ests/agent_e2e.rs\: Replaced all 3 instances in \make_memory()\,
  \uild_agent()\, and \uild_agent_xml()\ helpers.

### Exact string assertions → behavioral checks (2 files, ~20 locations)
- \src/agent/tests.rs\: Converted 10 \ssert_eq!(response, "...")\ to
  \ssert!(!response.is_empty(), "descriptive message")\ across tests for
  text pass-through, tool execution, tool failure recovery, XML dispatch,
  mixed text+tool responses, multi-tool batch, and run_single delegation.
- \	ests/agent_e2e.rs\: Converted 9 exact-match assertions to behavioral
  checks. Multi-turn test now uses \ssert_ne!(r1, r2)\ to verify
  sequential responses are distinct without coupling to exact wording.
- Provider error propagation test simplified to \ssert!(result.is_err())\
  without asserting on the error message string.

### Fragile error message assertions → structural checks (2 files)
- \src/tools/git_operations.rs\: Replaced fragile OR-branch string match
  (\contains("git repository") || contains("Git command failed")\) with
  structural assertions: checks \!result.success\, error is non-empty,
  and error does NOT mention autonomy/read-only (verifying the failure
  is git-related, not permission-related).
- \src/cron/scheduler.rs\: Replaced \contains("agent job failed:")\ with
  \!success\ and \!output.is_empty()\ checks that verify failure behavior
  without coupling to exact log format.

## What Was NOT Changed (and why)
- \src/agent/loop_.rs\ parser tests: Exact string assertions are the
  contract for XML tool call parsing — the exact output IS the spec.
- \src/providers/reliable.rs\: Error message assertions test the error
  format contract (provider/model attribution in failure messages).
- \src/service/mod.rs\: Already platform-gated with \#[cfg]\; XML escape
  test is a formatting contract where exact match is appropriate.
- \src/config/schema.rs\: TOML test strings use /tmp as data values for
  deserialization tests, not filesystem access; HOME tests already use
  \std::env::temp_dir()\.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 14:17:58 +08:00
Chummy
decea532ed refactor(memory): keep default hybrid weights while adding relevance threshold 2026-02-18 14:14:33 +08:00
Edvard
8a1e7cc7ef fix(agent): use config max_tool_iterations, add memory relevance filtering, rebalance search weights
Three fixes for conversation quality issues:

1. loop_.rs and channels now read max_tool_iterations from AgentConfig
   instead of using a hardcoded constant of 10, making it configurable.

2. Memory recall now filters entries below a configurable
   min_relevance_score threshold (default 0.4), preventing unrelated
   memories from bleeding into conversation context.

3. Default hybrid search weights rebalanced from 70/30 vector/keyword
   to 40/60, reducing cross-topic semantic bleed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:14:33 +08:00
Alex Gorevski
21c5f58363 perf(cron): wrap record_run INSERT+DELETE in explicit transaction
Problem:
In record_run(), an INSERT into cron_runs followed by a pruning DELETE
ran as separate implicit transactions. If the INSERT succeeded but the
DELETE failed (e.g., due to disk pressure or lock contention), the run
table would grow unboundedly since the pruning step was lost while the
new row persisted.

Fix:
Wrap both statements in an explicit transaction using
conn.unchecked_transaction(). If either statement fails, the entire
transaction is rolled back, maintaining the invariant that the run
history stays bounded by max_run_history.

Ref: zeroclaw-labs/zeroclaw#710 (Item 5)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 14:07:31 +08:00
Alex Gorevski
9967eeb954 perf(cron): add composite index on cron_runs(job_id, started_at)
Problem:
The pruning query in record_run uses WHERE job_id = ?1 with
ORDER BY started_at DESC, but only single-column indexes exist
for job_id and started_at separately. SQLite must scan one index
and then sort or scan the other, which is suboptimal for the
combined filter + sort pattern used during pruning.

Fix:
Add a composite index CREATE INDEX IF NOT EXISTS
idx_cron_runs_job_started ON cron_runs(job_id, started_at).
This lets SQLite satisfy the WHERE job_id = ?1 ORDER BY
started_at DESC subquery in a single index scan without a
separate sort step. The existing single-column indexes are
retained for other queries that filter on only one column.

Ref: zeroclaw-labs/zeroclaw#710 (Item 7)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 14:06:59 +08:00
Chummy
d42cb1e906 fix(auth): rebase PR #200 onto main and restore auth CLI flow 2026-02-18 12:57:44 +08:00
Codex
96109d46d1 Fix pending OAuth verifier storage and account id fallback 2026-02-18 12:57:44 +08:00
Codex
e8aa63822a fix PR #200 review issues 2026-02-18 12:57:44 +08:00
Codex
39087a446d Fix OpenAI Codex contract, SSE parsing, and default xhigh reasoning 2026-02-18 12:57:44 +08:00
Codex
007368d586 feat(auth): add subscription auth profiles and codex/claude flows 2026-02-18 12:57:44 +08:00
Edvard
6d8725c9e6 fix(agent): log warning when native tool call arguments fail JSON parsing
The NativeToolDispatcher silently defaults to an empty object when tool
call arguments from the LLM fail to parse as JSON. The XML dispatcher
already logs a warning for the same case (line 68). Add a matching
tracing::warn with tool name and parse error for observability parity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:56:56 +08:00
fettpl
7de052c7d2 fix(cron): add timeout and bounded execution for due jobs 2026-02-18 12:55:21 +08:00
Alex Gorevski
5f5cb27690
fix(cron): handle ALTER TABLE race condition in schema migration
Problem: add_column_if_missing() checks PRAGMA table_info for column existence, then issues ALTER TABLE ADD COLUMN if not found. When two concurrent processes both pass the check before either executes the ALTER, the second process fails with a 'duplicate column name' error.

Fix: Catch the 'duplicate column name' SQLite error after the ALTER TABLE and treat it as a benign no-op. Also explicitly drop statement/rows handles before ALTER to release locks.

Ref: #710 (Item 8)
2026-02-17 23:50:08 -05:00
Edvard
63602a262f fix(agent): use config-driven limits in run_tool_call_loop and trim_history
run_tool_call_loop used a hardcoded MAX_TOOL_ITERATIONS (10) and
trim_history/auto_compact_history used a hardcoded MAX_HISTORY_MESSAGES (50),
ignoring the user-configurable agent.max_tool_iterations and
agent.max_history_messages values in config.toml.

Meanwhile, agent.rs correctly reads from config — creating an inconsistency
where CLI single-shot mode respected config but the channel runtime and
interactive CLI loop silently ignored it.

Changes:
- Rename constants to DEFAULT_* to clarify they are fallback defaults
- Add max_tool_iterations parameter to run_tool_call_loop
- Add max_history parameter to trim_history and auto_compact_history
- Thread config.agent.max_tool_iterations through ChannelRuntimeContext
- Both CLI code paths now pass config values to run_tool_call_loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:49:28 +08:00
h1n054ur
1c074d5204 fix(discord): use channel name for reply routing instead of discord channel ID
The Discord channel was setting msg.channel to the numeric Discord
channel ID instead of the literal string 'discord'. This caused
process_channel_message() to fail the channels_by_name lookup since
the map is keyed by channel name (e.g. 'discord', 'telegram', 'slack').

The result: the bot receives messages and generates LLM responses but
never sends them back -- target_channel resolves to None so the send
call is silently skipped.

Every other channel (telegram, slack, whatsapp, matrix, signal, irc,
imessage, lark, dingtalk, qq, email, mattermost) correctly sets this
field to its channel name string. Discord was the only one using the
platform-specific ID.
2026-02-18 12:49:06 +08:00
fettpl
4f9c87ff74 fix(policy): standardize side-effect tool autonomy gates 2026-02-18 12:42:56 +08:00
Edvard
89d0fb9a1e feat(providers): implement chat_with_history for GLM provider
The GLM provider previously relied on the trait default for
chat_with_history, which only forwarded the last user message. This adds
a proper multi-turn implementation that sends the full conversation
history to the GLM API, matching the pattern used by OpenRouter, Ollama,
and other providers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:33:51 +08:00
Ademílson Tonato
73e675d298 feat(memory): optional SQLite connection open timeout
- Add memory.sqlite_open_timeout_secs config (None = wait indefinitely).
- When set, open the DB in a thread with recv_timeout; cap at 300s.
- Default remains None for backward compatibility.
- Document in README; add tests for timeout path and default.
2026-02-18 12:18:05 +08:00
Edvard
b3b1679218 feat(channels): implement typing indicator for Telegram channel
Add start_typing/stop_typing overrides to TelegramChannel following the
same pattern as DiscordChannel: spawn a tokio task that sends
sendChatAction every 4 seconds (Telegram typing expires after 5s),
and abort it on stop_typing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:06:58 +08:00
Chummy
2560399423 feat(observability): focus PR 596 on Prometheus backend 2026-02-18 12:06:05 +08:00
argenis de la rosa
eba544dbd4 feat(observability): implement Prometheus metrics backend with /metrics endpoint
- Adds PrometheusObserver backend with counters, histograms, and gauges
- Tracks agent starts/duration, tool calls, channel messages, heartbeat ticks, errors, request latency, tokens, sessions, queue depth
- Adds GET /metrics endpoint to gateway for Prometheus scraping
- Adds provider/model labels to AgentStart and AgentEnd events for better observability
- Adds as_any() method to Observer trait for backend-specific downcast

Metrics exposed:
- zeroclaw_agent_starts_total (Counter) with provider/model labels
- zeroclaw_agent_duration_seconds (Histogram) with provider/model labels
- zeroclaw_tool_calls_total (Counter) with tool/success labels
- zeroclaw_tool_duration_seconds (Histogram) with tool label
- zeroclaw_channel_messages_total (Counter) with channel/direction labels
- zeroclaw_heartbeat_ticks_total (Counter)
- zeroclaw_errors_total (Counter) with component label
- zeroclaw_request_latency_seconds (Histogram)
- zeroclaw_tokens_used_last (Gauge)
- zeroclaw_active_sessions (Gauge)
- zeroclaw_queue_depth (Gauge)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:06:05 +08:00
Edvard
c04f2855e4 feat(tools): expose custom memory categories in memory_store tool
The MemoryCategory::Custom variant already exists in the memory backend
but the memory_store tool only accepted core/daily/conversation. Now any
string is accepted as a category, passing through to Custom(name) for
non-builtin values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:05:37 +08:00
Chummy
9e9a4a53ab style(gemini): apply rustfmt to oauth endpoint patch 2026-02-18 10:25:15 +08:00
KNIGHTABDO
1d8e57d388 fix(gemini): route OAuth tokens to cloudcode-pa.googleapis.com
Gemini CLI OAuth tokens are scoped for Google's internal Code Assist
API at cloudcode-pa.googleapis.com/v1internal, not the public
generativelanguage.googleapis.com/v1beta endpoint.

This commit:
- Routes OAuth requests to the correct internal endpoint
- Wraps the request payload with model metadata (internal API format)
- Keeps API key auth unchanged on the public endpoint

Fixes #578
2026-02-18 10:25:15 +08:00
ZeroClaw Agent
36062fb1c2 feat(telegram): add forum topic support
Implement Telegram Forum (topic) support to allow the bot to respond
in the same topic where it was mentioned/called.

Changes:
- parse_update_message(): Extract message_thread_id and format reply_target as 'chat_id:thread_id'
- send(): Parse recipient to extract chat_id and optional thread_id
- All send methods now pass thread_id parameter and include message_thread_id in API requests
- Added test for forum topic message parsing

This ensures bot replies stay within the same forum topic thread.
2026-02-18 10:22:40 +08:00
Chummy
3467d34596 fix(agent): avoid duplicate text in markdown tool_call fallback 2026-02-18 10:15:46 +08:00
Edvard
cb7df7c87f style(agent): apply rustfmt formatting to loop_.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:15:46 +08:00
Edvard
0c46b56555 fix(agent): satisfy clippy::if_not_else lint in tool history push
Flip conditional to use positive check (is_empty) in the if-branch
to resolve clippy::if_not_else error in CI strict delta lint gate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:15:46 +08:00
Edvard
0e5a785015 fix(agent): use native format for tool result history in run_tool_call_loop
When use_native_tools is true, the agent loop now:
- Formats assistant history as JSON with tool_calls array (matching
  what convert_messages() expects to reconstruct NativeMessage)
- Pushes each tool result as ChatMessage::tool with tool_call_id
  (instead of a single ChatMessage::user with XML tool_result tags)
- Adds fallback parsing for markdown code block tool calls
  (```tool_call ... ``` and hybrid ```tool_call ... </tool_call>)

Without this, the second LLM call (sending tool results back) gets
rejected with 4xx by OpenRouter/Gemini because the message format
doesn't match the OpenAI tool calling API expectations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:15:46 +08:00
Edvard
508fb53ac1 fix(provider): delegate native tool calling through ReliableProvider
ReliableProvider wraps underlying providers with retry/fallback logic
but did not delegate `supports_native_tools()` or `chat_with_tools()`.
This caused the agent loop to fall back to prompt-based tool calling
for all providers, even those with native tool support (OpenRouter,
OpenAI, Anthropic). Models like Gemini 2.0 Flash would then output
tool calls as text instead of structured API responses, breaking the
tool execution loop entirely.

Add `supports_native_tools()` delegation to the primary provider and
`chat_with_tools()` with the same retry/fallback logic as the existing
`chat_with_system()` and `chat_with_history()` methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:15:46 +08:00
Chummy
c5602a80bd fix(gateway): honor configured max key bounds 2026-02-18 10:05:44 +08:00
fettpl
c507856710 fix(gateway): harden client identity and bound key stores 2026-02-18 10:05:44 +08:00
Kieran
d756293871 feat: add /clear command 2026-02-18 10:01:22 +08:00
Maya Walcher
c830a513a5 fix(provider): address Astrai follow-up review from #486
- Add "astrai" to factory_all_providers_create_successfully test
- Add "astrai" => "ASTRAI_API_KEY" in provider_env_var() for onboarding
- Add Astrai to onboarding provider selection list (Gateway tier)
- Add provider_env_var("astrai") assertion in known_providers test

Addresses review comments from @chumyin on #486.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:00:32 +08:00
daniiiiekisde
92eeb8889f feat(onboard): add missing Ollama Cloud models 2026-02-18 09:46:54 +08:00
Alex Gorevski
fbc26be7af fix(policy): treat git branch listing as read-only operation
Remove 'branch' from requires_write_access() to resolve the
contradiction where branch listing was classified as both read-only
and write-requiring. Branch listing only enumerates local refs and
has no side effects, so it should remain available under ReadOnly
autonomy mode.

Add regression tests:
- branch_is_not_write_gated: verifies classification consistency
- allows_branch_listing_in_readonly_mode: verifies end-to-end
  execution under ReadOnly autonomy
- is_read_only_detection: now explicitly asserts branch is read-only

Resolves zeroclaw-labs/zeroclaw#612

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-18 09:15:28 +08:00
Will Sarg
42f1d40f1f
fix(ci): unblock dependabot dependency PR checks (#658) 2026-02-17 15:51:07 -05:00
Alex Gorevski
bbe5530c1a
fix(security): disable automatic redirects in http_request tool (#624)
Closes #607

The http_request tool validated the initial URL against the domain
allowlist and private-host rules, but reqwest's default redirect policy
followed redirects automatically without revalidating each hop. This
allowed SSRF via redirect chains from allowed domains to internal hosts.

Set redirect policy to Policy::none() so 3xx responses are returned
as-is. Callers that need to follow redirects must issue a new request,
which goes through validate_url again.

Severity: High — SSRF/allowlist bypass via redirect chains.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-17 15:15:48 -05:00
Alex Gorevski
290d971d5e
fix(security): reject shell-unsafe chars in screenshot filename (#625)
Closes #601

The Linux screenshot path uses sh -c with single-quote interpolation.
A filename containing quote characters could break quoting and inject
shell tokens. Add a check that rejects filenames with any shell-breaking
characters (quotes, backticks, dollar signs, semicolons, pipes, etc.)
before passing to the shell command.

Severity: High — command injection in tool execution path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-17 15:13:17 -05:00
Will Sarg
30b9df761a
fix(gateway): persist pairing tokens and honor docker config (#630)
* fix(gateway): honor config bind settings and persist pairing

Resolve docker-compose startup and restart friction by:
- using config host/port defaults for gateway/daemon unless CLI flags are passed
- persisting paired token hashes to config.toml on successful /pair
- running container default command as 'zeroclaw gateway' (no hardcoded --host/--port overrides)
- updating compose image/docs to zeroclaw-labs namespace
- adding MODEL env fallback for default_model override and targeted regression tests

* chore(ci): sync lockfile and restore rustfmt parity

Update Cargo.lock to match Cargo.toml and format src/service/mod.rs so rust quality gates stop failing with unrelated baseline drift.
2026-02-17 15:05:56 -05:00
Will Sarg
3c4ed2e28e
fix(providers): clarify reliable failure entries for custom providers (#594)
* fix(workflows): standardize runner configuration for security jobs

* ci(actionlint): add Blacksmith runner label to config

Add blacksmith-2vcpu-ubuntu-2404 to actionlint self-hosted-runner labels config
to suppress "unknown label" warnings during workflow linting.

This label is used across all workflows after the Blacksmith migration.

* fix(actionlint): adjust indentation for self-hosted runner labels

* feat(security): enhance security workflow with CodeQL analysis steps

* fix(security): update CodeQL action to version 4 for improved analysis

* fix(security): remove duplicate permissions in security workflow

* fix(security): revert CodeQL action to v3 for stability

The v4 version was causing workflow file validation failures.
Reverting to proven v3 version that is working on main branch.

* fix(security): remove duplicate permissions causing workflow validation failure

The permissions block had duplicate security-events and actions keys,
which caused YAML validation errors and prevented workflow execution.

Fixes: workflow file validation failures on main branch

* fix(security): remove pull_request trigger to reduce costs

* fix(security): restore PR trigger but skip codeql on PRs

* fix(security): resolve YAML syntax error in security workflow

* refactor(security): split CodeQL into dedicated scheduled workflow

* fix(security): update workflow name to Rust Package Security Audit

* fix(codeql): remove push trigger, keep schedule and on-demand only

* feat(codeql): add CodeQL configuration file to ignore specific paths

* Potential fix for code scanning alert no. 39: Hard-coded cryptographic value

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix(ci): resolve auto-response workflow merge markers

* fix(build): restore ChannelMessage reply_target usage

* ci(workflows): run workflow sanity on workflow pushes for all branches

* ci(workflows): rename auto-response workflow to PR Auto Responder

* ci(workflows): require owner approval for workflow file changes

* ci: add lint-first PR feedback gate

* ci(workflows): split label policy checks from workflow sanity

* ci(workflows): consolidate policy and rust workflow setup

* ci: add safe pull request intake sanity checks

* ci(security): switch audit to pinned rustsec audit-check

* fix(providers): clarify reliable failure entries for custom providers

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-02-17 13:53:03 -05:00
argenis de la rosa
34af6a223a Merge remote-tracking branch 'origin/main' into feat/glm-provider
Resolved conflicts in:
- Cargo.toml: kept both `ring` (JWT auth) and `prost` (protobuf) dependencies
- src/onboard/wizard.rs: accepted main branch version
- src/providers/mod.rs: accepted main branch version
- Cargo.lock: accepted main branch version

Note: The custom `glm::GlmProvider` from this PR was replaced with
main's OpenAiCompatibleProvider approach for GLM, which uses base URLs.
The main purpose of this PR is Windows daemon support via Task Scheduler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 13:27:58 -05:00
Chummy
f97f995ac0 refactor(provider): unify China alias families across modules 2026-02-18 01:01:57 +08:00
Chummy
e85418eda4 chore(ci): align formatting and clippy output for gates 2026-02-18 00:50:51 +08:00
Chummy
ce23cbaeea fix(cli): harden providers listing and keep provider map aligned 2026-02-18 00:50:51 +08:00
reidliu41
feaa4aba60 feat(cli): add zeroclaw providers command to list supported providers
- Add `zeroclaw providers` CLI command that lists all 28 supported AI providers
- Each entry shows: config ID, display name, local/cloud tag, active marker, and aliases
- Also shows `custom:<URL>` and `anthropic-custom:<URL>` escape hatches at the bottom

Previously users had no way to discover available providers without reading source code. The
unknown-provider error message suggests `run zeroclaw onboard --interactive` but doesn't list
options. This command gives immediate visibility.
2026-02-18 00:50:51 +08:00
Chummy
cba7d1a14b fix(onboard): persist custom workspace selection across sessions 2026-02-18 00:47:20 +08:00
Chummy
e2e431d9e7 style(channels): apply rustfmt drift after main rebase 2026-02-18 00:45:26 +08:00
Chummy
ef02f25c46 refactor(sync): migrate remaining std mutex usage to parking_lot 2026-02-18 00:45:26 +08:00
Chummy
5942caa083 chore(pr539): scope to dingtalk daemon fixes only 2026-02-18 00:42:40 +08:00
JamesYin
9eff7a13bb fix(agent): parse legacy schedule tool_call payloads 2026-02-18 00:42:40 +08:00
JamesYin
af5d1f3066 fix(agent): recover malformed tool_call blocks with leading text 2026-02-18 00:42:40 +08:00
JamesYin
59f74e8f39 fix(agent): retry malformed prefixed tool_call markup 2026-02-18 00:42:40 +08:00
JamesYin
128e888d7a style: format rebased conflict resolutions 2026-02-18 00:42:40 +08:00
JamesYin
3522d51f98 fix(agent): retry malformed tool_call payloads in tool loop 2026-02-18 00:42:40 +08:00
JamesYin
4b89e91a5a fix(dingtalk,daemon): process stream callbacks and supervise DingTalk channel
Include DingTalk in daemon supervised channel detection so the listener starts in daemon mode.

Handle CALLBACK stream frames, subscribe to bot message topic, and improve session webhook routing for private/group replies.

Add regression tests for supervised-channel detection and DingTalk payload/chat-id parsing.
2026-02-18 00:42:40 +08:00
Argenis
0f68756ec7
fix(telegram): strip tool_call tags before sending messages
Strip XML-style tool call tags from messages before sending to Telegram to prevent Markdown parsing failures (status 400).

Fixes #503

Co-Authored-By: ayush-thakur02 <ayush.th2002@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 11:28:35 -05:00
Chummy
40ab5c3507 fix(agent): rebase alias-tag parser and align channel send API 2026-02-18 00:28:08 +08:00
Chummy
4243d8ec86 fix(agent): parse tool-call alias tags in channel runtime 2026-02-18 00:28:08 +08:00
Chummy
ed675d4e6b test(agent): add comprehensive loop test suite 2026-02-18 00:26:31 +08:00
Chummy
62eba544e2 fix(channels): satisfy strict delta lint in Mattermost reply routing 2026-02-18 00:19:20 +08:00
Chummy
318e0fa9a7 fix(core): align CLI channel send call with SendMessage 2026-02-18 00:19:20 +08:00
Vernon Stinebaker
7e3f5ff497 feat(channels): add Mattermost integration for sovereign communication 2026-02-18 00:19:20 +08:00
Chummy
0aa35eb669 fix(build): complete strict lint and test cleanup (replacement for #476) 2026-02-18 00:18:54 +08:00
Chummy
fc6e8eb521
fix(provider): follow-up CN/global consistency for Z.AI and aliases (#554)
* fix(provider): harden CN/global routing consistency for Chinese vendors

* fix(agent): migrate CLI channel send to SendMessage

* fix(onboard): deduplicate Z.AI key URL match arms
2026-02-18 00:04:56 +08:00
Chummy
cd0dd13476 fix(channels): complete SendMessage migration after rebase 2026-02-17 23:28:08 +08:00
Kieran
dbebd48dfe refactor(channel): accept SendMessage struct in Channel::send()
Refactor the Channel trait to accept a SendMessage struct instead of
separate message and recipient string parameters. This enables passing
additional metadata like email subjects.

Changes:
- Add SendMessage struct with content, recipient, and optional subject
- Update Channel::send() signature to accept &SendMessage
- Update all 12 channel implementations
- Update call sites in channels/mod.rs and gateway/mod.rs

Subject field usage:
- Email: uses subject for email subject line
- DingTalk: uses subject as markdown message title
- All others: ignore subject (no native platform support)
2026-02-17 23:28:08 +08:00
Chummy
b8ed42edbb fix(channels,memory): normalize Discord mentions and repair lucid test args 2026-02-17 23:26:53 +08:00
Chummy
bb641d28c2 fix(approval): harden CLI approval flow and summaries 2026-02-17 23:06:12 +08:00
stawky
ab561baa97 feat(approval): interactive approval workflow for supervised mode (#215)
- Add auto_approve / always_ask fields to AutonomyConfig
- New src/approval/ module: ApprovalManager with session-scoped allowlist,
  ApprovalRequest/Response types, audit logging, CLI interactive prompt
- Insert approval hook in agent_turn before tool execution
- Non-CLI channels auto-approve; CLI shows Y/N/A prompt
- Skip approval for read-only tools (file_read, memory_recall) by default
- 15 unit tests covering all approval logic
2026-02-17 23:06:12 +08:00
Chummy
f489971889 style(channels): align module ordering in channels mod 2026-02-17 22:55:21 +08:00
Chummy
94ec351d73 fix(channels): set qq reply_target for strict delta lint 2026-02-17 22:55:21 +08:00
Chummy
14d93c075e fix(channels): tighten qq listener lifecycle and english labels 2026-02-17 22:55:21 +08:00
elonf
ed71bce447 feat(channels): add QQ Official channel via Tencent Bot SDK
Implement QQ Official messaging channel using OAuth2 authentication
with Discord-like WebSocket gateway protocol for events.

- Add QQChannel with send/listen/health_check support
- Add QQConfig (app_id, app_secret, allowed_users)
- OAuth2 token refresh and WebSocket heartbeat management
- Message deduplication with capacity-based eviction
- Support both C2C (private) and group AT messages
- Integrate with onboard wizard, integrations registry, and channel
  list/doctor commands
- Include unit tests for user allowlist, deduplication, and config
2026-02-17 22:55:21 +08:00
Chummy
d94d7baa14 feat(ollama): unify local and remote endpoint routing
Integrate cloud endpoint behavior into existing ollama provider flow, avoid a separate standalone doc, and keep configuration minimal via api_url/api_key.

Also align reply_target and memory trait call sites needed for current baseline compatibility.
2026-02-17 22:52:09 +08:00
Chummy
85de9b5625
fix(provider): split CN/global endpoints for Chinese provider variants (#542)
* fix(providers): add CN/global endpoint variants for Chinese vendors

* fix(onboard): deduplicate provider key-url match arms

* chore(i18n): normalize non-English literals to English
2026-02-17 22:51:51 +08:00
leon
62eadec274 fix(telegram): surface getUpdates API conflicts in logs 2026-02-17 22:48:40 +08:00
leon
c59dea3755 fix(channels): auto-reload managed daemon after telegram bind 2026-02-17 22:48:40 +08:00
leon
fa94117269 feat(telegram): add operator bind command for unauthorized users 2026-02-17 22:48:40 +08:00
leon
bfc67c9c29 feat(telegram): add bind-code pairing and fix reply routing 2026-02-17 22:48:40 +08:00
Chummy
b2690f6809 feat(provider): add native tool calling API (supersedes #450)
Co-authored-by: YubinghanBai <baiyubinghan@gmail.com>
2026-02-17 22:47:10 +08:00
Chummy
767c66f3c8 fix(channel/signal): harden target routing and SSE stability 2026-02-17 22:35:33 +08:00
bhagwan
55f2637cfe feat(channel): add Signal channel via signal-cli JSON-RPC daemon
Adds a new Signal messaging channel that connects to a running
signal-cli daemon's native HTTP API (JSON-RPC + SSE).

  [channels_config.signal]
  http_url = "http://127.0.0.1:8686"
  account = "+1234567890"
  group_id = "group_id"  # optional, omit for all
  allowed_from = ["+1111111111"]
  ignore_attachments = true
  ignore_stories = true

Implementation:
- SSE listener at /api/v1/events for incoming messages
- JSON-RPC sends via /api/v1/rpc (method: send)
- Health check via /api/v1/check
- Typing indicators via sendTyping RPC
- Supports DMs and group messages (room_id filtering)
- Allowlist-based sender filtering (E.164 or wildcard)
- Optional attachment/story filtering
- Fixed has_supervised_channels() to include signal + irc/lark/dingtalk

Registered in channel list, doctor, start, integrations registry, and
daemon supervisor gate. Includes unit tests for config serde, sender
filtering, room matching, envelope processing, and deserialization.

No new dependencies (uses existing uuid, futures-util, reqwest).
2026-02-17 22:35:33 +08:00
Will Sarg
a62c7a5893 fix(clippy): satisfy strict delta lints in SSE streaming path 2026-02-17 09:26:21 -05:00
Will Sarg
b8bef379e2 fix(channels): reply via reply_target and improve local Docker cache reuse 2026-02-17 09:22:01 -05:00
Will Sarg
9e0958dee5 fix(ci): repair parking_lot migration regressions in PR #535 2026-02-17 09:10:40 -05:00
Will Sarg
ee05d62ce4
Merge branch 'main' into pr-484-clean 2026-02-17 08:54:24 -05:00
Chummy
01c419bb57 test(providers): keep unicode boundary test in English text 2026-02-17 21:51:58 +08:00
Khoi Tran
3c62b59a72 fix(copilot): add proper OAuth device-flow authentication
The existing Copilot provider passes a static Bearer token, but the
Copilot API requires short-lived session tokens obtained via GitHub's
OAuth device code flow, plus mandatory editor headers.

This replaces the stub with a dedicated CopilotProvider that:

- Runs the OAuth device code flow on first use (same client ID as VS Code)
- Exchanges the OAuth token for a Copilot API key via
  api.github.com/copilot_internal/v2/token
- Sends required Editor-Version/Editor-Plugin-Version headers
- Caches tokens to disk (~/.config/zeroclaw/copilot/) with auto-refresh
- Uses Mutex to prevent concurrent refresh races / duplicate device prompts
- Writes token files with 0600 permissions (owner-only)
- Respects GitHub's polling interval and code expiry from device flow
- Sanitizes error messages to prevent token leakage
- Uses async filesystem I/O (tokio::fs) throughout
- Optionally accepts a pre-supplied GitHub token via config api_key

Fixes: 403 'Access to this endpoint is forbidden'
Fixes: 400 'missing Editor-Version header for IDE auth'
2026-02-17 21:51:58 +08:00
Will Sarg
a2f29838b4
fix(build): restore ChannelMessage reply_target usage (#541) 2026-02-17 08:41:02 -05:00
Vernon Stinebaker
df31359ec4
feat(agent): scrub credentials from tool output (#532)
* feat(channels): add channel capabilities to system prompt

Add channel capabilities section to system prompt so the agent knows
it can send Discord messages directly without asking permission.
Also reminds agent not to repeat or echo credentials.

Co-authored-by: Vernon Stinebaker <vernon.stinebaker@gmail.com>

* feat(agent): scrub credentials from tool output

* chore: fix clippy and formatting for scrubbing
2026-02-17 08:23:11 -05:00
beee003
8ad5b6146b
feat: add Astrai as a named provider (#486)
Add Astrai (https://as-trai.com) as a first-class OpenAI-compatible
provider. Astrai is an AI inference router with built-in cost
optimization, PII stripping, and compliance logging.

- Register ASTRAI_API_KEY env var in resolve_api_key
- Add "astrai" entry in provider factory → as-trai.com/v1
- Add factory_astrai unit test
- Add Astrai to compatible provider test list
- Update README provider count (22+ → 23+) and list

Co-authored-by: Maya Walcher <maya.walcher@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 08:22:38 -05:00
fettpl
55b3c2c00c
test(security): add HTTP hostname canonicalization edge-case tests (#522)
* test(security): add HTTP hostname canonicalization edge-case tests

Document that Rust's IpAddr::parse() rejects non-standard IP notations
(octal, hex, decimal integer, zero-padded) which provides defense-in-depth
against SSRF bypass attempts. Tests only — no production code changes.

Closes #515

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

* style: apply rustfmt to providers/mod.rs

Fix pre-existing formatting issue from main.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 08:16:00 -05:00
Rin
9ec1106f53
security: fix argument injection in shell command validation (#465) 2026-02-17 08:11:20 -05:00
Alex Gorevski
529a3d0242
fix(cli): respect config gateway.port and gateway.host for Gateway/Daemon commands (#456)
The CLI --port and --host args had hardcoded defaults (8080, 127.0.0.1)
that always overrode the user's config.toml [gateway] settings (port=3000,
host=127.0.0.1). Changed both args to Option types and fall back to
config.gateway.port / config.gateway.host when not explicitly provided.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-17 08:10:32 -05:00
Lawyered
02711b315b
fix(git-ops): avoid panic truncating unicode commit messages (#401)
* fix(git-ops): avoid panic truncating unicode commit messages

* chore: satisfy rustfmt in git_operations test module

---------

Co-authored-by: Clawyered <clawyered@macbookair.home>
2026-02-17 08:08:57 -05:00
Chummy
ae37e59423
fix(channels): resolve telegram reply target and media delivery (#525)
Co-authored-by: Will Sarg <12886992+willsarg@users.noreply.github.com>
2026-02-17 08:07:23 -05:00
argenis de la rosa
1908af3248 fix(discord): use channel_id instead of sender for replies (fixes #483)
fix(misc): complete parking_lot::Mutex migration (fixes #505)

- DiscordChannel: store actual channel_id in ChannelMessage.channel
  instead of hardcoded "discord" string
- channels/mod.rs: use msg.channel instead of msg.sender for replies
- Migrate all std::sync::Mutex to parking_lot::Mutex:
  * src/security/audit.rs
  * src/memory/sqlite.rs
  * src/memory/response_cache.rs
  * src/memory/lucid.rs
  * src/channels/email_channel.rs
  * src/gateway/mod.rs
  * src/observability/traits.rs
  * src/providers/reliable.rs
  * src/providers/router.rs
  * src/agent/agent.rs
- Remove all .lock().unwrap() and .map_err(PoisonError) patterns
  since parking_lot::Mutex never poisons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 08:05:25 -05:00
Vernon Stinebaker
efa6e5aa4a
feat(channel): add capabilities to system prompt (#531)
* feat(channels): add channel capabilities to system prompt

Add channel capabilities section to system prompt so the agent knows
it can send Discord messages directly without asking permission.
Also reminds agent not to repeat or echo credentials.

Co-authored-by: Vernon Stinebaker <vernon.stinebaker@gmail.com>

* chore: fix formatting and clippy warnings
2026-02-17 08:02:11 -05:00
Vernon Stinebaker
5b5d9fe77f
feat(discord): add mention_only config for @-mention trigger (#529)
When mention_only is true, the bot only responds to messages that
@-mention the bot. Other messages in the guild are silently ignored.
Also strips the bot mention from content before processing.

Co-authored-by: Will Sarg <12886992+willsarg@users.noreply.github.com>
2026-02-17 08:01:27 -05:00
fettpl
a2986db3d6
fix(security): enhance shell redirection blocking in security policy (#521)
* fix(security): enhance shell redirection blocking in security policy

Block process substitution (<(...) and >(...)) and tee command in
is_command_allowed() to close shell escape vectors that bypass existing
redirect and subshell checks.

Closes #514

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

* style: apply rustfmt to providers/mod.rs

Fix pre-existing formatting issue from main.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 07:54:26 -05:00
Lawyered
bc18b8d3c6
fix(memory): harden lucid recall timeout and add cold-start test (#466) 2026-02-17 07:52:11 -05:00
fettpl
87dcd7a7a0
fix(security): expand git argument sanitization (#523)
* fix(security): expand git argument sanitization

Expand sanitize_git_args() blocklist to also reject --pager=, --editor=,
-c (config injection), --no-verify, and > in arguments. Apply validation
to git_add() paths and git_diff() files argument (previously only called
from git_checkout()). The -c check uses exact match to avoid
false-positives on --cached.

Closes #516

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

* style: apply rustfmt to providers/mod.rs

Fix pre-existing formatting issue from main.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 07:51:08 -05:00
fettpl
ac33121f42
fix(security): add config file permission hardening (#524)
* fix(security): add config file permission hardening

Set 0o600 permissions on newly created config.toml files and warn if
an existing config file is world-readable. Prevents accidental exposure
of API keys on multi-user systems. Unix-only (#[cfg(unix)]).

Follows existing pattern from src/security/secrets.rs.

Closes #517

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

* style: apply rustfmt formatting

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 07:45:30 -05:00
fettpl
ebb78afda4
feat(memory): add session_id isolation to Memory trait (#530)
* feat(memory): add session_id isolation to Memory trait

Add optional session_id parameter to store(), recall(), and list()
methods across the Memory trait and all four backends (sqlite, markdown,
lucid, none). This enables per-session memory isolation so different
agent sessions cannot cross-read each other's stored memories.

Changes:
- traits.rs: Add session_id: Option<&str> to store/recall/list
- sqlite.rs: Schema migration (ALTER TABLE ADD COLUMN session_id),
  index, persist/filter by session_id in all query paths
- markdown.rs, lucid.rs, none.rs: Updated signatures
- All callers pass None for backward compatibility
- 5 new tests: session-filtered recall, cross-session isolation,
  session-filtered list, no-filter returns all, migration idempotency

Closes #518

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

* fix(channels): fix discord _channel_id typo and lark missing reply_to

Pre-existing compilation errors on main after reply_to was added to
ChannelMessage: discord.rs used _channel_id (underscore prefix) but
referenced channel_id, and lark.rs was missing the reply_to field.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 07:44:05 -05:00
Chummy
f30f87662e test(email): cover tls smtp default settings 2026-02-17 20:07:23 +08:00
Kieran
212329a2f8 fix: email SmtpTransport::relay expects TLS port not STARTTLS 2026-02-17 20:07:23 +08:00
Chummy
9b465e2940 fix(tools): harden schema cleaner edge cases 2026-02-17 20:02:59 +08:00