* feat(ai): add Grok CLI as a Desktop no-API-key provider
Add grok-cli alongside Codex/Claude Code/Pi, with path/env settings,
isolated MCP-scoped headless runs, model listing, and connection test.
* feat(ai): expose Grok CLI reasoning effort like Codex
Attach low/medium/high effort capabilities to Grok models for the
assistant effort menu, wire resolve_model_effort for grok-cli, and
use the monochrome Grok mark icon.
* i18n: add Grok CLI error messages for remaining locales
Fill grokCli* cliErrors strings in es, it, ja, ko, pt-BR, and zh-TW
to match en/zh-CN and satisfy i18n autofill CI.
* fix(ai): align Grok CLI headless with official Build flags
Address review feedback on PR #5494:
- Use streaming-json + CliAgentJsonlDialect::GrokStreamingJson
- Switch permission to --always-approve and MCPTool(dbx__*) rules
- Prefer --effort over --reasoning-effort; keep --prompt-file
Verified with unit tests and a local headless e2e against grok 1.0.
* style(ai): fix rustfmt for Grok CLI provider files
* test(ai): fix Grok CLI entry in provider ordering spec
* fix(ai): harden grok CLI error wording and config schema
Add 'not signed in' to classify_grok_run_error and test_grok_connection
auth detection — real @xai-official/grok 1.0.0 emits this exact wording
on headless auth failure (verified against the shipped binary).
Remove invalid config.toml fields: permission_mode='always-approve'
(not a valid enum; auto-approve is driven by --always-approve flag) and
startup_timeout_sec/tool_timeout_sec/enabled_tools (not mcp_servers
schema fields per 'grok mcp add').
---------
Co-authored-by: t8y2 <1156263951@qq.com>
The stop button called aiCancelStream -> notify_one(), but the four
stream_*_with_tools SSE loops only `break`ed and returned Ok(token_usage)
when cancelled. run_agent_loop treated that as a normal completed turn and
kept going (often into contract-repair or the next turn), so isGenerating
stayed true and a single click did not stop the agent.
Return Err(AGENT_CANCELLED_ERROR) from the cancel branch so the existing
Err-cancel match arm sets loop_exit = Cancelled and stops the loop cleanly,
preserving whatever was streamed so far.
Additional:
- Promote AGENT_CANCELLED_ERROR to a shared `pub const` in ai.rs; agent_loop
now references ai::AGENT_CANCELLED_ERROR.
- Check cancellation before executing requested tools, so a stop landing
between stream-end and tool execution is honored.
- Wire cancellation into the text-only fallback (previously `_cancelled`,
ignored) via tokio::select! racing ai::complete against cancelled.notified().
Known limitation left: in-flight tool execution (e.g. execute_query) is
still not interruptible mid-call.
GLM-5.2 (and potentially other OpenAI-compatible providers) send id="" on
every tool-call delta after the first, while only the first delta carries
function.name. The streaming parser treated Some("") as a valid id and
emitted ToolCallStart for every chunk; the accumulator's insert then
overwrote the previously-correct name with "", producing "Unknown tool:"
errors and trapping the agent in a retry loop until the 30-turn limit.
Two fixes, mutually reinforcing:
1. OpenAI parser: only treat a non-empty id as a genuine tool-call start
(filter out id=""), so subsequent deltas no longer trigger ToolCallStart.
2. Accumulator: merge ToolCallStart into an existing entry instead of
blind insert — keep the known name when the new one is empty, and
preserve accumulated arguments. Defense in depth for any other provider
that re-sends id or splits name across chunks.
Added a regression test that feeds a re-sent ToolCallStart with an empty
name and asserts the name and arguments survive.
Verified end-to-end with the real GLM-5.2 provider: Unknown tool count
dropped from 12+ to 0, and the agent completed in 3 turns instead of
hitting the 30-turn safety limit.
* fix(ai): resolve Codex CLI from shell PATH on macOS
* fix(ai): hide Codex CLI subprocess windows on Windows
* feat(ai): allow to set environment variable for Codex CLI
* fix(ai): parse Codex MCP tool events
* fix(mcp): honor Postgres TLS settings in direct queries
Replace res.json() with res.text() + fallback JSON parse so that
non-JSON error bodies (e.g. HTTP 502 with empty body) no longer
produce the unclear 'error decoding response body' message.
Also add 502/503/504 to classify_error for proper category prefixes.
Anthropic rejects requests whose system field is an empty string with
"system: text content blocks must be non-empty". The connection test
hardcoded an empty system, and regular completion/stream requests sent
an empty system_prompt when none was configured.
Add a claude_system_prompt() helper that substitutes a non-empty default
for empty or whitespace-only prompts, and apply it to all Claude request
sites. Covered by a unit test.