Phase 02: Telegram Integration - 2 plan(s) in 2 wave(s) - Wave 1: persistent subprocess + message utilities (02-01) - Wave 2: bot integration + batching + file handling + systemd (02-02) - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-telegram-integration | 02 | execute | 2 |
|
|
false |
|
Purpose: This plan makes the entire system work end-to-end. Messages flow from Telegram through the batcher to the persistent Claude subprocess, responses come back formatted in MarkdownV2 with smart splitting, and the user sees typing indicators and tool call progress throughout. File attachments land in session folders with auto-analysis. The systemd service ensures reliability across container restarts.
Output: Updated bot.py with full integration, new message_batcher.py, systemd service file, working end-to-end flow.
<execution_context> @/home/mikkel/.claude/get-shit-done/workflows/execute-plan.md @/home/mikkel/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-telegram-integration/02-RESEARCH.md @.planning/phases/02-telegram-integration/02-CONTEXT.md @.planning/phases/02-telegram-integration/02-01-SUMMARY.md @telegram/bot.py @telegram/claude_subprocess.py @telegram/telegram_utils.py @telegram/session_manager.py Task 1: Create MessageBatcher and update bot.py with typing, progress, batching, and file handling telegram/message_batcher.py, telegram/bot.py **Part A: Create telegram/message_batcher.py**Implement MessageBatcher class for debounce-based message batching:
class MessageBatcher:
def __init__(self, callback: Callable, debounce_seconds: float = 2.0):
...
async def add_message(self, message: str):
"""Add message, reset debounce timer. When timer expires, flush batch via callback."""
...
- Uses asyncio.Queue to collect messages
- Cancels previous debounce timer when new message arrives
- After debounce_seconds of silence, joins all queued messages with
\n\nand calls callback - Callback is async (receives combined message string)
- Handles CancelledError gracefully during timer cancellation
- Follow research pattern from 02-RESEARCH.md (MessageBatcher section)
Part B: Update telegram/bot.py — make_callbacks() overhaul
Replace the current make_callbacks() with a new version that uses telegram_utils:
from telegram_utils import split_message_smart, escape_markdown_v2, typing_indicator_loop
from message_batcher import MessageBatcher
New make_callbacks(bot, chat_id) returns dict or tuple of callbacks:
-
on_output(text):
- Split text using
split_message_smart(text) - For each chunk: try sending with
parse_mode='MarkdownV2'afterescape_markdown_v2() - If MarkdownV2 parse fails (Telegram BadRequest), fall back to plain text send
- Stop the typing indicator (set stop_event)
- Split text using
-
on_error(error):
- Send error message to chat (plain text, no MarkdownV2)
- Stop the typing indicator
-
on_complete():
- Stop the typing indicator (set stop_event)
- Log completion
-
on_status(status):
- Send status as a brief message (e.g., "Claude restarted with context preserved")
-
on_tool_use(tool_name, tool_input): (NEW)
- Format tool call notification: extract meaningful target from tool_input
- For Bash tool: show command preview (first 50 chars)
- For Read tool: show file path
- For Edit tool: show file path
- For Grep/Glob: show pattern
- For Write tool: show file path
- Send as a single editable progress message (edit_message_text on a progress message)
- OR send as separate short messages (planner's discretion — separate messages are simpler and more reliable)
- Format: italic text like
_Reading config.json..._
Part C: Update handle_message()
Overhaul the message handler to use typing indicators and message batching:
-
On message received:
- Start typing indicator loop:
stop_typing = asyncio.Event(),asyncio.create_task(typing_indicator_loop(...)) - Pass stop_typing event to callbacks so on_output/on_complete can stop it
- Get or create subprocess (existing logic, but use
start()instead of constructor for persistent process)
- Start typing indicator loop:
-
Message batching:
- Create one
MessageBatcherper session (store in dict alongside subprocesses) - Batcher callback =
subprocess.send_message() - On message:
await batcher.add_message(text)instead of directsubprocess.send_message() - Typing indicator starts immediately on first message, stops on Claude response
- Create one
-
Subprocess auto-start:
- When no subprocess exists for active session, create ClaudeSubprocess and call
await subprocess.start() - Pass all 5 callbacks (on_output, on_error, on_complete, on_status, on_tool_use)
- When no subprocess exists for active session, create ClaudeSubprocess and call
Part D: Update handle_photo() and handle_document()
Save files to active session folder instead of global images/files directories:
-
handle_photo():
- Get active session directory from session_manager
- If no active session, prompt user to create one
- Download highest-quality photo to session directory as
photo_YYYYMMDD_HHMMSS.jpg - Auto-analyze: send message to Claude subprocess: "I've attached a photo: {filename}. {caption or 'Please describe what you see.'}"
- Start typing indicator while Claude analyzes
-
handle_document():
- Get active session directory from session_manager
- If no active session, prompt user to create one
- Download document to session directory with original filename (timestamp prefix for collision avoidance)
- If caption provided: send caption + "The file {filename} has been saved to your session." to Claude
- If no caption: send "User uploaded file: {filename}" to Claude (let Claude infer intent from context, per CONTEXT.md decision)
Part E: Update switch_session_cmd() and archive_session_cmd()
- On session switch: stop typing indicator for current session if running
- On session switch: batcher should flush immediately (don't lose queued messages)
- On archive: terminate subprocess, remove batcher
python -c "from message_batcher import MessageBatcher; print('import OK')"from ~/homelab/telegram/- bot.py imports telegram_utils functions and MessageBatcher without errors
- make_callbacks includes on_tool_use callback
- handle_message uses typing_indicator_loop
- handle_photo saves to session directory (not global images/)
- handle_document saves to session directory (not global files/)
- MessageBatcher has add_message() method MessageBatcher debounces rapid messages with configurable timer. Bot handlers use typing indicators, progress notifications for tool calls, smart message splitting with MarkdownV2, and file handling saves to session directories with auto-analysis.
Service file at ~/.config/systemd/user/telegram-bot.service:
[Unit]
Description=Homelab Telegram Bot
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/mikkel/homelab/telegram
ExecStart=/home/mikkel/venv/bin/python bot.py
Restart=on-failure
RestartSec=10
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=30
# Environment
Environment=PATH=/home/mikkel/.local/bin:/home/mikkel/bin:/usr/local/bin:/usr/bin:/bin
[Install]
WantedBy=default.target
Key settings:
- KillMode=mixed: Sends SIGTERM to main process, SIGKILL to remaining children (ensures Claude subprocesses are cleaned up)
- RestartSec=10: Wait 10s before restart to avoid rapid restart loops
- TimeoutStopSec=30: Give bot time to gracefully terminate subprocesses before force kill
- WorkingDirectory: Set to telegram/ so sibling imports work
After creating the service file:
mkdir -p ~/.config/systemd/user
# Write service file
systemctl --user daemon-reload
systemctl --user enable telegram-bot.service
Do NOT start the service yet (user will start it after verifying manually).
Also ensure loginctl enable-linger is set for the mikkel user (allows user services to run without active login session). Check with loginctl show-user mikkel -p Linger. If not enabled, note it as a requirement but do NOT run the command (requires root).
- Service file exists at ~/.config/systemd/user/telegram-bot.service
systemctl --user cat telegram-bot.serviceshows the service configurationsystemctl --user is-enabled telegram-bot.servicereturns "enabled"- Service file has KillMode=mixed and correct WorkingDirectory
- Check loginctl linger status and report
Systemd user service is created and enabled (not started). Bot can be started with
systemctl --user start telegram-bot.serviceand survives container restarts (with linger enabled). KillMode=mixed ensures Claude subprocesses are cleaned up on stop.
<success_criteria>
- User sends message in Telegram and receives Claude's response formatted in MarkdownV2
- Typing indicator visible for entire processing duration
- Tool call progress notifications appear
- Photos auto-analyzed, documents saved to session
- Long responses split correctly
- Rapid messages batched
- Systemd service configured and enabled
- Bot survives manual restart test </success_criteria>