Initial commit: Ajarbot with optimizations
Features: - Multi-platform bot (Slack, Telegram) - Memory system with SQLite FTS - Tool use capabilities (file ops, commands) - Scheduled tasks system - Dynamic model switching (/sonnet, /haiku) - Prompt caching for cost optimization Optimizations: - Default to Haiku 4.5 (12x cheaper) - Reduced context: 3 messages, 2 memory results - Optimized SOUL.md (48% smaller) - Automatic caching when using Sonnet (90% savings) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
225
bot_runner.py
Normal file
225
bot_runner.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
Multi-platform bot runner for ajarbot.
|
||||
|
||||
Usage:
|
||||
python bot_runner.py # Run with config from adapters.yaml
|
||||
python bot_runner.py --config custom.yaml # Use custom config file
|
||||
python bot_runner.py --init # Generate config template
|
||||
|
||||
Environment variables:
|
||||
AJARBOT_SLACK_BOT_TOKEN # Slack bot token (xoxb-...)
|
||||
AJARBOT_SLACK_APP_TOKEN # Slack app token (xapp-...)
|
||||
AJARBOT_TELEGRAM_BOT_TOKEN # Telegram bot token
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import traceback
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
from adapters.base import AdapterConfig
|
||||
from adapters.runtime import AdapterRuntime
|
||||
from adapters.slack.adapter import SlackAdapter
|
||||
from adapters.telegram.adapter import TelegramAdapter
|
||||
from agent import Agent
|
||||
from config.config_loader import ConfigLoader
|
||||
from scheduled_tasks import TaskScheduler
|
||||
|
||||
# Adapter class registry mapping platform names to their classes
|
||||
_ADAPTER_CLASSES = {
|
||||
"slack": SlackAdapter,
|
||||
"telegram": TelegramAdapter,
|
||||
}
|
||||
|
||||
|
||||
class BotRunner:
|
||||
"""Main bot runner that manages all adapters."""
|
||||
|
||||
def __init__(self, config_file: str = "adapters.yaml") -> None:
|
||||
self.config_loader = ConfigLoader()
|
||||
self.config = self.config_loader.load(config_file)
|
||||
self.runtime: AdapterRuntime = None
|
||||
self.agent: Agent = None
|
||||
self.scheduler: TaskScheduler = None
|
||||
|
||||
def _load_adapter(self, platform: str) -> bool:
|
||||
"""Load and register a single adapter. Returns True if loaded."""
|
||||
if not self.config_loader.is_adapter_enabled(platform):
|
||||
return False
|
||||
|
||||
print(f"\n[Setup] Loading {platform.title()} adapter...")
|
||||
adapter_cls = _ADAPTER_CLASSES[platform]
|
||||
platform_config = self.config_loader.get_adapter_config(platform)
|
||||
adapter = adapter_cls(AdapterConfig(
|
||||
platform=platform,
|
||||
enabled=True,
|
||||
credentials=platform_config.get("credentials", {}),
|
||||
settings=platform_config.get("settings", {}),
|
||||
))
|
||||
self.runtime.add_adapter(adapter)
|
||||
print(f"[Setup] {platform.title()} adapter loaded")
|
||||
return True
|
||||
|
||||
def setup(self) -> bool:
|
||||
"""Set up agent and adapters."""
|
||||
print("=" * 60)
|
||||
print("Ajarbot Multi-Platform Runner")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n[Setup] Initializing agent...")
|
||||
self.agent = Agent(
|
||||
provider="claude",
|
||||
workspace_dir="./memory_workspace",
|
||||
enable_heartbeat=False,
|
||||
)
|
||||
print("[Setup] Agent initialized")
|
||||
|
||||
self.runtime = AdapterRuntime(self.agent)
|
||||
|
||||
enabled_count = sum(
|
||||
self._load_adapter(platform)
|
||||
for platform in _ADAPTER_CLASSES
|
||||
)
|
||||
|
||||
# Load user mappings
|
||||
user_mapping = self.config_loader.get_user_mapping()
|
||||
for platform_user_id, username in user_mapping.items():
|
||||
self.runtime.map_user(platform_user_id, username)
|
||||
print(f"[Setup] User mapping: {platform_user_id} -> {username}")
|
||||
|
||||
if enabled_count == 0:
|
||||
print("\nWARNING: No adapters enabled!")
|
||||
print("Edit config/adapters.local.yaml and set enabled: true")
|
||||
print("Or run: python bot_runner.py --init")
|
||||
return False
|
||||
|
||||
print(f"\n[Setup] {enabled_count} adapter(s) configured")
|
||||
|
||||
# Initialize scheduler
|
||||
print("\n[Setup] Initializing task scheduler...")
|
||||
self.scheduler = TaskScheduler(
|
||||
self.agent,
|
||||
config_file="config/scheduled_tasks.yaml"
|
||||
)
|
||||
|
||||
# Register adapters with scheduler
|
||||
for platform, adapter in self.runtime.registry._adapters.items():
|
||||
self.scheduler.add_adapter(platform, adapter)
|
||||
|
||||
# List scheduled tasks
|
||||
tasks = self.scheduler.list_tasks()
|
||||
enabled_tasks = [t for t in tasks if t.get("enabled")]
|
||||
if enabled_tasks:
|
||||
print(f"[Setup] {len(enabled_tasks)} scheduled task(s) enabled:")
|
||||
for task_info in enabled_tasks:
|
||||
print(f" - {task_info['name']}: {task_info['schedule']}")
|
||||
if task_info.get("send_to_platform"):
|
||||
print(f" → {task_info['send_to_platform']}")
|
||||
|
||||
return True
|
||||
|
||||
async def run(self) -> None:
|
||||
"""Start all adapters and run until interrupted."""
|
||||
if not self.setup():
|
||||
return
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Starting bot...")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
await self.runtime.start()
|
||||
|
||||
# Start scheduler if configured
|
||||
if self.scheduler:
|
||||
self.scheduler.start()
|
||||
print("[Scheduler] Task scheduler started\n")
|
||||
|
||||
print("=" * 60)
|
||||
print("Bot is running! Press Ctrl+C to stop.")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[Shutdown] Received interrupt signal...")
|
||||
except Exception as e:
|
||||
print(f"\n[Error] {e}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
if self.scheduler:
|
||||
self.scheduler.stop()
|
||||
print("[Scheduler] Task scheduler stopped")
|
||||
await self.runtime.stop()
|
||||
print("\n[Shutdown] Bot stopped cleanly")
|
||||
|
||||
async def health_check(self) -> None:
|
||||
"""Check health of all adapters."""
|
||||
if not self.runtime:
|
||||
print("Runtime not initialized")
|
||||
return
|
||||
|
||||
status = await self.runtime.health_check()
|
||||
print("\n" + "=" * 60)
|
||||
print("Health Check")
|
||||
print("=" * 60)
|
||||
print(f"\nRuntime running: {status['runtime_running']}")
|
||||
print("\nAdapters:")
|
||||
for platform, adapter_status in status["adapters"].items():
|
||||
print(f"\n {platform.upper()}:")
|
||||
for key, value in adapter_status.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Ajarbot Multi-Platform Runner"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
default="adapters.yaml",
|
||||
help="Config file to use (default: adapters.yaml)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--init",
|
||||
action="store_true",
|
||||
help="Generate config template",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--health",
|
||||
action="store_true",
|
||||
help="Run health check",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.init:
|
||||
print("Generating configuration template...")
|
||||
loader = ConfigLoader()
|
||||
path = loader.save_template()
|
||||
print(f"\nConfiguration template created at: {path}")
|
||||
print("\nNext steps:")
|
||||
print("1. Edit the file with your credentials")
|
||||
print("2. Set enabled: true for adapters you want to use")
|
||||
print("3. Run: python bot_runner.py")
|
||||
return
|
||||
|
||||
runner = BotRunner(config_file=args.config)
|
||||
|
||||
if args.health:
|
||||
asyncio.run(runner.health_check())
|
||||
return
|
||||
|
||||
try:
|
||||
asyncio.run(runner.run())
|
||||
except KeyboardInterrupt:
|
||||
print("\nExiting...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user