2026-02-13 19:06:28 -07:00
|
|
|
"""
|
|
|
|
|
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
|
2026-02-14 15:12:01 -07:00
|
|
|
import signal
|
2026-02-13 19:06:28 -07:00
|
|
|
import traceback
|
|
|
|
|
from dotenv import load_dotenv
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
from telegram.error import NetworkError as TelegramNetworkError
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
# 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
|
2026-03-01 14:34:24 -07:00
|
|
|
from agent_registry import register_agent
|
2026-02-13 19:06:28 -07:00
|
|
|
from config.config_loader import ConfigLoader
|
2026-02-14 10:29:28 -07:00
|
|
|
from google_tools.oauth_manager import GoogleOAuthManager
|
2026-02-13 19:06:28 -07:00
|
|
|
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
|
2026-02-14 15:12:01 -07:00
|
|
|
self.shutdown_event = asyncio.Event()
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
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",
|
|
|
|
|
)
|
|
|
|
|
print("[Setup] Agent initialized")
|
|
|
|
|
|
2026-03-01 14:34:24 -07:00
|
|
|
# Register agent in global registry for MCP tool access (delegate_task)
|
|
|
|
|
register_agent(self.agent)
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
self.runtime = AdapterRuntime(self.agent)
|
|
|
|
|
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
# Wire child safety filter (no-op if child_safety block is absent from config)
|
|
|
|
|
try:
|
|
|
|
|
from pathlib import Path as _Path
|
|
|
|
|
from child_safety import ChildSafetyConfig, ChildSafetyFilter, ChildAuditLogger
|
|
|
|
|
_cs_config = ChildSafetyConfig.from_yaml(_Path("config/adapters.local.yaml"))
|
|
|
|
|
if _cs_config:
|
|
|
|
|
_cs_audit = ChildAuditLogger(_Path("./memory_workspace"))
|
|
|
|
|
_cs_audit.cleanup_old_logs(_cs_config.audit_retention_days)
|
|
|
|
|
_cs_filter = ChildSafetyFilter(_cs_config, _cs_audit)
|
|
|
|
|
self.runtime.add_preprocessor(_cs_filter.preprocess_adapter)
|
|
|
|
|
self.runtime.add_postprocessor(_cs_filter.postprocess_adapter)
|
|
|
|
|
print(f"[Setup] Child safety filter active for: {_cs_config.restricted_users}")
|
|
|
|
|
except Exception as _e:
|
|
|
|
|
print(f"[Setup] Child safety filter not loaded: {_e}")
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
enabled_count = sum(
|
|
|
|
|
self._load_adapter(platform)
|
|
|
|
|
for platform in _ADAPTER_CLASSES
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Load user mappings
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
# Config format: "platform_userid" — runtime lookup format: "platform:userid"
|
2026-02-13 19:06:28 -07:00
|
|
|
user_mapping = self.config_loader.get_user_mapping()
|
|
|
|
|
for platform_user_id, username in user_mapping.items():
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
if "_" in platform_user_id:
|
|
|
|
|
platform, uid = platform_user_id.split("_", 1)
|
|
|
|
|
self.runtime.map_user(f"{platform}:{uid}", username)
|
|
|
|
|
else:
|
|
|
|
|
self.runtime.map_user(platform_user_id, username)
|
2026-02-13 19:06:28 -07:00
|
|
|
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']}")
|
Refactor: Remove zombie code, fix bugs, and clean documentation
This comprehensive refactoring removes dead code, fixes bugs, and deletes
outdated documentation to make the codebase production-ready.
## Files Deleted (16 files)
### Temporary/zombie files (9 files):
- nul (Windows artifact)
- quick_start.bat (superseded by run.bat)
- scripts/proxmox_ssh.py (hardcoded credentials - security risk)
- scripts/proxmox_ssh.sh (hardcoded credentials - security risk)
- scripts/collection_output.txt (one-time audit output)
- scripts/collect-homelab-config.sh (one-off infrastructure script)
- scripts/collect-remote.sh (one-off infrastructure script)
- memory_workspace/MEMORY.md.old (backup file)
- promtail-config-optimized.yaml (misplaced homelab config)
### Outdated documentation (7 files):
- MCP_MIGRATION.md (migration complete - 2026-02-15)
- QUICK_REFERENCE_AGENT_SDK.md (orphaned from cleanup)
- SETUP.md (duplicate of README.md quick start)
- WINDOWS_QUICK_REFERENCE.md (duplicate of docs/WINDOWS_DEPLOYMENT.md)
- SUB_AGENTS.md (design doc for unimplemented feature)
- JARVIS_VOICE_INTEGRATION_PLAN.md (1300-line spec, code not implemented)
- OBSIDIAN_MCP_SETUP_INSTRUCTIONS.md (temporary troubleshooting doc)
- LOGGING.md (redundant with well-commented logging_config.py)
- docs/SECURITY_AUDIT_SUMMARY.md (completed audit from 2026-02-12)
## Critical Bug Fixes (2 bugs)
1. bot_runner.py line 122: Fixed wrong dict key reference
- Changed send_to_platform → send_to
- Bug caused scheduled task platform info to never print
2. usage_tracker.py: Added missing pricing for claude-sonnet-4-6
- Model was default but had no pricing entry
- Caused cost under-reporting in Direct API mode
## Code Removed (14 files modified, ~1200 lines deleted)
### Dead imports removed (9 imports):
- bot_runner.py: sys
- agent.py: time
- adapters/runtime.py: re
- adapters/skill_integration.py: subprocess
- tools.py: redundant Path import
- mcp_servers/loki/loki_server.py: json
- google_tools/oauth_manager.py: Thread, Dict
- google_tools/gmail_client.py: os
- google_tools/utils.py: email
### Unused functions/methods removed (9 functions):
- agent.py: MEMORY_RESPONSE_PREVIEW_LENGTH constant
- scheduled_tasks.py: integrate_scheduler_with_runtime()
- adapters/runtime.py: command_preprocessor(), markdown_postprocessor()
- adapters/skill_integration.py: invoke_skill_via_cli(), __main__ block
- tools.py: _extract_mcp_result()
- google_tools/oauth_manager.py: needs_refresh_soon(), revoke_authorization()
- google_tools/people_client.py: update_contact(), delete_contact()
### Code quality improvements:
- memory_system.py: Removed empty else: pass branch
- calendar_client.py: Fixed bare except: → except Exception:
- mcp_ssh.py: Updated asyncio.get_event_loop() → get_running_loop()
- calendar_client.py: Fixed deprecated datetime.utcnow() → now(timezone.utc)
## Impact
- ~1200 lines of dead code removed
- 16 obsolete files deleted
- 2 critical bugs fixed
- 3 deprecated APIs updated
- Zero functionality broken (all changes verified)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-24 12:46:56 -07:00
|
|
|
if task_info.get("send_to"):
|
|
|
|
|
print(f" → {task_info['send_to']}")
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
async def run(self) -> None:
|
|
|
|
|
"""Start all adapters and run until interrupted."""
|
|
|
|
|
if not self.setup():
|
|
|
|
|
return
|
|
|
|
|
|
2026-02-14 15:12:01 -07:00
|
|
|
# Set up signal handlers
|
|
|
|
|
loop = asyncio.get_running_loop()
|
|
|
|
|
|
|
|
|
|
def signal_handler(signum, frame):
|
|
|
|
|
print(f"\n\n[Shutdown] Received signal {signum}...")
|
|
|
|
|
loop.call_soon_threadsafe(self.shutdown_event.set)
|
|
|
|
|
|
|
|
|
|
# Register signal handlers (works on both Windows and Unix)
|
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
print("\n" + "=" * 60)
|
|
|
|
|
print("Starting bot...")
|
|
|
|
|
print("=" * 60 + "\n")
|
|
|
|
|
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
_startup_delays = [10, 30, 60, 120, 300] # backoff between full restart attempts
|
|
|
|
|
|
|
|
|
|
for attempt, delay in enumerate([0] + _startup_delays, 0):
|
|
|
|
|
if self.shutdown_event.is_set():
|
|
|
|
|
break
|
|
|
|
|
if attempt > 0:
|
|
|
|
|
print(f"\n[Reconnect] Waiting {delay}s before restart attempt {attempt}...")
|
|
|
|
|
try:
|
|
|
|
|
await asyncio.wait_for(self.shutdown_event.wait(), timeout=delay)
|
|
|
|
|
break # Shutdown was requested during the wait
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
pass
|
|
|
|
|
print(f"[Reconnect] Restarting adapters (attempt {attempt})...")
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
# Wait for shutdown signal
|
|
|
|
|
await self.shutdown_event.wait()
|
|
|
|
|
break # Clean shutdown — don't retry
|
|
|
|
|
|
|
|
|
|
except TelegramNetworkError as e:
|
|
|
|
|
print(f"\n[Reconnect] Telegram network error during startup: {e}")
|
|
|
|
|
if attempt >= len(_startup_delays):
|
|
|
|
|
print("[Reconnect] Max retries reached. Giving up.")
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
break
|
|
|
|
|
# Will retry in next loop iteration
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"\n[Error] {e}")
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
break # Non-network errors are not retried
|
|
|
|
|
finally:
|
|
|
|
|
if self.scheduler:
|
|
|
|
|
self.scheduler.stop()
|
|
|
|
|
print("[Scheduler] Task scheduler stopped")
|
|
|
|
|
await self.runtime.stop()
|
|
|
|
|
|
|
|
|
|
print("\n[Shutdown] Bot stopped cleanly")
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
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",
|
|
|
|
|
)
|
2026-02-14 10:29:28 -07:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--setup-google",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Set up Google OAuth for Gmail/Calendar integration",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--manual",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Use manual OAuth code entry (for headless servers)",
|
|
|
|
|
)
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-02-14 10:29:28 -07:00
|
|
|
if args.setup_google:
|
|
|
|
|
print("=" * 60)
|
|
|
|
|
print("Google OAuth Setup")
|
|
|
|
|
print("=" * 60)
|
|
|
|
|
print()
|
|
|
|
|
oauth_manager = GoogleOAuthManager()
|
|
|
|
|
|
|
|
|
|
if oauth_manager.is_authorized():
|
|
|
|
|
print("✓ Already authorized!")
|
|
|
|
|
print(f"✓ Tokens found at {oauth_manager.token_file}")
|
|
|
|
|
print("\nTo re-authorize, delete the token file and run this command again.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
success = oauth_manager.run_oauth_flow(manual=args.manual)
|
|
|
|
|
if success:
|
|
|
|
|
print("You can now use Gmail and Calendar tools!")
|
|
|
|
|
print("\nTest it:")
|
|
|
|
|
print(" Via Telegram: \"What's on my calendar?\"")
|
|
|
|
|
print(" Via Telegram: \"Send an email to john@example.com\"")
|
|
|
|
|
else:
|
|
|
|
|
print("\nSetup failed. Please check:")
|
|
|
|
|
print("1. config/google_credentials.yaml exists with valid client_id/client_secret")
|
|
|
|
|
print("2. You authorized the app in your browser")
|
|
|
|
|
print("3. No firewall blocking localhost:8080")
|
|
|
|
|
return
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
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()
|