- blockquote expandable: replies are collapsed by default in Telegram
- strip <think>...</think> sections (chain-of-thought from reasoning
models like DeepSeek R1 / QwQ) before sending the response
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The workspace root matched every relative path, so prompts/ was never reached,
and paths like workspace/notes.txt landed at workspace/workspace/notes.txt.
Fix: _resolve_sandboxed_path now treats allowed_roots[0] as the resolution base
(project root) and allowed_roots[1:] as permitted subdirectories. _get_allowed_roots
returns (project_root, workspace_dir, prompts_root). Paths are now correctly routed:
workspace/notes.txt -> workspace/notes.txt, prompts/ai/parts/x.md -> prompts/ai/parts/x.md.
app/ and other project dirs remain blocked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Configure via ai.prompt.user_template in config.json using {text}, {full_name},
{user_id}, {first_name}, {last_name}, {username} placeholders. Applied to both
history user messages and the current prompt. No-op when unset.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AIService.generate() now runs an agentic loop: sends tools to the API,
handles tool_calls responses, executes tools, appends results, and repeats
until a text response (up to tool_max_rounds, default 10)
- New app/ai/tools.py: six sandboxed file tools (write/read/create/modify/delete/list),
OpenAI JSON schemas, path sandbox validator, and MCP→OpenAI schema converter
- Paths are sandboxed to workspace/ (project root) and prompts/ai/; traversal blocked
- MinimaxImageUnderstandClient gains list_tools() and call_tool() methods; MCP tool
schemas are lazily fetched once per session and forwarded to the AI as callable tools
- AIConfig gains workspace_dir (Path, default workspace/) and tool_max_rounds (int, default 10)
- MCP client opened lazily only when an MCP tool is actually called during a loop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Some providers (e.g. Ollama) may return response bytes that aren't valid
UTF-8. json.loads() raises UnicodeDecodeError in that case. Add an httpx
response event hook that replaces invalid bytes before the SDK parses the
response body.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>