ductor v0.15.0
Multi-language UI, smart heartbeat system, cron routing, image processing, and a bunch of quality-of-life improvements.
TL;DR
- 7 languages — EN, DE, NL, FR, RU, ES, PT. Switch with one config line, hot-reloadable.
- Heartbeat per group/topic — independent schedules, custom prompts, per-target quiet hours. Each target runs its own loop.
- Cron results route back — jobs remember where they were created and deliver results there, not broadcast.
- Images auto-convert to WebP — resize + compress incoming photos, no more Claude API dimension errors.
/modelnever blocks — switch models while the agent is working.- Seen indicator + technical footer — optional read receipt emoji and token/cost info on every response.
- Transport-aware delivery — Telegram and Matrix messages stay on their transport. Cascading fallback when a transport is unavailable.
Multi-Language Support
Bot UI (commands, status messages, onboarding) is now available in 7 languages.
| Code | Language |
|---|---|
en |
English (default) |
de |
Deutsch |
nl |
Nederlands |
fr |
Français |
ru |
Русский |
es |
Español |
pt |
Português |
{"language": "de"}Hot-reloadable — change without restart.
Heartbeat Per Group/Topic
Heartbeat is no longer limited to private chats. Configure independent heartbeats for any group or forum topic, each with its own prompt, interval, and quiet hours.
{
"heartbeat": {
"enabled": true,
"interval_minutes": 30,
"group_targets": [
{
"enabled": true,
"chat_id": -1001234567890,
"topic_id": 5,
"prompt": "Check server status and report anomalies.",
"interval_minutes": 5,
"quiet_start": 23,
"quiet_end": 7
}
]
}
}- Each target with a custom
interval_minutesruns its own independent asyncio loop enabled: falsepauses a target without removing it- Chat validation with 1-hour TTL cache before firing
- Delivery fallback: if the target chat/topic is gone, the result goes to your main private chat
- The whole heartbeat system can be started/stopped via hot-reload (no restart needed)
Cron Result Routing
Cron jobs now remember where they were created and send results back there.
- Jobs created in a group topic → results go to that topic (UNICAST)
- Jobs without routing context → broadcast to all users (legacy behavior, backward compatible)
- Delivery failure → fallback to main user private chat with explanation
/cronoverview now shows the routing target per jobenabled: falseis now properly respected — disabled jobs don't execute or reschedule
Environment variables DUCTOR_CHAT_ID, DUCTOR_TOPIC_ID, and DUCTOR_TRANSPORT are auto-captured by cron_add.py.
Image Processing
Incoming images are automatically resized and converted to WebP after download. No more Claude API "exceeds dimension limit" errors from phone cameras.
- Max 2000px (configurable), WebP output (configurable), quality 85 (configurable)
- Works across all transports: Telegram, Matrix, API
- Errors are non-fatal — original file used as fallback
- New dependency: Pillow (installed automatically)
{
"image": {
"max_dimension": 2000,
"output_format": "webp",
"quality": 85
}
}/model Never Blocks
The /model selector now opens instantly, even while the agent is processing. The busy-info ("process still running — new model applies to next message") only appears after you actually switch, not when opening the selector.
- Topic-aware: switching in Topic A doesn't affect Topic B
ProcessRegistrynow trackstopic_idfor per-topic busy checks
Seen Indicator & Technical Footer
Two opt-in features, both default off:
- Seen indicator — 👀 emoji reaction on incoming messages (Telegram) or read receipt (Matrix)
- Technical footer — model name, token count, cost, and duration appended to every response
{
"scene": {
"seen_reaction": true,
"technical_footer": true
}
}Both are transport-agnostic (defined in BotProtocol) and hot-reloadable.
Transport-Aware Delivery
The MessageBus now routes UNICAST envelopes to the correct transport only. When Telegram and Matrix run in parallel, a heartbeat for a Telegram group stays on Telegram.
TransportAdapterdeclares itstransport_name("tg"/"mx")- Cascading fallback: target transport unavailable → other transport gets the message with explanation
DUCTOR_TRANSPORTenv var propagated to CLI subprocesses
Tool Activity: Shell Label
Tool activity now shows [TOOL: Shell] instead of [TOOL: Bash] — platform-neutral across Linux, macOS, and Windows. Applied to Telegram, Matrix, and API transports.
Bug Fixes
- Media ignores
group_mention_only(#57) — media messages in groups now respect the config flag - UpdateObserver on all agents (#58) — only the main agent runs the update checker
- Cron reschedule kills running jobs (#56) — executing jobs survive
cron_jobs.jsonchanges - Heartbeat not delivered to topic —
topic_idnow passed asthread_idto Telegram - Technical footer missing in streaming — footer is appended as text segment before finalize
Documentation
- All 13 docs files updated for new features
- RULES files (CLAUDE.md/AGENTS.md/GEMINI.md) updated with all config sections
config.example.jsonupdated withlanguage,image,scene, andheartbeat.group_targets- README updated with language support and feature list
Hot-Reloadable Settings
These settings now take effect within seconds — no restart needed:
model, provider, streaming, heartbeat, cleanup, scene, image, language, allowed_user_ids, allowed_group_ids, group_mention_only