* 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.
* feat(ai): add task-oriented Agent actions with per-action contract
Introduce three Agent-native actions — query, exploreSchema,
executeAndExplain — so Agent mode surfaces task-oriented operations
instead of reusing Ask's SQL-producing action set.
- ai.ts: extend AiAction with the new actions; add ASK_ACTIONS /
AGENT_ACTIONS and defaultActionForMode / isValidActionForMode
helpers; raise maxTokens for the explanatory task actions.
- aiSkills.ts: add skill definitions (zh/en) for the three actions.
- aiAgentPlan.ts: task actions no longer emit a misleading
"unsupported_action" execute step; they execute via the
execute_query tool events, not the legacy client-side path.
- agent_loop.rs: per-action task-contract rules in both the prompt
augmentation and repair paths (query -> execute & answer from real
data; exploreSchema -> list_tables/get_columns; executeAndExplain
-> run current SQL & explain).
- i18n (en/zh-CN/zh-TW): new action labels, placeholders, a
generateNoExec label for Agent mode, and a task-oriented agent
modeHint.
es/it/ja/pt-BR have no `ai` section and fall back to `en`, matching
existing coverage.
* feat(ai): split Ask/Agent action menus with mode-aware defaults
Wire the new Agent task actions into the assistant UI so each mode
shows a distinct, intent-matching action set.
- actionButtons is now a computed returning Ask or Agent buttons
based on assistantMode. Agent menu: query / exploreSchema /
executeAndExplain / generate (labeled "生成但不执行" via
generateNoExec). Ask menu is unchanged.
- Switching mode lands on that mode's default (Ask -> generate,
Agent -> query); the shared `generate` is not carried across
because its label/semantics differ per mode. Vector DBs stay on
`generate` since their action menu is hidden and they lack SQL
tools (no execute_query contract).
- External Ask-style triggers (Fix with AI, Explain history) switch
to Ask mode when invoked from Agent mode so the action is valid
and the menu reflects what runs.
- Reset the active action to the mode default after each send.
* test(ai): cover Agent task-action contract and mode/action mapping
Lock in the deterministic parts of the Ask/Agent split so only the
LLM-behavior criteria need manual verification.
Backend (agent_loop.rs):
- query contract instructs the LLM to call execute_query and is not
treated as a SQL-producing action.
- exploreSchema contract points to list_tables/get_columns and says
not to execute data queries.
- executeAndExplain contract tells the LLM to run the current SQL.
- task actions do not require a SQL deliverable (a conclusion without
a fenced SQL block still satisfies the contract).
- query repair prompt also targets execute_query.
Frontend (aiActions.spec.ts):
- defaultActionForMode: Ask -> generate, Agent -> query (not generate).
- isValidActionForMode: generate valid in both; Ask-only actions
rejected in Agent mode and vice versa.
- ASK_ACTIONS / AGENT_ACTIONS composition (Agent menu starts with
query and still offers generate).
* fix(ai): correct action menu on mode-switched triggers and i18n gaps
Issues found during review of the Ask/Agent action split:
- [#I01] triggerAction (Fix with AI / Explain history) invoked from
Agent mode switched to Ask and set the action, but the mode-switch
watch then reset activeAction to the Ask default ("generate"),
overwriting the action just set — so the menu showed "Generate SQL"
during a fix. Add a suppressModeActionReset flag so programmatic
mode switches don't clobber the explicitly-set action.
- [#C01] Remove dead `unsupported_action` reason: buildAiAgentPlan no
longer produces it, so drop the type variant and the unreachable
skippedTitleKey case.
- [#I02] es/it/ja/pt-BR were missing the new Agent action keys
(query/exploreSchema/executeAndExplain/generateNoExec) and their
placeholders, causing English fallback in the Agent menu. Add the
translations to match each locale's existing style.
* test(ai): align app-tests with task-action split
- aiSkills: add query/exploreSchema/executeAndExplain to the expected
action set; broaden the id check (new ids are not _sql-suffixed) and
relax the userInstruction assertion (schema inspection need not mention SQL).
- aiAgentPlan: drop the unsupported_action skipped step for non-generate
actions (#C01 removed it from the plan and the reason union); add coverage
for task-oriented Agent actions not driving client-side execution.
* test(ai): pin locale in dialect prompt spec
buildSystemPrompt selects zh/en copy via currentLocale(), which reflects the
host navigator.language. The spec asserts English strings, so it failed on
zh-CN machines (e.g. Windows with a Chinese system locale) while passing on
CI's en-US runners. Pin the locale to en in beforeAll so the assertions are
deterministic regardless of the host OS locale.
* fix(ai): merge tool call status cards
* fix(ai): constrain markdown table overflow
* fix(ai): restore streaming of answer text via TextDelta events
The on_chunk closure was missing live AgentEvent::TextDelta emission for text content, causing the AI answer text to appear all at once after the turn completed. Reasoning deltas were unaffected.
Root cause: commit c30033628 rewrote the on_chunk closure and accidentally dropped the TextDelta emit line while keeping ReasoningDelta intact.
Fix: restore TextDelta emit in on_chunk for incremental streaming, and remove the post-turn full-TextDelta workaround which would duplicate content.
* test(ai): extract chunk_to_events and add streaming event tests
Extract the event-generation logic from the on_chunk closure into a pure function chunk_to_events() to make it testable, and add 6 unit tests covering all chunk content combinations: text-only, reasoning-only, mixed, empty, and edge cases.
This ensures a regression like the one fixed in the previous commit (where TextDelta emission was dropped while ReasoningDelta was kept) would be caught by tests.
- Add explain_query tool with structured EXPLAIN result, gated on supports_explain_plan()
- Add supports_sql_query() to gate execute_query/get_sample_data for non-SQL DBs (Redis, ES, MongoDB, etc.)
- Split agent loop tool dispatch: execute_query runs sequentially, all others run in parallel via join_all
- Wire explain_query result button to ExplainPlanViewer in AiAssistant + App.vue
- Fix O(n²) agentSteps rebuild: incremental O(1) push per event during streaming
- finally block falls back to full rebuild only when streaming push did not occur
Move tool calling from non-streaming (complete) API calls to real
streaming SSE, so text, reasoning, and tool call arguments all arrive
incrementally — users see the AI think and act in real time.
- Extract ~370 lines of non-streaming tool-call functions from
agent_loop.rs (call_with_tools, call_openai_with_tools,
call_claude_with_tools, call_gemini_with_tools) and replace with
a single ai::stream_with_tools() dispatch call.
- Add streaming-with-tools infrastructure to ai.rs (~550 lines):
- StreamToolEvent enum: Chunk, ToolCallStart, ToolCallDelta,
ToolCallComplete — provider-agnostic, feeds into the
StreamingToolCallAccumulator
- stream_claude_with_tools(): parse SSE content_block_start/delta/
stop events, incremental input_json_delta for tool arguments
- stream_openai_with_tools(): parse SSE delta.tool_calls with
incremental function.arguments
- stream_gemini_with_tools(): parse SSE functionCall (Gemini
sends complete objects, not deltas — emit as one chunk)
- Cancellation support via tokio::select! in every streaming loop
- Extended timeout for reasoning models (600s vs 120s)
- StreamingToolCallAccumulator: collect partial tool call fragments
from streaming deltas, then deserialize into complete ToolCall
objects in index order once the stream ends.