Commit Graph

1250 Commits

Author SHA1 Message Date
Aleksandr Prilipko 229830ce17 feat(providers): auto-refresh expired Gemini OAuth tokens in warmup
Добавлен автоматический refresh протухших OAuth токенов Gemini при вызове warmup().

## Проблема

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

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

## Решение

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

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

### Тесты

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

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

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

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

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

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

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

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

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

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

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

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

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

Added 3 tests:
- parse_tool_calls_handles_tool_name_fence_format
- parse_tool_calls_handles_tool_name_fence_shell
- parse_tool_calls_handles_multiple_tool_name_fences

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

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

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

Issue: #1391

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

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

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

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

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

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

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

Issue: #1304

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

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

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

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

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

---------

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

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

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

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

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

Fixes #1281.

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

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

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

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

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

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

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

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

Fixes #1282
2026-02-24 16:02:59 +08:00
Chummy 409a74c72b fix(kimi-code): include empty reasoning_content in tool history 2026-02-24 16:02:59 +08:00
Chummy 4a2503605d test(cron): add shell one-shot regression coverage 2026-02-24 16:02:59 +08:00
reidliu41 d6283d2bab fix(cron): set delete_after_run for one-shot shell jobs 2026-02-24 16:02:59 +08:00
Chummy ef8f2fed70 fix(discord): send attachment markers as files/urls 2026-02-24 16:02:59 +08:00
Chummy ce53dcde46 fix(minimax): avoid parsing merged system image markers as vision parts 2026-02-24 16:02:59 +08:00
Chummy c6eb44438b fix(channels): render WhatsApp Web pairing QR in terminal 2026-02-24 16:02:59 +08:00
Chummy f162eede13 fix(config): enforce 0600 on every config save 2026-02-24 16:02:59 +08:00
Chummy 123be02653 fix(slack): bootstrap poll cursor to avoid replay 2026-02-24 16:02:59 +08:00
Chummy 742aa0208f fix(security): honor explicit command paths in allowed_commands 2026-02-24 16:02:59 +08:00
Chummy 36c4e923f1 chore: suppress strict-delta clippy bool-count lint on compatible provider 2026-02-24 15:59:49 +08:00
Chummy 5505465f93 chore: fix lint gate formatting and codex test runtime options 2026-02-24 15:59:49 +08:00
Chummy b3b5055080 feat: replay custom provider api mode, route max_tokens, and lark image support 2026-02-24 15:59:49 +08:00
Chummy 3d5a5c3d3c fix(clippy): satisfy strict delta in websocket url mapping 2026-02-24 15:08:03 +08:00
Chummy 57cbb49d65 fix(fmt): align compatible provider websocket changes 2026-02-24 15:08:03 +08:00
Chummy 666f1a7d10 feat(provider): add responses websocket transport fallback 2026-02-24 15:08:03 +08:00
Chummy ffb5942e60 style(qq): format channel changes 2026-02-24 14:46:42 +08:00
Chummy f72c87dd26 fix(qq): support passive replies and media image send 2026-02-24 14:46:42 +08:00
Chummy 57f8979df1 fix(test): serialize openai codex env variable tests 2026-02-24 14:32:01 +08:00
Chummy 04e5950020 fix(gateway): remove unused websocket sink import 2026-02-24 14:21:34 +08:00
Chummy 68f1ba1617 chore(fmt): normalize gateway import order for webchat fix 2026-02-24 14:21:34 +08:00
Preventnetworkhacking 35a5815513 fix(gateway): enable tool execution in web chat agent
Web chat was calling provider.chat_with_history() directly, bypassing
the agent loop. Tool calls were rendered as raw XML instead of executing.

Changes:
- Add tools_registry_exec to AppState for executable tools
- Replace chat_with_history with run_tool_call_loop in ws.rs
- Maintain conversation history per WebSocket session
- Add multimodal and max_tool_iterations config to AppState

Closes #1524
2026-02-24 14:21:34 +08:00
Chummy fb95fc61a0 fix(browser): harden rust_native interactability for click/fill/type 2026-02-24 14:12:08 +08:00
Chummy 0377a35811 chore(fmt): fix loop_ test formatting after #1505 2026-02-24 13:51:43 +08:00
Chummy 8ab75fdda9 test: add regression coverage for provider parser cron and telegram 2026-02-24 13:45:13 +08:00
Chummy 15b54670ff fix: improve tool-call parsing and shell expansion checks 2026-02-24 13:45:13 +08:00
Preventnetworkhacking 82c7fe8d8b fix(telegram): populate thread_ts for per-topic session isolation
When a Telegram message originates from a forum topic, the thread_id was
extracted and used for reply routing but never stored in ChannelMessage.thread_ts.
This caused all messages from the same sender to share conversation history
regardless of which topic they were posted in.

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

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

Closes #1532
2026-02-24 13:40:04 +08:00
Chummy ace493b32f chore(fmt): format gateway api after dashboard-save fix 2026-02-24 13:30:43 +08:00
argenis de la rosa 9751433803 fix(gateway): preserve masked config values on dashboard save
Replace line-based TOML masking with structured config masking so secret fields keep their original types (including reliability.api_keys arrays).\nHydrate dashboard PUT payloads with runtime config_path/workspace_dir and restore masked secret placeholders from current config before validation/save.\nAlso allow GET on /api/doctor for dashboard/client compatibility to avoid 405 responses.
2026-02-24 13:22:07 +08:00