    async def _handle_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /start command."""
        user = update.effective_user
        chat_id = update.effective_chat.id

        # Store owner chat_id for startup notifications
        try:
            redis_client.set("genesis:telegram:owner_chat_id", str(chat_id))
            redis_client.set(f"genesis:telegram:user:{user.id}:chat_id", str(chat_id))
            logger.info(f"Stored owner chat_id: {chat_id}")
        except Exception as e:
            logger.warning(f"Could not store chat_id: {e}")

        await context.bot.send_message(
            chat_id=chat_id,
            text=(
                f"Greetings, {user.first_name}. I am ClawdBot, the Genesis Interface Agent.\n\n"
                "I can receive:\n"
                "- Voice messages (forwarded to Genesis)\n"
                "- Text commands\n"
                "- Screenshots\n\n"
                "**Quick Start**: Type /call to start a voice conversation.\n\n"
                "Use /help for all commands."
            ),
            parse_mode='Markdown'
        )

        # Log connection
        self._publish_observation("system", {
            "event": "telegram_user_connected",
            "user_id": user.id,
            "username": user.username,
            "first_name": user.first_name,
            "chat_id": chat_id
        })

"""
ClawdBot Telegram Skill
========================

Non-blocking Telegram bot integration for Genesis system.

Features:
- Async-compatible with main event loop
- Voice note reception and forwarding
- Text message handling
- Graceful shutdown support

Usage:
    The TelegramSkill is initialized by ClawdBot main.py if TELEGRAM_BOT_TOKEN
    is set in the environment. Messages are forwarded to Genesis via Redis.
"""

import logging
import os
import asyncio
from datetime import datetime, timezone
from typing import Optional, Dict, Any
from telegram import Update
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler,
    MessageHandler,
    filters
)

# Use the new enterprise Redis client
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ClawdBot.redis_client_v2 import redis_client

logger = logging.getLogger("ClawdBotTelegram")

CH_OBSERVATIONS = "genesis:observations"
CH_COMMANDS = "genesis:commands"

class TelegramSkill:
    """
    Non-blocking Telegram bot skill for ClawdBot.

    Key improvements over previous version:
    - Uses async polling that doesn't block the event loop
    - Proper graceful shutdown handling
    - Uses enterprise Redis client (v2)
    - Better error handling and logging
    """

    def __init__(self):
        self.token = os.getenv("TELEGRAM_BOT_TOKEN")
        self.app = None
        self._running = False

        if not self.token:
            logger.warning("TELEGRAM_BOT_TOKEN not found. Telegram Skill disabled.")
            return

        try:
            self.app = ApplicationBuilder().token(self.token).build()
            self._setup_handlers()
            logger.info("Telegram Skill initialized")
        except Exception as e:
            logger.error(f"Failed to initialize Telegram: {e}")
            self.app = None

    def _setup_handlers(self):
        """Register message handlers."""
        if not self.app:
            return

        # Command handlers
        self.app.add_handler(CommandHandler('start', self._handle_start))
        self.app.add_handler(CommandHandler('status', self._handle_status))
        self.app.add_handler(CommandHandler('help', self._handle_help))
        self.app.add_handler(CommandHandler('call', self._handle_call))

        # Message handlers
        self.app.add_handler(MessageHandler(filters.VOICE, self._handle_voice))
        self.app.add_handler(MessageHandler(filters.AUDIO, self._handle_audio))
        self.app.add_handler(MessageHandler(filters.PHOTO, self._handle_photo))
        self.app.add_handler(MessageHandler(
            filters.TEXT & (~filters.COMMAND),
            self._handle_text
        ))

        # Error handler
        self.app.add_error_handler(self._handle_error)

    async def _handle_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /start command."""
        user = update.effective_user
        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text=(
                f"Greetings, {user.first_name}. I am ClawdBot, the Genesis Interface Agent.\n\n"
                "I can receive:\n"
                "- Voice messages (forwarded to Genesis)\n"
                "- Text commands\n"
                "- Screenshots\n\n"
                "Use /help for available commands."
            )
        )

        # Log connection
        self._publish_observation("system", {
            "event": "telegram_user_connected",
            "user_id": user.id,
            "username": user.username,
            "first_name": user.first_name
        })

    async def _handle_status(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /status command."""
        # Check Redis connection
        redis_ok = redis_client.is_connected()

        status_text = (
            "**ClawdBot Status**\n"
            f"- Redis: {'Connected' if redis_ok else 'Disconnected'}\n"
            f"- Telegram: Connected\n"
            f"- Time: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}"
        )

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text=status_text,
            parse_mode='Markdown'
        )

    async def _handle_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /help command."""
        help_text = (
            "**ClawdBot Commands**\n\n"
            "/start - Initialize connection\n"
            "/status - Check system status\n"
            "/call - Start voice conversation\n"
            "/help - Show this help\n\n"
            "**Message Types**\n"
            "- Send voice notes for processing\n"
            "- Send text messages as commands\n"
            "- Send photos for analysis"
        )

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text=help_text,
            parse_mode='Markdown'
        )

    async def _handle_call(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /call command - initiate voice conversation."""
        user = update.effective_user
        chat_id = update.effective_chat.id

        logger.info(f"Voice call requested by {user.first_name}")

        # Store chat_id for voice bridge to use
        try:
            redis_client.set(f"genesis:voice:active_chat:{user.id}", str(chat_id), ex=3600)
        except Exception as e:
            logger.warning(f"Could not store chat_id: {e}")

        # Notify voice bridge that user wants to talk
        self._publish_observation("call_request", {
            "user_id": user.id,
            "username": user.username,
            "first_name": user.first_name,
            "chat_id": chat_id
        })

        await context.bot.send_message(
            chat_id=chat_id,
            text=(
                "**Voice Mode Activated**\n\n"
                "I'm ready to talk. Just send me a voice message and I'll respond with voice.\n\n"
                "Tips:\n"
                "- Hold the mic button to record\n"
                "- Speak naturally, I understand context\n"
                "- Say 'text mode' if you want text replies instead"
            ),
            parse_mode='Markdown'
        )

    async def _handle_voice(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle voice notes."""
        voice_file = await update.message.voice.get_file()
        user = update.effective_user

        logger.info(f"Received voice note from {user.first_name} ({voice_file.file_size} bytes)")

        # Download file locally
        try:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            save_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "voice_input")
            os.makedirs(save_dir, exist_ok=True)

            file_path = os.path.join(save_dir, f"voice_{user.id}_{timestamp}.ogg")
            await voice_file.download_to_drive(file_path)

            # Update observation with file path
            self._publish_observation("voice_note", {
                "user_id": user.id,
                "username": user.username,
                "first_name": user.first_name,
                "file_path": file_path,  # Critical for processing
                "file_id": voice_file.file_id,
                "duration": update.message.voice.duration,
                "chat_id": update.effective_chat.id
            })

            logger.info(f"Voice note saved to {file_path}")

        except Exception as e:
            logger.error(f"Failed to download voice note: {e}")

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text="Voice note received. Transmitting to Genesis Cortex..."
        )

    async def _handle_audio(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle audio files."""
        audio = update.message.audio
        audio_file = await audio.get_file()
        user = update.effective_user

        logger.info(f"Received audio from {user.first_name}: {audio.title or 'Untitled'}")

        self._publish_observation("audio", {
            "user_id": user.id,
            "first_name": user.first_name,
            "file_id": audio_file.file_id,
            "title": audio.title,
            "performer": audio.performer,
            "duration": audio.duration,
            "mime_type": audio.mime_type
        })

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text="Audio received and forwarded to Genesis."
        )

    async def _handle_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle photos."""
        # Get the largest photo
        photo = update.message.photo[-1]
        photo_file = await photo.get_file()
        user = update.effective_user

        logger.info(f"Received photo from {user.first_name} ({photo.width}x{photo.height})")

        self._publish_observation("photo", {
            "user_id": user.id,
            "first_name": user.first_name,
            "file_id": photo_file.file_id,
            "width": photo.width,
            "height": photo.height,
            "file_size": photo.file_size
        })

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text="Photo received and forwarded for analysis."
        )

    async def _handle_text(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle text messages."""
        user = update.effective_user
        text = update.message.text

        logger.info(f"Received text from {user.first_name}: {text[:50]}...")

        self._publish_observation("text_message", {
            "user_id": user.id,
            "username": user.username,
            "first_name": user.first_name,
            "text": text,
            "chat_id": update.effective_chat.id
        })

        # Check if it looks like a command for Genesis
        if text.startswith("!") or text.startswith("/genesis"):
            await context.bot.send_message(
                chat_id=update.effective_chat.id,
                text="Command forwarded to Genesis orchestrator."
            )
        else:
            await context.bot.send_message(
                chat_id=update.effective_chat.id,
                text="Message received and logged."
            )

    async def _handle_error(self, update: object, context: ContextTypes.DEFAULT_TYPE):
        """Handle errors."""
        logger.error(f"Telegram error: {context.error}")

        self._publish_observation("error", {
            "error": str(context.error),
            "update": str(update) if update else None
        })

    def _publish_observation(self, obs_type: str, data: dict):
        """Publish observation to Redis."""
        try:
            redis_client.publish(CH_OBSERVATIONS, {
                "source": "telegram",
                "type": obs_type,
                "timestamp": datetime.now(timezone.utc).isoformat(),
                **data
            })
        except Exception as e:
            logger.error(f"Failed to publish observation: {e}")

    async def start_async(self):
        """
        Start the Telegram bot in a non-blocking way.

        This method initializes and starts polling without blocking
        the main asyncio event loop.
        """
        if not self.app:
            logger.warning("Telegram app not initialized, cannot start")
            return

        self._running = True

        try:
            await self.app.initialize()
            await self.app.start()
            await self.app.updater.start_polling(
                allowed_updates=Update.ALL_TYPES,
                drop_pending_updates=True
            )
            logger.info("Telegram bot polling started (non-blocking)")
        except Exception as e:
            logger.error(f"Failed to start Telegram polling: {e}")
            self._running = False

    async def stop_async(self):
        """Stop the Telegram bot gracefully."""
        if not self.app or not self._running:
            return

        self._running = False

        try:
            await self.app.updater.stop()
            await self.app.stop()
            await self.app.shutdown()
            logger.info("Telegram bot stopped gracefully")
        except Exception as e:
            logger.error(f"Error stopping Telegram: {e}")

    def run(self):
        """
        Blocking run for standalone execution.

        Note: This blocks the event loop. Use start_async() for
        integration with ClawdBot main event loop.
        """
        if not self.app:
            logger.error("Cannot run - Telegram app not initialized")
            return

        logger.info("Starting Telegram Bot Polling (blocking mode)...")
        self.app.run_polling()

    @property
    def is_running(self) -> bool:
        """Check if bot is running."""
        return self._running

    async def execute(self, action: str, args: Dict[str, Any]):
        """
        Execute a command from the Skills Manager.

        Actions:
        - send_message: Send a text message
        - send_voice: Send a voice note (OGG/MP3)
        """
        if not self.app:
            logger.warning("Telegram not initialized, cannot execute command")
            return

        chat_id = args.get("chat_id")
        if not chat_id:
            # Try to get last chat_id from Redis or default?
            # For now, require chat_id.
            logger.error("Missing chat_id for Telegram command")
            return

        try:
            if action == "send_message":
                text = args.get("text")
                if text:
                    await self.app.bot.send_message(chat_id=chat_id, text=text)
                    logger.info(f"Sent message to {chat_id}: {text[:20]}...")

            elif action == "send_voice":
                file_path = args.get("file_path")
                if file_path and os.path.exists(file_path):
                    with open(file_path, 'rb') as voice:
                        await self.app.bot.send_voice(chat_id=chat_id, voice=voice)
                    logger.info(f"Sent voice note to {chat_id}: {file_path}")
                else:
                    logger.error(f"Voice file not found: {file_path}")

            else:
                logger.warning(f"Unknown Telegram action: {action}")

        except Exception as e:
            logger.error(f"Failed to execute Telegram action {action}: {e}")

# Standalone runner for testing
if __name__ == "__main__":
    from dotenv import load_dotenv
    load_dotenv()

    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )

    skill = TelegramSkill()
    if skill.app:
        skill.run()
    else:
        print("Failed to initialize Telegram skill. Check TELEGRAM_BOT_TOKEN.")

+        chat_id = update.effective_chat.id

+        # Store owner chat_id for startup notifications

+            redis_client.set("genesis:telegram:owner_chat_id", str(chat_id))

+            redis_client.set(f"genesis:telegram:user:{user.id}:chat_id", str(chat_id))

+            logger.info(f"Stored owner chat_id: {chat_id}")

+            logger.warning(f"Could not store chat_id: {e}")

-            chat_id=update.effective_chat.id,

-                "Use /help for available commands."

+                "**Quick Start**: Type /call to start a voice conversation.\n\n"

+                "Use /help for all commands."

-            "first_name": user.first_name

+            "first_name": user.first_name,


Now add a method to notify the owner when the bot starts, and call it from `start_async`:


Edit

    async def start_async(self):
        """
        Start the Telegram bot in a non-blocking way.

        This method initializes and starts polling without blocking
        the main asyncio event loop.
        """
        if not self.app:
            logger.warning("Telegram app not initialized, cannot start")
            return

        self._running = True

        try:
            await self.app.initialize()
            await self.app.start()
            await self.app.updater.start_polling(
                allowed_updates=Update.ALL_TYPES,
                drop_pending_updates=True
            )
            logger.info("Telegram bot polling started (non-blocking)")
        except Exception as e:
            logger.error(f"Failed to start Telegram polling: {e}")
            self._running = False

    async def start_async(self):
        """
        Start the Telegram bot in a non-blocking way.

        This method initializes and starts polling without blocking
        the main asyncio event loop.
        """
        if not self.app:
            logger.warning("Telegram app not initialized, cannot start")
            return

        self._running = True

        try:
            await self.app.initialize()
            await self.app.start()
            await self.app.updater.start_polling(
                allowed_updates=Update.ALL_TYPES,
                drop_pending_updates=True
            )
            logger.info("Telegram bot polling started (non-blocking)")

            # Notify owner that bot is online
            await self._notify_owner_online()

        except Exception as e:
            logger.error(f"Failed to start Telegram polling: {e}")
            self._running = False

    async def _notify_owner_online(self):
        """Send startup notification to owner."""
        try:
            owner_chat_id = redis_client.get("genesis:telegram:owner_chat_id")
            if owner_chat_id:
                await self.app.bot.send_message(
                    chat_id=int(owner_chat_id),
                    text=(
                        "**ClawdBot Online**\n\n"
                        "Genesis Voice Bridge is ready.\n"
                        "Type /call to start talking."
                    ),
                    parse_mode='Markdown'
                )
                logger.info(f"Sent online notification to {owner_chat_id}")
        except Exception as e:
            logger.warning(f"Could not send startup notification: {e}")


The file E:\genesis-system\ClawdBot\skills\telegram_bot.py has been updated successfully.

    async def start_async(self):
        """
        Start the Telegram bot in a non-blocking way.

        This method initializes and starts polling without blocking
        the main asyncio event loop.
        """
        if not self.app:
            logger.warning("Telegram app not initialized, cannot start")
            return

        self._running = True

        try:
            await self.app.initialize()
            await self.app.start()
            await self.app.updater.start_polling(
                allowed_updates=Update.ALL_TYPES,
                drop_pending_updates=True
            )
            logger.info("Telegram bot polling started (non-blocking)")

            # Notify owner that bot is online
            await self._notify_owner_online()

        except Exception as e:
            logger.error(f"Failed to start Telegram polling: {e}")
            self._running = False

    async def _notify_owner_online(self):
        """Send startup notification to owner."""
        try:
            owner_chat_id = redis_client.get("genesis:telegram:owner_chat_id")
            if owner_chat_id:
                await self.app.bot.send_message(
                    chat_id=int(owner_chat_id),
                    text=(
                        "**ClawdBot Online**\n\n"
                        "Genesis Voice Bridge is ready.\n"
                        "Type /call to start talking."
                    ),
                    parse_mode='Markdown'
                )
                logger.info(f"Sent online notification to {owner_chat_id}")
        except Exception as e:
            logger.warning(f"Could not send startup notification: {e}")

"""
ClawdBot Telegram Skill
========================

Non-blocking Telegram bot integration for Genesis system.

Features:
- Async-compatible with main event loop
- Voice note reception and forwarding
- Text message handling
- Graceful shutdown support

Usage:
    The TelegramSkill is initialized by ClawdBot main.py if TELEGRAM_BOT_TOKEN
    is set in the environment. Messages are forwarded to Genesis via Redis.
"""

import logging
import os
import asyncio
from datetime import datetime, timezone
from typing import Optional, Dict, Any
from telegram import Update
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler,
    MessageHandler,
    filters
)

# Use the new enterprise Redis client
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ClawdBot.redis_client_v2 import redis_client

logger = logging.getLogger("ClawdBotTelegram")

CH_OBSERVATIONS = "genesis:observations"
CH_COMMANDS = "genesis:commands"

class TelegramSkill:
    """
    Non-blocking Telegram bot skill for ClawdBot.

    Key improvements over previous version:
    - Uses async polling that doesn't block the event loop
    - Proper graceful shutdown handling
    - Uses enterprise Redis client (v2)
    - Better error handling and logging
    """

    def __init__(self):
        self.token = os.getenv("TELEGRAM_BOT_TOKEN")
        self.app = None
        self._running = False

        if not self.token:
            logger.warning("TELEGRAM_BOT_TOKEN not found. Telegram Skill disabled.")
            return

        try:
            self.app = ApplicationBuilder().token(self.token).build()
            self._setup_handlers()
            logger.info("Telegram Skill initialized")
        except Exception as e:
            logger.error(f"Failed to initialize Telegram: {e}")
            self.app = None

    def _setup_handlers(self):
        """Register message handlers."""
        if not self.app:
            return

        # Command handlers
        self.app.add_handler(CommandHandler('start', self._handle_start))
        self.app.add_handler(CommandHandler('status', self._handle_status))
        self.app.add_handler(CommandHandler('help', self._handle_help))
        self.app.add_handler(CommandHandler('call', self._handle_call))

        # Message handlers
        self.app.add_handler(MessageHandler(filters.VOICE, self._handle_voice))
        self.app.add_handler(MessageHandler(filters.AUDIO, self._handle_audio))
        self.app.add_handler(MessageHandler(filters.PHOTO, self._handle_photo))
        self.app.add_handler(MessageHandler(
            filters.TEXT & (~filters.COMMAND),
            self._handle_text
        ))

        # Error handler
        self.app.add_error_handler(self._handle_error)

    async def _handle_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /start command."""
        user = update.effective_user
        chat_id = update.effective_chat.id

        # Store owner chat_id for startup notifications
        try:
            redis_client.set("genesis:telegram:owner_chat_id", str(chat_id))
            redis_client.set(f"genesis:telegram:user:{user.id}:chat_id", str(chat_id))
            logger.info(f"Stored owner chat_id: {chat_id}")
        except Exception as e:
            logger.warning(f"Could not store chat_id: {e}")

        await context.bot.send_message(
            chat_id=chat_id,
            text=(
                f"Greetings, {user.first_name}. I am ClawdBot, the Genesis Interface Agent.\n\n"
                "I can receive:\n"
                "- Voice messages (forwarded to Genesis)\n"
                "- Text commands\n"
                "- Screenshots\n\n"
                "**Quick Start**: Type /call to start a voice conversation.\n\n"
                "Use /help for all commands."
            ),
            parse_mode='Markdown'
        )

        # Log connection
        self._publish_observation("system", {
            "event": "telegram_user_connected",
            "user_id": user.id,
            "username": user.username,
            "first_name": user.first_name,
            "chat_id": chat_id
        })

    async def _handle_status(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /status command."""
        # Check Redis connection
        redis_ok = redis_client.is_connected()

        status_text = (
            "**ClawdBot Status**\n"
            f"- Redis: {'Connected' if redis_ok else 'Disconnected'}\n"
            f"- Telegram: Connected\n"
            f"- Time: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}"
        )

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text=status_text,
            parse_mode='Markdown'
        )

    async def _handle_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /help command."""
        help_text = (
            "**ClawdBot Commands**\n\n"
            "/start - Initialize connection\n"
            "/status - Check system status\n"
            "/call - Start voice conversation\n"
            "/help - Show this help\n\n"
            "**Message Types**\n"
            "- Send voice notes for processing\n"
            "- Send text messages as commands\n"
            "- Send photos for analysis"
        )

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text=help_text,
            parse_mode='Markdown'
        )

    async def _handle_call(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle /call command - initiate voice conversation."""
        user = update.effective_user
        chat_id = update.effective_chat.id

        logger.info(f"Voice call requested by {user.first_name}")

        # Store chat_id for voice bridge to use
        try:
            redis_client.set(f"genesis:voice:active_chat:{user.id}", str(chat_id), ex=3600)
        except Exception as e:
            logger.warning(f"Could not store chat_id: {e}")

        # Notify voice bridge that user wants to talk
        self._publish_observation("call_request", {
            "user_id": user.id,
            "username": user.username,
            "first_name": user.first_name,
            "chat_id": chat_id
        })

        await context.bot.send_message(
            chat_id=chat_id,
            text=(
                "**Voice Mode Activated**\n\n"
                "I'm ready to talk. Just send me a voice message and I'll respond with voice.\n\n"
                "Tips:\n"
                "- Hold the mic button to record\n"
                "- Speak naturally, I understand context\n"
                "- Say 'text mode' if you want text replies instead"
            ),
            parse_mode='Markdown'
        )

    async def _handle_voice(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle voice notes."""
        voice_file = await update.message.voice.get_file()
        user = update.effective_user

        logger.info(f"Received voice note from {user.first_name} ({voice_file.file_size} bytes)")

        # Download file locally
        try:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            save_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "voice_input")
            os.makedirs(save_dir, exist_ok=True)

            file_path = os.path.join(save_dir, f"voice_{user.id}_{timestamp}.ogg")
            await voice_file.download_to_drive(file_path)

            # Update observation with file path
            self._publish_observation("voice_note", {
                "user_id": user.id,
                "username": user.username,
                "first_name": user.first_name,
                "file_path": file_path,  # Critical for processing
                "file_id": voice_file.file_id,
                "duration": update.message.voice.duration,
                "chat_id": update.effective_chat.id
            })

            logger.info(f"Voice note saved to {file_path}")

        except Exception as e:
            logger.error(f"Failed to download voice note: {e}")

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text="Voice note received. Transmitting to Genesis Cortex..."
        )

    async def _handle_audio(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle audio files."""
        audio = update.message.audio
        audio_file = await audio.get_file()
        user = update.effective_user

        logger.info(f"Received audio from {user.first_name}: {audio.title or 'Untitled'}")

        self._publish_observation("audio", {
            "user_id": user.id,
            "first_name": user.first_name,
            "file_id": audio_file.file_id,
            "title": audio.title,
            "performer": audio.performer,
            "duration": audio.duration,
            "mime_type": audio.mime_type
        })

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text="Audio received and forwarded to Genesis."
        )

    async def _handle_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle photos."""
        # Get the largest photo
        photo = update.message.photo[-1]
        photo_file = await photo.get_file()
        user = update.effective_user

        logger.info(f"Received photo from {user.first_name} ({photo.width}x{photo.height})")

        self._publish_observation("photo", {
            "user_id": user.id,
            "first_name": user.first_name,
            "file_id": photo_file.file_id,
            "width": photo.width,
            "height": photo.height,
            "file_size": photo.file_size
        })

        await context.bot.send_message(
            chat_id=update.effective_chat.id,
            text="Photo received and forwarded for analysis."
        )

    async def _handle_text(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle text messages."""
        user = update.effective_user
        text = update.message.text

        logger.info(f"Received text from {user.first_name}: {text[:50]}...")

        self._publish_observation("text_message", {
            "user_id": user.id,
            "username": user.username,
            "first_name": user.first_name,
            "text": text,
            "chat_id": update.effective_chat.id
        })

        # Check if it looks like a command for Genesis
        if text.startswith("!") or text.startswith("/genesis"):
            await context.bot.send_message(
                chat_id=update.effective_chat.id,
                text="Command forwarded to Genesis orchestrator."
            )
        else:
            await context.bot.send_message(
                chat_id=update.effective_chat.id,
                text="Message received and logged."
            )

    async def _handle_error(self, update: object, context: ContextTypes.DEFAULT_TYPE):
        """Handle errors."""
        logger.error(f"Telegram error: {context.error}")

        self._publish_observation("error", {
            "error": str(context.error),
            "update": str(update) if update else None
        })

    def _publish_observation(self, obs_type: str, data: dict):
        """Publish observation to Redis."""
        try:
            redis_client.publish(CH_OBSERVATIONS, {
                "source": "telegram",
                "type": obs_type,
                "timestamp": datetime.now(timezone.utc).isoformat(),
                **data
            })
        except Exception as e:
            logger.error(f"Failed to publish observation: {e}")

    async def start_async(self):
        """
        Start the Telegram bot in a non-blocking way.

        This method initializes and starts polling without blocking
        the main asyncio event loop.
        """
        if not self.app:
            logger.warning("Telegram app not initialized, cannot start")
            return

        self._running = True

        try:
            await self.app.initialize()
            await self.app.start()
            await self.app.updater.start_polling(
                allowed_updates=Update.ALL_TYPES,
                drop_pending_updates=True
            )
            logger.info("Telegram bot polling started (non-blocking)")
        except Exception as e:
            logger.error(f"Failed to start Telegram polling: {e}")
            self._running = False

    async def stop_async(self):
        """Stop the Telegram bot gracefully."""
        if not self.app or not self._running:
            return

        self._running = False

        try:
            await self.app.updater.stop()
            await self.app.stop()
            await self.app.shutdown()
            logger.info("Telegram bot stopped gracefully")
        except Exception as e:
            logger.error(f"Error stopping Telegram: {e}")

    def run(self):
        """
        Blocking run for standalone execution.

        Note: This blocks the event loop. Use start_async() for
        integration with ClawdBot main event loop.
        """
        if not self.app:
            logger.error("Cannot run - Telegram app not initialized")
            return

        logger.info("Starting Telegram Bot Polling (blocking mode)...")
        self.app.run_polling()

    @property
    def is_running(self) -> bool:
        """Check if bot is running."""
        return self._running

    async def execute(self, action: str, args: Dict[str, Any]):
        """
        Execute a command from the Skills Manager.

        Actions:
        - send_message: Send a text message
        - send_voice: Send a voice note (OGG/MP3)
        """
        if not self.app:
            logger.warning("Telegram not initialized, cannot execute command")
            return

        chat_id = args.get("chat_id")
        if not chat_id:
            # Try to get last chat_id from Redis or default?
            # For now, require chat_id.
            logger.error("Missing chat_id for Telegram command")
            return

        try:
            if action == "send_message":
                text = args.get("text")
                if text:
                    await self.app.bot.send_message(chat_id=chat_id, text=text)
                    logger.info(f"Sent message to {chat_id}: {text[:20]}...")

            elif action == "send_voice":
                file_path = args.get("file_path")
                if file_path and os.path.exists(file_path):
                    with open(file_path, 'rb') as voice:
                        await self.app.bot.send_voice(chat_id=chat_id, voice=voice)
                    logger.info(f"Sent voice note to {chat_id}: {file_path}")
                else:
                    logger.error(f"Voice file not found: {file_path}")

            else:
                logger.warning(f"Unknown Telegram action: {action}")

        except Exception as e:
            logger.error(f"Failed to execute Telegram action {action}: {e}")

# Standalone runner for testing
if __name__ == "__main__":
    from dotenv import load_dotenv
    load_dotenv()

    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )

    skill = TelegramSkill()
    if skill.app:
        skill.run()
    else:
        print("Failed to initialize Telegram skill. Check TELEGRAM_BOT_TOKEN.")

+            # Notify owner that bot is online

+            await self._notify_owner_online()

+    async def _notify_owner_online(self):

+        """Send startup notification to owner."""

+            owner_chat_id = redis_client.get("genesis:telegram:owner_chat_id")

+                await self.app.bot.send_message(

+                    chat_id=int(owner_chat_id),

+                        "**ClawdBot Online**\n\n"

+                        "Genesis Voice Bridge is ready.\n"

+                        "Type /call to start talking."

+                    parse_mode='Markdown'

+                logger.info(f"Sent online notification to {owner_chat_id}")

+            logger.warning(f"Could not send startup notification: {e}")


Now let's test the syntax is correct:


Bash

python -m py_compile ClawdBot/skills/telegram_bot.py && echo "Syntax OK"

Check telegram_bot.py syntax


Syntax OK


Now let's start ClawdBot and test it:


Start ClawdBot


Command running in background with ID: b454137. Output is being written to: C:\Users\P3\AppData\Local\Temp\claude\E--genesis-system\tasks\b454137.output


Check ClawdBot output


Error: Exit code 1
/usr/bin/bash: line 1: type: C:UsersP3AppDataLocalTempclaudeE--genesis-systemtasksb454137.output: not found


     1→[stderr] 2026-01-27 20:17:53,098 - ClawdBotRedisV2 - INFO - Connected to Redis at redis-genesis-u50607.vm.elestio.app:26379
     2→2026-01-27 20:17:53,098 - ClawdBotRedisV2 - INFO - ClawdBot Redis Client V2 initialized - Elestio: True
     3→2026-01-27 20:17:53,102 - ClawdBotMain - INFO - ClawdBot Node [genesis_primary] initializing...
     4→2026-01-27 20:17:53,105 - ClawdBotRedisV2 - INFO - Subscribed to channel: genesis:commands
     5→2026-01-27 20:17:53,105 - ClawdBotMain - INFO - Subscribed to control channel: genesis:control:genesis_primary
     6→2026-01-27 20:17:53,401 - ClawdBotSkills - INFO - Loaded Skill: browser
     7→e:\genesis-system\ClawdBot\skills\screen_monitor.py:1: SyntaxWarning: invalid escape sequence '\g'
     8→  """
     9→2026-01-27 20:17:53,610 - ClawdBotVision - INFO - Screenshot storage: e:\genesis-system\data\screenshots
    10→2026-01-27 20:17:53,610 - ClawdBotSkills - INFO - Loaded Skill: screen_monitor
    11→2026-01-27 20:17:53,619 - ClawdBotSkills - ERROR - Failed to load skill telegram_bot: No module named 'telegram'
    12→2026-01-27 20:17:53,621 - ClawdBotMain - WARNING - Telegram initialization failed: No module named 'telegram'
    13→2026-01-27 20:17:53,622 - ClawdBotMain - INFO - Heartbeat started (interval: 15s, TTL: 30s)
    14→2026-01-27 20:17:54,989 - ClawdBotMain - INFO - ClawdBot Node [genesis_primary] is listening on genesis:commands
    15→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

[stderr] 2026-01-27 20:17:53,098 - ClawdBotRedisV2 - INFO - Connected to Redis at redis-genesis-u50607.vm.elestio.app:26379
2026-01-27 20:17:53,098 - ClawdBotRedisV2 - INFO - ClawdBot Redis Client V2 initialized - Elestio: True
2026-01-27 20:17:53,102 - ClawdBotMain - INFO - ClawdBot Node [genesis_primary] initializing...
2026-01-27 20:17:53,105 - ClawdBotRedisV2 - INFO - Subscribed to channel: genesis:commands
2026-01-27 20:17:53,105 - ClawdBotMain - INFO - Subscribed to control channel: genesis:control:genesis_primary
2026-01-27 20:17:53,401 - ClawdBotSkills - INFO - Loaded Skill: browser
e:\genesis-system\ClawdBot\skills\screen_monitor.py:1: SyntaxWarning: invalid escape sequence '\g'
  """
2026-01-27 20:17:53,610 - ClawdBotVision - INFO - Screenshot storage: e:\genesis-system\data\screenshots
2026-01-27 20:17:53,610 - ClawdBotSkills - INFO - Loaded Skill: screen_monitor
2026-01-27 20:17:53,619 - ClawdBotSkills - ERROR - Failed to load skill telegram_bot: No module named 'telegram'
2026-01-27 20:17:53,621 - ClawdBotMain - WARNING - Telegram initialization failed: No module named 'telegram'
2026-01-27 20:17:53,622 - ClawdBotMain - INFO - Heartbeat started (interval: 15s, TTL: 30s)
2026-01-27 20:17:54,989 - ClawdBotMain - INFO - ClawdBot Node [genesis_primary] is listening on genesis:commands


ClawdBot is running but missing the `telegram` package. Let me install it and also kick off the autonomous evolution loop as you requested.


Install python-telegram-bot


[notice] A new release of pip is available: 24.3.1 -> 25.3
[notice] To update, run: python.exe -m pip install --upgrade pip


<task-notification>
<task-id>b454137</task-id>
<output-file>C:\Users\P3\AppData\Local\Temp\claude\E--genesis-system\tasks\b454137.output</output-file>
<status>killed</status>
<summary>Background command "Start ClawdBot" was killed</summary>
</task-notification>
Read the output file to retrieve the result: C:\Users\P3\AppData\Local\Temp\claude\E--genesis-system\tasks\b454137.output


Successfully killed shell: b454137 (python ClawdBot/main.py)


     1→
     2→"""
     3→Continuous Evolution Loop
     4→The Heartbeat of Genesis.
     5→Runs an infinite loop of Scout -> Plan -> Evolve cycles using low-cost Gemini Flash agents.
     6→"""
     7→
     8→import time
     9→import sys
    10→import os
    11→import signal
    12→from datetime import datetime
    13→import json
    14→
    15→# Add root (genesis-system) to path for swarms import
    16→root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    17→if root_path not in sys.path:
    18→    sys.path.insert(0, root_path)
    19→
    20→# Optional swarms import (requires google-generativeai)
    21→try:
    22→    from swarms.genesis_swarm import GenesisSwarm
    23→    SWARM_AVAILABLE = True
    24→except ImportError as e:
    25→    SWARM_AVAILABLE = False
    26→    GenesisSwarm = None
    27→    print(f"[WARN] Swarm unavailable: {e}")
    28→
    29→# Hyperdrive integration for multi-agent selection
    30→try:
    31→    from hyperdrive_controller import HyperdriveController, AgentSelector
    32→    HYPERDRIVE_AVAILABLE = True
    33→except ImportError:
    34→    HYPERDRIVE_AVAILABLE = False
    35→    HyperdriveController = None
    36→    AgentSelector = None
    37→
    38→def is_unceasing_motion_active():
    39→    path = "E:/genesis-system/config/unceasing_motion.json"
    40→    if os.path.exists(path):
    41→        with open(path, "r") as f:
    42→            config = json.load(f)
    43→            return config.get("unceasing_motion", False)
    44→    return False
    45→
    46→class EvolutionLoop:
    47→    def __init__(self):
    48→        # Optional swarm (requires google-generativeai package)
    49→        self.swarm = GenesisSwarm() if SWARM_AVAILABLE and GenesisSwarm else None
    50→        self.running = True
    51→
    52→        # Hyperdrive agent selection
    53→        self.agent_selector = AgentSelector() if HYPERDRIVE_AVAILABLE else None
    54→
    55→        # Handle graceful shutdown
    56→        signal.signal(signal.SIGINT, self.shutdown)
    57→        signal.signal(signal.SIGTERM, self.shutdown)
    58→
    59→    def select_agent(self, task_description: str) -> str:
    60→        """Select optimal agent for task using Hyperdrive."""
    61→        if self.agent_selector:
    62→            classification = self.agent_selector.classify_task(task_description)
    63→            return classification.recommended_agent.value
    64→        return "gemini-2.0-flash"  # Default
    65→
    66→    def shutdown(self, signum, frame):
    67→        print("\n\n🛑 [Loop] Shutdown signal received. Stopping...")
    68→        self.running = False
    69→
    70→    def start(self):
    71→        print(f"\n🚀 [Loop] GENESIS EVOLUTION HEARTBEAT STARTED")
    72→        print(f"   Model: Gemini 2.0 Flash Exp")
    73→        budget = self.swarm.daily_budget if self.swarm else "N/A (swarm unavailable)"
    74→        print(f"   Budget: ${budget}/day")
    75→        print(f"   Press Ctrl+C to stop.\n")
    76→
    77→        cycle_count = 0
    78→
    79→        while self.running:
    80→            try:
    81→                cycle_count += 1
    82→                cycle_start = time.time()
    83→
    84→                print(f"\n=== CYCLE {cycle_count} START: {datetime.now().strftime('%H:%M:%S')} ===")
    85→
    86→                # Execute the swarm cycle (if available)
    87→                if self.swarm:
    88→                    if is_unceasing_motion_active():
    89→                        print("🌑 [Unceasing Motion] Night Mode Active. Prioritizing background chores.")
    90→                        self.swarm.think("unsupervised_evolution")
    91→                    else:
    92→                        self.swarm.run_evolution_cycle()
    93→
    94→                    # Check for nervous system triggers
    95→                    self.swarm.trigger_nervous_system("heartbeat", {"cycle": cycle_count})
    96→                else:
    97→                    # Hyperdrive-only mode without swarm
    98→                    print("🔄 [Hyperdrive] Running without swarm (install google-generativeai for full features)")
    99→
   100→                elapsed = time.time() - cycle_start
   101→                print(f"=== CYCLE {cycle_count} COMPLETE ({elapsed:.2f}s) ===")
   102→
   103→                # Sleep between cycles (conserving budget)
   104→                # For aggressive evolution, sleep 60s. For maintenance, sleep 300s.
   105→                sleep_time = 60
   106→                print(f"💤 Sleeping {sleep_time}s...")
   107→                time.sleep(sleep_time)
   108→
   109→            except Exception as e:
   110→                print(f"❌ [Loop] Error: {e}")
   111→                time.sleep(60) # Backoff on error
   112→
   113→if __name__ == "__main__":
   114→    loop = EvolutionLoop()
   115→    loop.start()
   116→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

"""
Continuous Evolution Loop
The Heartbeat of Genesis.
Runs an infinite loop of Scout -> Plan -> Evolve cycles using low-cost Gemini Flash agents.
"""

import time
import sys
import os
import signal
from datetime import datetime
import json

# Add root (genesis-system) to path for swarms import
root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if root_path not in sys.path:
    sys.path.insert(0, root_path)

# Optional swarms import (requires google-generativeai)
try:
    from swarms.genesis_swarm import GenesisSwarm
    SWARM_AVAILABLE = True
except ImportError as e:
    SWARM_AVAILABLE = False
    GenesisSwarm = None
    print(f"[WARN] Swarm unavailable: {e}")

# Hyperdrive integration for multi-agent selection
try:
    from hyperdrive_controller import HyperdriveController, AgentSelector
    HYPERDRIVE_AVAILABLE = True
except ImportError:
    HYPERDRIVE_AVAILABLE = False
    HyperdriveController = None
    AgentSelector = None

def is_unceasing_motion_active():
    path = "E:/genesis-system/config/unceasing_motion.json"
    if os.path.exists(path):
        with open(path, "r") as f:
            config = json.load(f)
            return config.get("unceasing_motion", False)
    return False

class EvolutionLoop:
    def __init__(self):
        # Optional swarm (requires google-generativeai package)
        self.swarm = GenesisSwarm() if SWARM_AVAILABLE and GenesisSwarm else None
        self.running = True

        # Hyperdrive agent selection
        self.agent_selector = AgentSelector() if HYPERDRIVE_AVAILABLE else None

        # Handle graceful shutdown
        signal.signal(signal.SIGINT, self.shutdown)
        signal.signal(signal.SIGTERM, self.shutdown)

    def select_agent(self, task_description: str) -> str:
        """Select optimal agent for task using Hyperdrive."""
        if self.agent_selector:
            classification = self.agent_selector.classify_task(task_description)
            return classification.recommended_agent.value
        return "gemini-2.0-flash"  # Default

    def shutdown(self, signum, frame):
        print("\n\n🛑 [Loop] Shutdown signal received. Stopping...")
        self.running = False

    def start(self):
        print(f"\n🚀 [Loop] GENESIS EVOLUTION HEARTBEAT STARTED")
        print(f"   Model: Gemini 2.0 Flash Exp")
        budget = self.swarm.daily_budget if self.swarm else "N/A (swarm unavailable)"
        print(f"   Budget: ${budget}/day")
        print(f"   Press Ctrl+C to stop.\n")

        cycle_count = 0

        while self.running:
            try:
                cycle_count += 1
                cycle_start = time.time()

                print(f"\n=== CYCLE {cycle_count} START: {datetime.now().strftime('%H:%M:%S')} ===")

                # Execute the swarm cycle (if available)
                if self.swarm:
                    if is_unceasing_motion_active():
                        print("🌑 [Unceasing Motion] Night Mode Active. Prioritizing background chores.")
                        self.swarm.think("unsupervised_evolution")
                    else:
                        self.swarm.run_evolution_cycle()

                    # Check for nervous system triggers
                    self.swarm.trigger_nervous_system("heartbeat", {"cycle": cycle_count})
                else:
                    # Hyperdrive-only mode without swarm
                    print("🔄 [Hyperdrive] Running without swarm (install google-generativeai for full features)")

                elapsed = time.time() - cycle_start
                print(f"=== CYCLE {cycle_count} COMPLETE ({elapsed:.2f}s) ===")

                # Sleep between cycles (conserving budget)
                # For aggressive evolution, sleep 60s. For maintenance, sleep 300s.
                sleep_time = 60
                print(f"💤 Sleeping {sleep_time}s...")
                time.sleep(sleep_time)

            except Exception as e:
                print(f"❌ [Loop] Error: {e}")
                time.sleep(60) # Backoff on error

if __name__ == "__main__":
    loop = EvolutionLoop()
    loop.start()


Let me also check the genesis_execution_layer and the swarm to understand what's available: