refactor: Phase 2b — consolidate 28 modules into 14 packages

Complete the module consolidation planned in REFACTORING_PLAN.md:

Modules merged:
- work_orders/ + task_queue/ → swarm/ (subpackages)
- self_modify/ + self_tdd/ + upgrades/ → self_coding/ (subpackages)
- tools/ → creative/tools/
- chat_bridge/ + telegram_bot/ + shortcuts/ + voice/ → integrations/ (new)
- ws_manager/ + notifications/ + events/ + router/ → infrastructure/ (new)
- agents/ + agent_core/ + memory/ → timmy/ (subpackages)

Updated across codebase:
- 66 source files: import statements rewritten
- 13 test files: import + patch() target strings rewritten
- pyproject.toml: wheel includes (28→14), entry points updated
- CLAUDE.md: singleton paths, module map, entry points table
- AGENTS.md: file convention updates
- REFACTORING_PLAN.md: execution status, success metrics

Extras:
- Module-level CLAUDE.md added to 6 key packages (Phase 6.2)
- Zero test regressions: 1462 tests passing

https://claude.ai/code/session_01JNjWfHqusjT3aiN4vvYgUk
This commit is contained in:
Claude
2026-02-26 22:07:41 +00:00
parent 24c3d33c3b
commit 9f4c809f70
138 changed files with 913 additions and 407 deletions

View File

@@ -0,0 +1,163 @@
"""Telegram bot integration for Timmy Time.
Bridges Telegram messages to Timmy (the local AI agent). The bot token
is supplied via the dashboard setup endpoint or the TELEGRAM_TOKEN env var.
Optional dependency — install with:
pip install ".[telegram]"
"""
import asyncio
import json
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
# State file lives in the project root alongside timmy.db
_STATE_FILE = Path(__file__).parent.parent.parent / "telegram_state.json"
def _load_token_from_file() -> str | None:
"""Read the saved bot token from the state file."""
try:
if _STATE_FILE.exists():
data = json.loads(_STATE_FILE.read_text())
return data.get("token") or None
except Exception as exc:
logger.debug("Could not read telegram state file: %s", exc)
return None
def _save_token_to_file(token: str) -> None:
"""Persist the bot token to the state file."""
_STATE_FILE.write_text(json.dumps({"token": token}))
class TelegramBot:
"""Manages the lifecycle of the python-telegram-bot Application.
Integrates with an existing asyncio event loop (e.g. FastAPI's).
"""
def __init__(self) -> None:
self._app = None
self._token: str | None = None
self._running: bool = False
# ── Token helpers ─────────────────────────────────────────────────────────
def load_token(self) -> str | None:
"""Return the token from the state file or TELEGRAM_TOKEN env var."""
from_file = _load_token_from_file()
if from_file:
return from_file
try:
from config import settings
return settings.telegram_token or None
except Exception:
return None
def save_token(self, token: str) -> None:
"""Persist token so it survives restarts."""
_save_token_to_file(token)
# ── Status ────────────────────────────────────────────────────────────────
@property
def is_running(self) -> bool:
return self._running
@property
def token_set(self) -> bool:
return bool(self._token)
# ── Lifecycle ─────────────────────────────────────────────────────────────
async def start(self, token: str | None = None) -> bool:
"""Start the bot. Returns True on success, False otherwise."""
if self._running:
return True
tok = token or self.load_token()
if not tok:
logger.warning("Telegram bot: no token configured, skipping start.")
return False
try:
from telegram import Update
from telegram.ext import (
Application,
CommandHandler,
ContextTypes,
MessageHandler,
filters,
)
except ImportError:
logger.error(
"python-telegram-bot is not installed. "
'Run: pip install ".[telegram]"'
)
return False
try:
self._token = tok
self._app = Application.builder().token(tok).build()
self._app.add_handler(CommandHandler("start", self._cmd_start))
self._app.add_handler(
MessageHandler(filters.TEXT & ~filters.COMMAND, self._handle_message)
)
await self._app.initialize()
await self._app.start()
await self._app.updater.start_polling(allowed_updates=Update.ALL_TYPES)
self._running = True
logger.info("Telegram bot started.")
return True
except Exception as exc:
logger.error("Telegram bot failed to start: %s", exc)
self._running = False
self._token = None
self._app = None
return False
async def stop(self) -> None:
"""Gracefully shut down the bot."""
if not self._running or self._app is None:
return
try:
await self._app.updater.stop()
await self._app.stop()
await self._app.shutdown()
logger.info("Telegram bot stopped.")
except Exception as exc:
logger.error("Error stopping Telegram bot: %s", exc)
finally:
self._running = False
# ── Handlers ──────────────────────────────────────────────────────────────
async def _cmd_start(self, update, context) -> None:
await update.message.reply_text(
"Sir, affirmative. I'm Timmy — your sovereign local AI agent. "
"Send me any message and I'll get right on it."
)
async def _handle_message(self, update, context) -> None:
user_text = update.message.text
try:
from timmy.agent import create_timmy
agent = create_timmy()
run = await asyncio.to_thread(agent.run, user_text, stream=False)
response = run.content if hasattr(run, "content") else str(run)
except Exception as exc:
logger.error("Timmy error in Telegram handler: %s", exc)
response = f"Timmy is offline: {exc}"
await update.message.reply_text(response)
# Module-level singleton
telegram_bot = TelegramBot()