ParseMode is a plain Enum, not a str subclass — string "html" fails
equality check against the enum member and raises ValueError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Markdown (headings, bold, italic, strikethrough, code blocks, links)
is converted to Telegram-compatible HTML tags before sending. Each
reply chunk is wrapped in <blockquote> for visual distinction.
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>
On each startup, if config.json exists alongside config.example.json, any scalar
fields (strings, ints, booleans) present in the example but absent in the running
config are added automatically and the file is rewritten atomically. Existing values
and dict/list sections are never touched. Added fields are logged at INFO level.
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>
Replace premium-only ⏳/✅/❌ with reactions available to all users:
🤔 while processing, 👍 on success, 👎 on error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of sending/editing a verbose status message, now reacts to the
command message: ⏳ while processing, ✅ on success, ❌ on error.
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>
- Remove "Use .cmd for this role" reply when owner uses wrong prefix;
wrong-prefix commands are now silently ignored for all users
- Fix AIImageUnderstandConfig default api_host to api.minimax.chat
(minimax-coding-plan-mcp routes all calls there, not api.minimax.io)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>