Files
gravity_control/main.py
Variet Worker 3d75825bba feat(collector): brain event 중계 추가 — Watcher 이벤트를 Gateway로 전달
- collector.py: _forward_events_loop — BrainEvent를 JSON으로 serialize하여 /api/event POST
- gateway.py: /api/event 엔드포인트 — 수신한 이벤트를 bot event_queue에 주입
- main.py: event_queue를 CollectorBridge에 전달

이제 task.md, implementation_plan, walkthrough 변경사항이 Collector→Gateway→Discord 경로로 전달됨
2026-03-11 22:24:48 +09:00

134 lines
4.3 KiB
Python

"""Gravity Control — Antigravity Discord Bridge.
Entry point that runs the brain watcher and Discord bot together.
"""
import asyncio
import io
import logging
import os
import sys
from config import Config
from watcher import BrainWatcher
from bot import GravityBot
# Logging setup (UTF-8 forced for Windows cp949 compatibility)
_utf8_stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.StreamHandler(_utf8_stdout),
logging.FileHandler("gravity_control.log", encoding="utf-8"),
],
)
logger = logging.getLogger("gravity_control")
async def main():
"""Run the bridge: watcher + Discord bot."""
# Validate config
errors = Config.validate()
if errors:
for e in errors:
logger.error(f"Config error: {e}")
logger.error("Fix configuration issues and restart.")
sys.exit(1)
logger.info("=" * 50)
logger.info("Gravity Control — Antigravity Discord Bridge")
logger.info("=" * 50)
logger.info(f"Brain path: {Config.BRAIN_PATH}")
logger.info(f"Debounce: {Config.DEBOUNCE_SECONDS}s")
logger.info(f"Bot mode: {Config.BOT_MODE}")
# Shared event queue
event_queue = asyncio.Queue()
# Get the running loop
loop = asyncio.get_running_loop()
# ── Collector mode: no Discord bot, just relay local ↔ Gateway ──
if Config.BOT_MODE == "remote":
from bridge import LocalTransport, RemoteTransport
from collector import CollectorBridge
if not Config.REMOTE_BRIDGE_URL:
logger.error("REMOTE_BRIDGE_URL is required for remote (Collector) mode")
sys.exit(1)
bridge_dir = Config.BRAIN_PATH.parent / "bridge"
local = LocalTransport(bridge_dir)
local.ensure_dirs()
remote = RemoteTransport(Config.REMOTE_BRIDGE_URL, api_key=Config.GATEWAY_API_KEY)
collector = CollectorBridge(local, remote, project_name=Config.PROJECT_NAME,
event_queue=event_queue)
logger.info(f"Collector mode: {Config.REMOTE_BRIDGE_URL}")
# Optionally start watcher for brain events (local display only)
watcher = BrainWatcher(event_queue, loop)
try:
watcher.start()
logger.info(f"Watcher started, {len(watcher.known_sessions)} existing sessions")
await collector.start()
except KeyboardInterrupt:
logger.info("Received keyboard interrupt")
except Exception as e:
logger.error(f"Fatal error: {e}", exc_info=True)
finally:
await collector.stop()
watcher.stop()
logger.info("Collector shutdown complete")
return
# ── Local / Gateway mode ──
# Create components
watcher = None
if Config.BOT_MODE != 'gateway':
watcher = BrainWatcher(event_queue, loop)
bot = GravityBot(event_queue)
try:
# Start watcher (local mode only — gateway receives data via HTTP)
if watcher:
watcher.start()
logger.info(f"Watcher started, {len(watcher.known_sessions)} existing sessions")
else:
logger.info("Gateway mode — watcher disabled (data via HTTP API)")
# Start Gateway HTTP API (gateway mode)
if Config.BOT_MODE == 'gateway':
from gateway import GatewayAPI
gateway_port = int(os.environ.get('GATEWAY_PORT', '8585'))
gateway = GatewayAPI(bot, port=gateway_port, api_key=Config.GATEWAY_API_KEY)
await gateway.start()
logger.info(f"Gateway API running on port {gateway_port}")
# Run Discord bot (blocks until bot disconnects)
await bot.start(Config.DISCORD_TOKEN)
except KeyboardInterrupt:
logger.info("Received keyboard interrupt")
except Exception as e:
logger.error(f"Fatal error: {e}", exc_info=True)
finally:
# Cleanup
if watcher:
watcher.stop()
if not bot.is_closed():
await bot.close()
logger.info("Gravity Control shutdown complete")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass