Skip to content

v0.15.0

Latest

Choose a tag to compare

@PleasePrompto PleasePrompto released this 14 Mar 13:01

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.
  • /model never 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_minutes runs its own independent asyncio loop
  • enabled: false pauses 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
  • /cron overview now shows the routing target per job
  • enabled: false is 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
  • ProcessRegistry now tracks topic_id for 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.

  • TransportAdapter declares its transport_name ("tg" / "mx")
  • Cascading fallback: target transport unavailable → other transport gets the message with explanation
  • DUCTOR_TRANSPORT env 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.json changes
  • Heartbeat not delivered to topictopic_id now passed as thread_id to 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.json updated with language, image, scene, and heartbeat.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