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>
Thinking/reasoning models (Kimi K2.5, GLM-4.7, DeepSeek-R1) return a
reasoning_content field in assistant messages containing tool calls.
ZeroClaw was silently dropping this field when constructing conversation
history, causing provider APIs to reject follow-up requests with 400
errors: "thinking is enabled but reasoning_content is missing in
assistant tool call message".
Add reasoning_content: Option<String> as an opaque pass-through at every
layer of the pipeline: ChatResponse, ConversationMessage, NativeMessage
structs, parse/convert/build functions, and dispatcher. The field is
skip_serializing_if = None so it is invisible for non-thinking models.
Closes#1327
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
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>
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>
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.
- 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>
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>
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>
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>
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>
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>
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>
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.
- 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
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>
- 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>
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
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>
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>
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>
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
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>
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>
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>