feat(phase2): 승인/거부 버튼 + bridge 프로토콜 + 테이블 변환 수정 + 중복 메시지 해결
This commit is contained in:
127
bridge.py
Normal file
127
bridge.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""Bridge protocol — file-based communication between Discord bot and Antigravity.
|
||||
|
||||
Bridge directory: ~/.gemini/antigravity/bridge/
|
||||
Structure:
|
||||
bridge/
|
||||
pending/ ← Bot writes approval requests for Discord
|
||||
response/ ← Bot writes user responses from Discord
|
||||
commands/ ← Bot writes user text input from Discord
|
||||
|
||||
Protocol:
|
||||
1. VS Code Extension detects pending approval → writes JSON to pending/
|
||||
2. Bot reads pending/ → sends Discord message with ✅/❌ buttons
|
||||
3. User clicks button → Bot writes JSON to response/
|
||||
4. VS Code Extension reads response/ → executes action
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
|
||||
from config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApprovalStatus(Enum):
|
||||
PENDING = "pending"
|
||||
APPROVED = "approved"
|
||||
REJECTED = "rejected"
|
||||
TIMEOUT = "timeout"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApprovalRequest:
|
||||
"""An approval request from Antigravity."""
|
||||
request_id: str
|
||||
conversation_id: str
|
||||
command: str # The command/action needing approval
|
||||
description: str # Human-readable description
|
||||
timestamp: float
|
||||
status: str = "pending"
|
||||
discord_message_id: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserResponse:
|
||||
"""A user response from Discord."""
|
||||
request_id: str
|
||||
approved: bool
|
||||
user_input: str = ""
|
||||
timestamp: float = 0
|
||||
|
||||
|
||||
class BridgeProtocol:
|
||||
"""Manages the file-based bridge protocol."""
|
||||
|
||||
def __init__(self):
|
||||
self.bridge_dir = Config.BRAIN_PATH.parent / "bridge"
|
||||
self.pending_dir = self.bridge_dir / "pending"
|
||||
self.response_dir = self.bridge_dir / "response"
|
||||
self.commands_dir = self.bridge_dir / "commands"
|
||||
|
||||
# Create directories
|
||||
for d in [self.pending_dir, self.response_dir, self.commands_dir]:
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
logger.info(f"Bridge protocol initialized: {self.bridge_dir}")
|
||||
|
||||
def get_pending_requests(self) -> list[ApprovalRequest]:
|
||||
"""Read all pending approval requests."""
|
||||
requests = []
|
||||
for f in self.pending_dir.glob("*.json"):
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding="utf-8"))
|
||||
if data.get("status") == "pending":
|
||||
requests.append(ApprovalRequest(**data))
|
||||
except (json.JSONDecodeError, TypeError, OSError) as e:
|
||||
logger.warning(f"Bad pending request {f.name}: {e}")
|
||||
return requests
|
||||
|
||||
def write_response(self, response: UserResponse):
|
||||
"""Write a user response to the response directory."""
|
||||
response.timestamp = time.time()
|
||||
filename = f"{response.request_id}.json"
|
||||
filepath = self.response_dir / filename
|
||||
|
||||
filepath.write_text(
|
||||
json.dumps(asdict(response), ensure_ascii=False, indent=2),
|
||||
encoding="utf-8"
|
||||
)
|
||||
logger.info(f"Response written: {filename} (approved={response.approved})")
|
||||
|
||||
# Mark pending request as processed
|
||||
pending_file = self.pending_dir / filename
|
||||
if pending_file.exists():
|
||||
try:
|
||||
data = json.loads(pending_file.read_text(encoding="utf-8"))
|
||||
data["status"] = "approved" if response.approved else "rejected"
|
||||
pending_file.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8"
|
||||
)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
|
||||
def write_command(self, conversation_id: str, text: str):
|
||||
"""Write a user text command for Antigravity to consume."""
|
||||
cmd_id = f"{int(time.time() * 1000)}"
|
||||
filepath = self.commands_dir / f"{cmd_id}.json"
|
||||
|
||||
data = {
|
||||
"id": cmd_id,
|
||||
"conversation_id": conversation_id,
|
||||
"text": text,
|
||||
"timestamp": time.time(),
|
||||
"consumed": False,
|
||||
}
|
||||
|
||||
filepath.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8"
|
||||
)
|
||||
logger.info(f"Command written: {cmd_id} for {conversation_id[:8]}")
|
||||
return cmd_id
|
||||
Reference in New Issue
Block a user