diff --git a/bot.py b/bot.py index fff7cf7..1d0967b 100644 --- a/bot.py +++ b/bot.py @@ -30,8 +30,7 @@ from parser import ( md_to_discord_text, format_task_embed_text, ) -from watcher import BrainEvent, EventType -from bridge import BridgeProtocol, ApprovalRequest, UserResponse +from models import BrainEvent, EventType, ApprovalRequest, UserResponse logger = logging.getLogger(__name__) @@ -47,10 +46,9 @@ class ApprovalView(discord.ui.View): (e.g., ✅ Allow Once / ✅ Allow This Conversation / ❌ Deny) """ - def __init__(self, bridge: BridgeProtocol, request: ApprovalRequest, + def __init__(self, request: ApprovalRequest, buttons: list[dict] | None = None, hub=None): super().__init__(timeout=1800) # 30 minutes - self.bridge = bridge self.hub = hub # WSHub instance for WS response routing self.request = request self.responded = False @@ -100,12 +98,9 @@ class ApprovalView(discord.ui.View): # Hub WS route (primary — reaches remote Extensions) delivered = False if self.hub: - delivered = await self.hub.send_response_to_pending_owner(self.request.request_id, { + await self.hub.send_response_to_pending_owner(self.request.request_id, { "type": "response", "data": response_data, }) - if not delivered: - # File bridge fallback (Hub unavailable OR owner disconnected) - self.bridge.write_response(UserResponse(**response_data)) embed = interaction.message.embeds[0] if interaction.message.embeds else None if embed: color = discord.Color.red() if is_reject else discord.Color.green() @@ -131,13 +126,10 @@ class ApprovalView(discord.ui.View): "step_type": getattr(self.request, 'step_type', ''), "project_name": getattr(self.request, 'project_name', ''), } - delivered = False if self.hub: - delivered = await self.hub.send_response_to_pending_owner(self.request.request_id, { + await self.hub.send_response_to_pending_owner(self.request.request_id, { "type": "response", "data": response_data, }) - if not delivered: - self.bridge.write_response(UserResponse(**response_data)) embed = interaction.message.embeds[0] if interaction.message.embeds else None if embed: embed.color = discord.Color.green() @@ -158,13 +150,10 @@ class ApprovalView(discord.ui.View): "step_type": getattr(self.request, 'step_type', ''), "project_name": getattr(self.request, 'project_name', ''), } - delivered = False if self.hub: - delivered = await self.hub.send_response_to_pending_owner(self.request.request_id, { + await self.hub.send_response_to_pending_owner(self.request.request_id, { "type": "response", "data": response_data, }) - if not delivered: - self.bridge.write_response(UserResponse(**response_data)) embed = interaction.message.embeds[0] if interaction.message.embeds else None if embed: embed.color = discord.Color.red() @@ -172,12 +161,14 @@ class ApprovalView(discord.ui.View): await interaction.response.edit_message(embed=embed, view=None) async def on_timeout(self): - if not self.responded: - self.bridge.write_response(UserResponse( - request_id=self.request.request_id, approved=False, - step_type=getattr(self.request, 'step_type', ''), - project_name=getattr(self.request, 'project_name', ''), - )) + if not self.responded and self.hub: + await self.hub.send_response_to_pending_owner(self.request.request_id, { + "type": "response", "data": { + "request_id": self.request.request_id, "approved": False, + "step_type": getattr(self.request, 'step_type', ''), + "project_name": getattr(self.request, 'project_name', ''), + } + }) # ─── Bot ───────────────────────────────────────────────────────────── @@ -207,7 +198,6 @@ class GravityBot(commands.Bot): self._sent_commands: dict[str, str] = {} # request_id → command text (for MERGE edit detection) self._ready_event = asyncio.Event() self._channel_lock = asyncio.Lock() - self.bridge = BridgeProtocol() self.session_category: discord.CategoryChannel | None = None self.guild: discord.Guild | None = None self.auto_approve_projects: set[str] = set() # projects with auto-approve enabled @@ -233,7 +223,7 @@ class GravityBot(commands.Bot): "project_name": kwargs.get('project_name', project), } - # Hub route (primary — skip file bridge to prevent double delivery) + # Hub route (primary) if self.hub: import time as _time cmd_data["id"] = str(int(_time.time() * 1000)) @@ -246,14 +236,6 @@ class GravityBot(commands.Bot): asyncio.create_task( self.hub.broadcast_to_project(project, msg) ) - return # ← WS sent, skip file bridge - - # Legacy fallback (file bridge + gateway HTTP) — only when Hub is unavailable - self.bridge.write_command(project, text, **kwargs) - if self.gateway: - import time as _time - cmd_data["id"] = cmd_data.get("id", str(int(_time.time() * 1000))) - self.gateway.push_command(project, cmd_data) def _cap_dict(self, d: dict, max_size: int = 5000): """Prevent memory leaks by capping dictionary sizes using insertion order (oldest first).""" @@ -269,8 +251,6 @@ class GravityBot(commands.Bot): async def setup_hook(self): self.loop.create_task(self._process_events()) - self.pending_approval_scanner.start() - self.chat_snapshot_scanner.start() self._register_slash_commands() # Register Hub handlers (if Hub is available, set after setup_hook by main.py) asyncio.get_event_loop().call_soon(self._register_hub_handlers) @@ -353,57 +333,12 @@ class GravityBot(commands.Bot): logger.error("No permission to create category!") return - # Discover existing project channels - await self._discover_channels() - - # Load conversation → project registrations from Extension - self._load_registrations() - - # Sync slash commands to guild - try: - self.tree.copy_global_to(guild=self.guild) - synced = await self.tree.sync(guild=self.guild) - logger.info(f"Synced {len(synced)} slash commands to guild") - except Exception as e: - logger.warning(f"Slash command sync failed: {e}") - - # Open the gate + # Start WS Hub processors by ensuring ready gate is open self._ready_event.set() logger.info("Ready gate opened — event processing enabled") - # Start scanner loops - if not self.pending_approval_scanner.is_running(): - self.pending_approval_scanner.start() - if not self.chat_snapshot_scanner.is_running(): - self.chat_snapshot_scanner.start() - logger.info("Scanner loops started") - # ─── Channel Management ────────────────────────────────────────── - def _load_registrations(self): - """Read bridge/register/ to learn conversation → project mappings.""" - register_dir = self.bridge.bridge_dir / "register" - if not register_dir.exists(): - return - - count = 0 - for f in register_dir.glob("*.json"): - try: - data = json.loads(f.read_text(encoding="utf-8-sig")) - conv_id = data.get("conversation_id", "") - project = data.get("project_name", "") - if conv_id and project: - self.conv_to_project[conv_id] = project - count += 1 - except (json.JSONDecodeError, OSError): - pass - - # Only log when count changes - prev = getattr(self, '_last_reg_count', -1) - if count != prev: - self._last_reg_count = count - if count: - logger.info(f"Loaded {count} conversation→project registrations") # ─── Channel Management ────────────────────────────────────────── @@ -618,270 +553,7 @@ class GravityBot(commands.Bot): # ─── Approval Scanner ──────────────────────────────────────────── - @tasks.loop(seconds=30) # Hub mode: WS is primary, file scan is fallback only - async def pending_approval_scanner(self): - """Scan bridge/pending/ for new approval requests + reload registrations. - Per-tick caps prevent Discord API rate limit cascade when multiple - projects generate pending files simultaneously. - """ - try: - # Reload conv→project registrations each cycle - self._load_registrations() - - # Channels are created on-demand when actual signals arrive - # (via _get_channel in snapshot scanner / approval sender) - - MAX_NEW_PER_TICK = 5 # Phase 1: max new pending to process per tick - MAX_STATUS_PER_TICK = 5 # Phase 2: max status changes to process per tick - phase1_processed = 0 - - requests = self.bridge.get_pending_requests() - for req in requests: - if phase1_processed >= MAX_NEW_PER_TICK: - break - if req.request_id in self._sent_approval_ids: - continue - if req.discord_message_id != 0: - continue - - # Learn project mapping from pending approval - project = req.project_name or Config.PROJECT_NAME - if req.conversation_id and req.conversation_id != '__global__': - self.conv_to_project[req.conversation_id] = project - - # ── SafeToAutoRun: approve immediately and quietly ── - if getattr(req, "safe_to_auto_run", False): - self._cap_dict(self._sent_approval_ids) - self._sent_approval_ids[req.request_id] = True - - # Generate approve response back to extension - approve_btn_index = 0 - pending_file = self.bridge.pending_dir / f"{req.request_id}.json" - if pending_file.exists(): - try: - pdata = json.loads(pending_file.read_text(encoding="utf-8-sig")) - btns = pdata.get("buttons") - if btns and len(btns) > 1: - reject_words = {"deny", "reject", "cancel", "reject all", "decline", "dismiss", "stop"} - for b in btns: - txt = b.get("text", "").lower().strip() - if txt not in reject_words: - approve_btn_index = b.get("index", 0) - break - except (json.JSONDecodeError, OSError): - pass - - self.bridge.write_response(UserResponse( - request_id=req.request_id, - approved=True, - button_index=approve_btn_index, - step_type=getattr(req, 'step_type', ''), - project_name=project, - )) - logger.info(f"SafeToAutoRun (Quietly Auto-approved): {req.request_id[:12]} project={project}") - phase1_processed += 1 - continue - - # ── Auto-approve: if project has auto enabled, approve immediately ── - if project in self.auto_approve_projects: - # Defence: reject-word commands should NEVER be auto-approved - # (DOM observer may create standalone "Deny" pending from file_permission UI) - reject_commands = {"deny", "reject", "cancel", "decline", "dismiss", "stop"} - if req.command.strip().lower() in reject_commands: - logger.warning(f"Auto-approve BLOCKED: command='{req.command}' is reject-word — skipping") - self._cap_dict(self._sent_approval_ids) - self._sent_approval_ids[req.request_id] = True - phase1_processed += 1 - continue - - self._cap_dict(self._sent_approval_ids) - self._sent_approval_ids[req.request_id] = True - - # Smart button_index: read buttons array from pending file - # file_permission buttons = [Allow Once(0), Allow This Conv(1), Deny(2)] - # MUST pick non-reject button for safety - approve_btn_index = 0 - pending_file = self.bridge.pending_dir / f"{req.request_id}.json" - if pending_file.exists(): - try: - pdata = json.loads(pending_file.read_text(encoding="utf-8-sig")) - btns = pdata.get("buttons") - if btns and len(btns) > 1: - reject_words = {"deny", "reject", "cancel", "reject all", - "decline", "dismiss", "stop"} - for b in btns: - txt = b.get("text", "").lower().strip() - if txt not in reject_words: - approve_btn_index = b.get("index", 0) - break - except (json.JSONDecodeError, OSError): - pass - - # Write auto-approve response for Extension - self.bridge.write_response(UserResponse( - request_id=req.request_id, - approved=True, - button_index=approve_btn_index, - step_type=getattr(req, 'step_type', ''), - project_name=project, - )) - # Show compact auto-approved embed in Discord - channel = await self._get_channel(project) - if channel: - try: - embed = discord.Embed( - title="🤖 자동 승인됨", - description=f"✅ **{req.command}**\n\n```\n{req.description[:2000]}\n```" if getattr(req, "description", "") else f"✅ **{req.command}**", - color=discord.Color.green(), - ) - embed.set_footer(text=f"auto-approve | {req.request_id[:12]}") - await channel.send(embed=embed) - except Exception as e: - logger.error(f"[AUTO-APPROVE] Discord send failed for {project}: {e}") - else: - logger.warning(f"[AUTO-APPROVE] No Discord channel for project={project} — notification skipped") - logger.info(f"Auto-approved: {req.request_id[:12]} project={project} btn_idx={approve_btn_index}") - phase1_processed += 1 - continue - - # Defer short-command pendings (e.g. "Run") by 4 cycles (~12s) - # to give step_probe time to merge detailed command info - # (step_probe MERGE happens ~10s after pending creation) - if len(req.command) <= 15: - if req.request_id not in self._deferred_ids: - self._deferred_ids[req.request_id] = 1 - continue # skip this cycle - elif self._deferred_ids[req.request_id] < 4: - self._deferred_ids[req.request_id] += 1 - # Re-read from file (step_probe may have merged) - fresh = self.bridge.read_pending_request(req.request_id) - if fresh and len(fresh.command) > 15: - req = fresh # use merged version — send now! - else: - continue # wait one more cycle - - # Clean up defer tracking - self._deferred_ids.pop(req.request_id, None) - - channel = await self._get_channel(project) - if channel: - self._cap_dict(self._sent_approval_ids) - self._sent_approval_ids[req.request_id] = True - self._cap_dict(self._sent_commands) - self._sent_commands[req.request_id] = req.command - await self._send_approval_request(channel, req) - phase1_processed += 1 - else: - logger.warning(f"[APPROVAL] No Discord channel for project={project} — approval request skipped (rid={req.request_id[:12]})") - - # ── Single-pass: handle auto_resolved, expired, and MERGE in one glob ── - phase2_processed = 0 - for f in self.bridge.pending_dir.glob("*.json"): - if phase2_processed >= MAX_STATUS_PER_TICK: - break - try: - data = json.loads(f.read_text(encoding="utf-8-sig")) - status = data.get("status", "pending") - rid = data.get("request_id", "") - - if status == "auto_resolved": - # FIX #5: Use _approval_messages as fallback when discord_message_id is 0 - msg_id = data.get("discord_message_id", 0) or self._approval_messages.get(rid, 0) - project = data.get("project_name", Config.PROJECT_NAME) - logger.info(f"[AUTO-RESOLVED] rid={rid[:12]} project={project} msg_id={msg_id} cmd='{data.get('command', '')[:60]}'") - if msg_id: - channel = await self._get_channel(project) - if channel: - try: - msg = await channel.fetch_message(msg_id) - embed = discord.Embed( - title="✅ AG에서 직접 승인됨", - description=f"```\n{data.get('command', '')[:500]}\n```", - color=discord.Color.green(), - ) - embed.set_footer(text=f"ID: {rid}") - await msg.edit(embed=embed, view=None) - logger.info(f"[AUTO-RESOLVED] ✅ Discord message {msg_id} updated") - except discord.NotFound: - logger.warning(f"[AUTO-RESOLVED] Discord message {msg_id} not found") - else: - logger.warning(f"[AUTO-RESOLVED] No msg_id for rid={rid[:12]} — cannot edit Discord message") - f.unlink() - self._deferred_ids.pop(rid, None) - self._sent_commands.pop(rid, None) - self._approval_messages.pop(rid, None) - self._sent_approval_ids.pop(rid, None) - phase2_processed += 1 - - elif status == "expired": - msg_id = data.get("discord_message_id", 0) - project = data.get("project_name", Config.PROJECT_NAME) - if msg_id: - channel = await self._get_channel(project) - if channel: - try: - msg = await channel.fetch_message(msg_id) - embed = discord.Embed( - title="⏰ 만료됨", - description=f"```\n{data.get('command', '')[:500]}\n```", - color=discord.Color.light_grey(), - ) - embed.set_footer(text=f"ID: {rid}") - await msg.edit(embed=embed, view=None) - except discord.NotFound: - pass - f.unlink() - self._deferred_ids.pop(rid, None) - self._sent_commands.pop(rid, None) - self._sent_approval_ids.pop(rid, None) - phase2_processed += 1 - - elif status == "pending": - # MERGE check: step_probe updated command in already-sent pending - if rid not in self._sent_approval_ids: - continue - msg_id = data.get("discord_message_id", 0) - if not msg_id: - continue - new_cmd = data.get("command", "") - old_cmd = self._sent_commands.get(rid, "") - if new_cmd and new_cmd != old_cmd and len(new_cmd) > len(old_cmd): - self._sent_commands[rid] = new_cmd - project = data.get("project_name", Config.PROJECT_NAME) - channel = await self._get_channel(project) - if channel: - try: - msg = await channel.fetch_message(msg_id) - buttons = data.get("buttons") - desc_parts = [f"**명령어:**\n```\n{new_cmd[:1000]}\n```"] - if buttons and len(buttons) > 1: - btn_names = [b.get("text", "?") for b in buttons] - desc_parts.append(f"**선택지:** {' / '.join(btn_names)}") - desc = data.get("description", "") - if desc: - desc_parts.append(desc[:500]) - embed = discord.Embed( - title="⚠️ 승인 요청", - description="\n".join(desc_parts), - color=discord.Color.orange(), - timestamp=datetime.now(timezone.utc), - ) - embed.set_footer(text=f"ID: {rid}") - await msg.edit(embed=embed) - logger.info(f"MERGE edit: {rid[:12]} cmd='{new_cmd[:60]}'") - except discord.NotFound: - pass - - except (json.JSONDecodeError, OSError): - pass - - except Exception as e: - logger.error(f"Error scanning approvals: {e}") - - @pending_approval_scanner.before_loop - async def before_scanner(self): - await self.wait_until_ready() async def _send_approval_request( self, channel: discord.TextChannel, request: ApprovalRequest @@ -1133,9 +805,8 @@ class GravityBot(commands.Bot): self._cap_dict(self._sent_approval_ids) self._sent_approval_ids[request.request_id] = True - delivered = False if self.hub: - delivered = await self.hub.send_response_to_pending_owner(request.request_id, { + await self.hub.send_response_to_pending_owner(request.request_id, { "type": "response", "data": { "request_id": request.request_id, @@ -1145,13 +816,6 @@ class GravityBot(commands.Bot): "project_name": request.project_name, }, }) - if not delivered: - # File bridge fallback (Hub unavailable OR owner disconnected) - self.bridge.write_response(UserResponse( - request_id=request.request_id, approved=True, - step_type=request.step_type, - project_name=request.project_name, - )) # Send compact auto-approved embed to Discord (was missing — caused silent approvals) channel = await self._get_channel(request.project_name) if channel: @@ -1282,114 +946,4 @@ class GravityBot(commands.Bot): # ─── Chat Snapshot Scanner ───────────────────────────────────────── - @tasks.loop(seconds=30) # Hub mode: WS is primary, file scan is fallback only - async def chat_snapshot_scanner(self): - """Scan bridge/chat_snapshots/ for AI response dumps.""" - try: - snapshot_dir = self.bridge.bridge_dir / "chat_snapshots" - if not snapshot_dir.exists(): - return - for f in snapshot_dir.glob("*.json"): - try: - data = json.loads(f.read_text(encoding="utf-8-sig")) - project = data.get("project_name", Config.PROJECT_NAME) - content = data.get("content", "") - attached_files = data.get("attached_files", []) - - if content or attached_files: - channel = await self._get_channel(project) - if not channel: - logger.warning(f"[SNAPSHOT] No Discord channel for project={project} — snapshot skipped (len={len(content)})") - elif channel: - import io - - # ── Send attached files (from Extension's writeChatSnapshotWithFiles) ── - discord_files = [] - for af in attached_files: - af_name = af.get("name", "document.md") - af_content = af.get("content", "") - if af_content: - discord_files.append(discord.File( - io.BytesIO(af_content.encode("utf-8")), - filename=af_name, - )) - - FILE_ATTACH_THRESHOLD = 4000 - if len(content) > FILE_ATTACH_THRESHOLD: - # Long chat content → summary embed + file attachment - summary = content[:500].rsplit('\n', 1)[0] - embed = discord.Embed( - title="💬 AI 대화 내용", - description=f"{summary}\n\n📎 *전체 내용은 첨부 파일 참조* ({len(content):,}자)", - color=discord.Color.purple(), - timestamp=datetime.now(timezone.utc), - ) - # Add content itself as file attachment - discord_files.append(discord.File( - io.BytesIO(content.encode("utf-8")), - filename="chat_message.md", - )) - try: - await channel.send(embed=embed, files=discord_files) - logger.info(f"[SNAPSHOT] Sent to #{channel.name} (file, {len(content)} chars)") - except discord.NotFound: - logger.warning(f"Channel deleted for {project}, re-creating...") - self.project_channels.pop(project, None) - channel = await self._get_channel(project) - if channel: - # Re-create files (discord.File consumed after send) - discord_files2 = [] - for af in attached_files: - af_name = af.get("name", "document.md") - af_content = af.get("content", "") - if af_content: - discord_files2.append(discord.File( - io.BytesIO(af_content.encode("utf-8")), - filename=af_name, - )) - discord_files2.append(discord.File( - io.BytesIO(content.encode("utf-8")), - filename="chat_message.md", - )) - await channel.send(embed=embed, files=discord_files2) - logger.info(f"[SNAPSHOT] Sent to #{channel.name} after re-create (file, {len(content)} chars)") - except Exception as e: - logger.error(f"[SNAPSHOT] Discord send failed for {project}: {e}") - else: - # Short content → inline embed (original) - embed = discord.Embed( - title="💬 AI 대화 내용", - description=content, - color=discord.Color.purple(), - timestamp=datetime.now(timezone.utc), - ) - try: - await channel.send( - embed=embed, - files=discord_files if discord_files else discord.utils.MISSING, - ) - logger.info(f"[SNAPSHOT] Sent to #{channel.name} (inline, {len(content)} chars)") - except discord.NotFound: - logger.warning(f"Channel deleted for {project}, re-creating...") - self.project_channels.pop(project, None) - channel = await self._get_channel(project) - if channel: - await channel.send(embed=embed) - logger.info(f"[SNAPSHOT] Sent to #{channel.name} after re-create (inline)") - except Exception as e: - logger.error(f"[SNAPSHOT] Discord send failed for {project}: {e}") - - f.unlink() # Cleanup - except Exception as e: - logger.warning(f"Bad chat snapshot {f.name}: {e}") - try: - f.rename(f.with_suffix('.json.failed')) - except OSError: - pass - except Exception as e: - logger.error(f"Error scanning chat snapshots: {e}") - - @chat_snapshot_scanner.before_loop - async def before_chat_scanner(self): - await self.wait_until_ready() diff --git a/bridge.py b/bridge.py deleted file mode 100644 index 95cdb21..0000000 --- a/bridge.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Bridge protocol — 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 -import uuid -from abc import ABC, abstractmethod -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 - project_name: str = "" # Project routing key - step_type: str = "" # e.g. 'diff_review', passed through to response - safe_to_auto_run: bool = False # Allows bot to silently auto-approve - - - -@dataclass -class UserResponse: - """A user response from Discord.""" - request_id: str - approved: bool - user_input: str = "" - timestamp: float = 0 - button_index: int = -1 # -1 = legacy (approve/reject), 0+ = specific button index - step_type: str = "" # pass through from pending for extension routing - project_name: str = "" # for multi-project: extension uses this when pending file is missing - - -# ─── Transport Abstraction ─── - -class BridgeTransport(ABC): - """Abstract transport for bridge I/O. - - Implementations handle reading/writing JSON files for the bridge protocol, - regardless of whether the storage is local filesystem or remote HTTP. - """ - - @abstractmethod - def list_json_files(self, subdir: str) -> list[str]: - """List JSON filenames in a subdirectory (e.g. 'pending', 'response').""" - ... - - @abstractmethod - def read_json(self, subdir: str, filename: str) -> dict | None: - """Read and parse a JSON file. Returns None if not found or corrupt.""" - ... - - @abstractmethod - def write_json(self, subdir: str, filename: str, data: dict) -> None: - """Write data as JSON to a file in the given subdirectory.""" - ... - - @abstractmethod - def delete_file(self, subdir: str, filename: str) -> bool: - """Delete a file. Returns True if deleted, False if not found.""" - ... - - @abstractmethod - def ensure_dirs(self) -> None: - """Ensure all required subdirectories exist.""" - ... - - -class LocalTransport(BridgeTransport): - """File-system based transport (default, single-PC mode). - - Reads/writes directly to the bridge directory on local disk. - This is the existing behavior, extracted into a transport class. - """ - - def __init__(self, bridge_dir: Path): - self.bridge_dir = bridge_dir - - def list_json_files(self, subdir: str) -> list[str]: - d = self.bridge_dir / subdir - if not d.exists(): - return [] - return [f.name for f in d.glob("*.json")] - - def read_json(self, subdir: str, filename: str) -> dict | None: - fp = self.bridge_dir / subdir / filename - if not fp.exists(): - return None - try: - return json.loads(fp.read_text(encoding="utf-8-sig")) - except (json.JSONDecodeError, OSError) as e: - logger.warning(f"LocalTransport: bad file {subdir}/{filename}: {e}") - return None - - def write_json(self, subdir: str, filename: str, data: dict) -> None: - d = self.bridge_dir / subdir - d.mkdir(parents=True, exist_ok=True) - fp = d / filename - fp.write_text( - json.dumps(data, ensure_ascii=False, indent=2), - encoding="utf-8", - ) - - def delete_file(self, subdir: str, filename: str) -> bool: - fp = self.bridge_dir / subdir / filename - if fp.exists(): - try: - fp.unlink() - return True - except OSError: - return False - return False - - def ensure_dirs(self) -> None: - for sub in ("pending", "response", "commands"): - (self.bridge_dir / sub).mkdir(parents=True, exist_ok=True) - - - -# ─── Bridge Protocol (uses Transport) ─── - -class BridgeProtocol: - """Manages the bridge protocol via a pluggable transport.""" - - def __init__(self, transport: BridgeTransport | None = None): - if transport is None: - bridge_dir = Config.BRAIN_PATH.parent / "bridge" - transport = LocalTransport(bridge_dir) - self.transport = transport - - # Legacy attributes for backward compatibility - # (bot.py uses self.bridge.pending_dir etc. in some places) - if isinstance(transport, LocalTransport): - self.bridge_dir = transport.bridge_dir - self.pending_dir = transport.bridge_dir / "pending" - self.response_dir = transport.bridge_dir / "response" - self.commands_dir = transport.bridge_dir / "commands" - - # Ensure directories exist - self.transport.ensure_dirs() - - # Startup cleanup: purge stale pending files (> 5 min old) - self._cleanup_stale_pending() - - logger.info(f"Bridge protocol initialized: transport={type(transport).__name__}") - - def _cleanup_stale_pending(self, max_age_seconds: int = 300): - """Remove pending files older than max_age_seconds on startup.""" - now = time.time() - cleaned = 0 - for fname in self.transport.list_json_files("pending"): - data = self.transport.read_json("pending", fname) - if data is None: - self.transport.delete_file("pending", fname) - cleaned += 1 - continue - ts = data.get("timestamp", 0) - if now - ts > max_age_seconds: - self.transport.delete_file("pending", fname) - cleaned += 1 - if cleaned: - logger.info(f"Startup cleanup: removed {cleaned} stale pending files") - - def get_pending_requests(self) -> list[ApprovalRequest]: - """Read all pending approval requests. Skips files older than 30 minutes.""" - requests = [] - fields = {f.name for f in ApprovalRequest.__dataclass_fields__.values()} - now = time.time() - MAX_AGE = 1800 # 30 minutes (matches Discord button timeout) - CLEANUP_AGE = 86400 # 1 day - for fname in self.transport.list_json_files("pending"): - data = self.transport.read_json("pending", fname) - if data is None: - continue - ts = data.get("timestamp", 0) - if now - ts > CLEANUP_AGE: - # Too old even to keep as expired — delete to prevent accumulation - self.transport.delete_file("pending", fname) - continue - if now - ts > MAX_AGE: - # Too old — mark expired and skip - if data.get("status") != "expired": - data["status"] = "expired" - self.transport.write_json("pending", fname, data) - continue - if data.get("status") == "pending": - # Filter to known fields only - filtered = {k: v for k, v in data.items() if k in fields} - try: - requests.append(ApprovalRequest(**filtered)) - except TypeError as e: - logger.warning(f"Bad pending request {fname}: {e}") - return requests - - def read_pending_request(self, request_id: str) -> ApprovalRequest | None: - """Re-read a specific pending request (to get merged data).""" - fname = f"{request_id}.json" - data = self.transport.read_json("pending", fname) - if data is None: - return None - fields = {fn.name for fn in ApprovalRequest.__dataclass_fields__.values()} - filtered = {k: v for k, v in data.items() if k in fields} - try: - return ApprovalRequest(**filtered) - except TypeError: - return None - - def write_response(self, response: UserResponse): - """Write a user response to the response directory.""" - response.timestamp = time.time() - fname = f"{response.request_id}.json" - - self.transport.write_json("response", fname, asdict(response)) - logger.info(f"Response written: {fname} (approved={response.approved})") - - # Delete pending file after processing (prevents re-processing and accumulation) - self.transport.delete_file("pending", fname) - - def write_command(self, conversation_id: str, text: str, *, project_name: str = ""): - """Write a user text command for Antigravity to consume.""" - cmd_id = f"{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}" - fname = f"{cmd_id}.json" - - data = { - "id": cmd_id, - "conversation_id": conversation_id, - "project_name": project_name, - "text": text, - "timestamp": time.time(), - "consumed": False, - } - - self.transport.write_json("commands", fname, data) - logger.info(f"Command written: {cmd_id} → project={project_name}") - return cmd_id diff --git a/docs/devlog/2026-04-10.md b/docs/devlog/2026-04-10.md index 8348f27..2c9d00a 100644 --- a/docs/devlog/2026-04-10.md +++ b/docs/devlog/2026-04-10.md @@ -1,4 +1,4 @@ | NNN | 시간 | 작업 설명 | 커밋해시 | 완료여부 | |---|---|---|---|---| | 001 | 17:11 | step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스 | COMMITTING | ✅ | -| #613 | 21:10 | Bridge Relay AI Chat Body DOM extraction and template literal Regex fix | TBD | ? | +| #613 | 21:10 | Bridge Relay AI Chat Body DOM extraction and template literal Regex fix | TBD | ✅ | diff --git a/docs/devlog/2026-04-11.md b/docs/devlog/2026-04-11.md new file mode 100644 index 0000000..8770d72 --- /dev/null +++ b/docs/devlog/2026-04-11.md @@ -0,0 +1,5 @@ +# 2026-04-11 + +| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 | +|-------|-------|----------|-----------|-----------| +| 001 | 13:04 | Pure 웹소켓 게이트웨이 완전 전환 및 레거시 파일 브릿지 통신코드 삭제 | `TBD` | ✅ | diff --git a/docs/devlog/entries/20260411-001.md b/docs/devlog/entries/20260411-001.md new file mode 100644 index 0000000..4e9f4b6 --- /dev/null +++ b/docs/devlog/entries/20260411-001.md @@ -0,0 +1,13 @@ +# Pure 웹소켓 게이트웨이 전환 (Legacy 파일 브릿지 통신 완전히 제거) + +- **시간**: 2026-04-11 11:00~13:00 +- **Commit**: `(To be updated)` +- **Vikunja**: #N/A + +## 결정 사항 +- 기존 VS Code 익스텐션과 로컬 Discord Bot 간에 이루어지던 `.gemini/antigravity/bridge/` 기반 파일 공유 통신 체계를 100% 제거하였습니다. +- 파이썬 봇 서버(`bot.py`) 내부에서 동작하던 물리적인 폴링 디렉토리 스캐너(`pending_approval_scanner` 및 `chat_snapshot_scanner`) 파일 디펜던시 루프를 완전히 삭제하고 `Hub` WS 핸들러로 대체했습니다. 봇 패키지에 남아있던 `bridge.py`와 `watcher.py` 또한 사용할 이유가 없어져 레포지토리에서 영구적으로 폐기 구별을 내렸습니다. + +## 새로 알게된 사실 혹은 트러블슈팅 +- 익스텐션에서 `activeSessionId` 변경 시 `watcher.py` 대신 Node.js 네이티브 `fs.watch` 기반으로 자체적인 `BrainWatcher`를 인하우스로 구현해 `step-probe.ts`에 주입함으로써 파이썬 의존도를 완전히 분리할 수 있었습니다. +- 권한 팝업 중복 처리 역시 폴더 스캔 대신 단순히 인메모리 `lastFilePermissionTime` 단일 변수로 최적화되었습니다. diff --git a/extension/gravity-bridge-0.1.0.vsix b/extension/gravity-bridge-0.1.0.vsix deleted file mode 100644 index 1be984f..0000000 Binary files a/extension/gravity-bridge-0.1.0.vsix and /dev/null differ diff --git a/extension/out/extension.js b/extension/out/extension.js index aba5d9a..c75b45e 100644 --- a/extension/out/extension.js +++ b/extension/out/extension.js @@ -120,15 +120,6 @@ function detectProjectName() { return 'default'; } // ─── Bridge File I/O ─── -function ensureBridgeDir() { - const dirs = ['', 'response', 'commands', 'chat_snapshots']; - for (const d of dirs) { - const p = path.join(bridgePath, d); - if (!fs.existsSync(p)) { - fs.mkdirSync(p, { recursive: true }); - } - } -} // Module-level activeSessionId so writeChatSnapshot can register sessions lazily let activeSessionId = ''; let activeTrajectoryId = ''; @@ -136,39 +127,16 @@ let activeTrajectoryId = ''; const recentDiscordSentTexts = new Map(); function writeChatSnapshot(text) { try { - // WS route (preferred) — skip file write to prevent duplicate Discord delivery if (wsBridge && wsBridge.isConnected()) { wsBridge.sendChat({ content: text, - conversation_id: activeSessionId, + conversation_id: (0, step_probe_1.getActiveSessionId)(), project_name: projectName, }); logToFile(`[SNAPSHOT-WS] sent (${text.length} chars)`); - if (activeSessionId) { - (0, step_probe_1.writeRegistration)(activeSessionId); + if ((0, step_probe_1.getActiveSessionId)()) { + (0, step_probe_1.writeRegistration)((0, step_probe_1.getActiveSessionId)()); } - return; - } - // File route (fallback — only when WS is NOT connected) - const snapshotDir = path.join(bridgePath, 'chat_snapshots'); - if (!fs.existsSync(snapshotDir)) { - fs.mkdirSync(snapshotDir, { recursive: true }); - } - const id = Date.now().toString(); - const data = { - id, - project_name: projectName, - content: text, - timestamp: Date.now() / 1000, - }; - const filePath = path.join(snapshotDir, `${id}.json`); - fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); - console.log(`Gravity Bridge: chat snapshot written (${text.length} chars) → ${id}.json`); - logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars)`); - logToFile(`[SNAPSHOT] content: ${text.substring(0, 200)}`); - // Lazily register session → project mapping (correct because projectName is per-window) - if (activeSessionId) { - (0, step_probe_1.writeRegistration)(activeSessionId); } } catch (e) { @@ -177,38 +145,17 @@ function writeChatSnapshot(text) { } function writeChatSnapshotWithFiles(text, files) { try { - // WS route (preferred) — skip file write to prevent duplicate Discord delivery if (wsBridge && wsBridge.isConnected()) { wsBridge.sendChat({ content: text, attached_files: files, - conversation_id: activeSessionId, + conversation_id: (0, step_probe_1.getActiveSessionId)(), project_name: projectName, }); logToFile(`[SNAPSHOT-WS] sent with ${files.length} files (${text.length} chars)`); - if (activeSessionId) { - (0, step_probe_1.writeRegistration)(activeSessionId); + if ((0, step_probe_1.getActiveSessionId)()) { + (0, step_probe_1.writeRegistration)((0, step_probe_1.getActiveSessionId)()); } - return; - } - // File route (fallback — only when WS is NOT connected) - const snapshotDir = path.join(bridgePath, 'chat_snapshots'); - if (!fs.existsSync(snapshotDir)) { - fs.mkdirSync(snapshotDir, { recursive: true }); - } - const id = Date.now().toString(); - const data = { - id, - project_name: projectName, - content: text, - attached_files: files, - timestamp: Date.now() / 1000, - }; - const filePath = path.join(snapshotDir, `${id}.json`); - fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); - logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars, ${files.length} files)`); - if (activeSessionId) { - (0, step_probe_1.writeRegistration)(activeSessionId); } } catch (e) { @@ -405,7 +352,6 @@ async function activate(context) { const config = vscode.workspace.getConfiguration('gravityBridge'); const configPath = config.get('bridgePath'); bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge'); - ensureBridgeDir(); console.log(`Gravity Bridge: bridge path: ${bridgePath}`); // ── WebSocket Hub Connection ── const hubUrl = process.env.GRAVITY_HUB_URL || config.get('hubUrl') || ''; @@ -541,6 +487,7 @@ async function activate(context) { get activeSessionId() { return (0, step_probe_1.getStepProbeContext)().activeSessionId; }, get sessionStalled() { return (0, step_probe_1.getStepProbeContext)().sessionStalled; }, get lastPendingStepIndex() { return (0, step_probe_1.getStepProbeContext)().lastPendingStepIndex; }, + writeChatSnapshot, }; const bridgePort = await (0, http_bridge_1.startHttpBridge)(httpBridgeCtx, sdk); let localPort = bridgePort; diff --git a/extension/out/extension.js.map b/extension/out/extension.js.map index 8be858c..71953f7 100644 --- a/extension/out/extension.js.map +++ b/extension/out/extension.js.map @@ -1 +1 @@ -{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiKH,0CAoJC;AAUD,4BAmQC;AAED,gCAmBC;AArlBD,+CAAiC;AACjC,uCAAyB;AACzB,2CAA6B;AAC7B,uCAAyB;AACzB,kDAAoC;AACpC,2CAA4E;AAC5E,6CAA8R;AAC9R,+CAAyF;AACzF,iDAAuD;AACvD,uDAAqH;AAErH,oDAAoD;AACpD,SAAS,SAAS,CAAC,GAAW;IAC1B,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvE,2FAA2F;IAC3F,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,MAAM,IAAI,GAAG,GAAG,EAAE,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,mBAAmB,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC;QACD,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACvD,mDAAmD;QACnD,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,IAAI,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;gBAC/B,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAClD,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;YACxE,CAAC;QACL,CAAC;QAAC,MAAM,CAAC,CAAC,4BAA4B,CAAC,CAAC;QACxC,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACjE,CAAC;AACL,CAAC;AAED,8CAA8C;AAC9C,IAAI,cAAmB,CAAC;AACxB,IAAI,GAAQ,CAAC;AAEb,IAAI,SAA+B,CAAC;AACpC,IAAI,UAAkB,CAAC;AACvB,IAAI,WAAmB,CAAC;AACxB,IAAI,YAAY,GAAW,EAAE,CAAC,CAAE,kEAAkE;AAClG,IAAI,QAAQ,GAAG,KAAK,CAAC;AACrB,IAAI,kBAAkB,GAAG,KAAK,CAAC,CAAE,iCAAiC;AAClE,IAAI,OAAO,GAAwB,IAAI,CAAC;AACxC,IAAI,QAAQ,GAA0B,IAAI,CAAC,CAAE,2BAA2B;AAGxE,4BAA4B;AAE5B,SAAS,iBAAiB;IACtB,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAS,aAAa,CAAC,CAAC;IACrD,IAAI,UAAU,EAAE,CAAC;QAAC,OAAO,UAAU,CAAC;IAAC,CAAC;IAEtC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC;IAClD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QAClC,IAAI,CAAC;YACD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACvD,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC/B,MAAM,aAAa,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBAC7D,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;gBACzE,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;oBACpB,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;gBAC3D,CAAC;YACL,CAAC;QACL,CAAC;QAAC,MAAM,CAAC,CAAC,CAAC;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC;AAED,0BAA0B;AAI1B,iFAAiF;AACjF,IAAI,eAAe,GAAG,EAAE,CAAC;AACzB,IAAI,kBAAkB,GAAG,EAAE,CAAC;AAC5B,2DAA2D;AAC3D,MAAM,sBAAsB,GAAwB,IAAI,GAAG,EAAE,CAAC;AAE9D,SAAS,iBAAiB,CAAC,IAAY;IACnC,IAAI,CAAC;QACD,IAAI,QAAQ,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;YACrC,QAAQ,CAAC,QAAQ,CAAC;gBACd,OAAO,EAAE,IAAI;gBACb,eAAe,EAAE,IAAA,+BAAqB,GAAE;gBACxC,YAAY,EAAE,WAAW;aAC5B,CAAC,CAAC;YACH,SAAS,CAAC,uBAAuB,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC;YACvD,IAAI,IAAA,+BAAqB,GAAE,EAAE,CAAC;gBAAC,IAAA,8BAAiB,EAAC,IAAA,+BAAqB,GAAE,CAAC,CAAC;YAAC,CAAC;QAChF,CAAC;IACL,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;AACL,CAAC;AAED,SAAS,0BAA0B,CAAC,IAAY,EAAE,KAA+C;IAC7F,IAAI,CAAC;QACD,IAAI,QAAQ,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;YACrC,QAAQ,CAAC,QAAQ,CAAC;gBACd,OAAO,EAAE,IAAI;gBACb,cAAc,EAAE,KAAK;gBACrB,eAAe,EAAE,IAAA,+BAAqB,GAAE;gBACxC,YAAY,EAAE,WAAW;aAC5B,CAAC,CAAC;YACH,SAAS,CAAC,2BAA2B,KAAK,CAAC,MAAM,WAAW,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC;YAClF,IAAI,IAAA,+BAAqB,GAAE,EAAE,CAAC;gBAAC,IAAA,8BAAiB,EAAC,IAAA,+BAAqB,GAAE,CAAC,CAAC;YAAC,CAAC;QAChF,CAAC;IACL,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5E,CAAC;AACL,CAAC;AAGD,6DAA6D;AAE7D,0BAA0B;AAE1B,KAAK,UAAU,OAAO,CAAC,OAAgC;IACnD,IAAI,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;QACzC,cAAc,GAAG,SAAS,CAAC,cAAc,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,gDAAgD,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3E,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAC;QACD,GAAG,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,GAAG,CAAC,UAAU,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QAEjD,0EAA0E;QAC1E,qEAAqE;QACrE,wEAAwE;QACxE,2EAA2E;QAC3E,MAAM,eAAe,EAAE,CAAC;QAExB,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,oCAAoC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/D,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACI,KAAK,UAAU,eAAe;IACjC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC;QAAC,SAAS,CAAC,wCAAwC,CAAC,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;IACpF,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC;QAClD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAAC,SAAS,CAAC,wCAAwC,CAAC,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;QAE5G,wFAAwF;QACxF,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QACrC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAE7E,IAAI,CAAC,IAAI,EAAE,CAAC;YAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;QAEvE,qDAAqD;QACrD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QACpB,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAElC,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,sMAAsM,CAAC;YACxN,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACpE,MAAM,MAAM,GAAG,MAAM,SAAS,CAC1B,yGAAyG,OAAO,EAAE,EAClH,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CACzD,CAAC;YACF,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,SAAS,CAAC,yCAAyC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YACvF,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC5E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAAC,SAAS,CAAC,yCAAyC,CAAC,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;QAC/F,wEAAwE;QACxE,0EAA0E;QAC1E,SAAS,CAAC,kBAAkB,KAAK,CAAC,MAAM,0BAA0B,IAAI,GAAG,CAAC,CAAC;QAE3E,4EAA4E;QAC5E,IAAI,WAAW,GAAkB,IAAI,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACjC,0CAA0C;YAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YACvD,IAAI,OAAO,EAAE,CAAC;gBACV,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;gBACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtB,WAAW,GAAG,IAAI,CAAC;oBACnB,MAAM;gBACV,CAAC;YACL,CAAC;QACL,CAAC;QAED,IAAI,CAAC,WAAW,EAAE,CAAC;YACf,SAAS,CAAC,wCAAwC,IAAI,MAAM,KAAK,CAAC,MAAM,aAAa,CAAC,CAAC;YACvF,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,gDAAgD;QAChD,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC7D,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAEnD,IAAI,CAAC,SAAS,IAAI,CAAC,YAAY,EAAE,CAAC;YAC9B,SAAS,CAAC,gDAAgD,CAAC,CAAC;YAC5D,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QAE1C,4CAA4C;QAC5C,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,SAAS,CAAC,2CAA2C,OAAO,EAAE,CAAC,CAAC;YAChE,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,uDAAuD;QACvD,IAAI,aAAqB,CAAC;QAC1B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAC1B,cAAc,EACd,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CACzD,CAAC;YACF,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;iBACpC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;iBAC5E,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,yDAAyD;YACzD,SAAS,CAAC,4BAA4B,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,OAAO,YAAY,GAAG,EAAE,CAAC,CAAC;YAChH,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;YAChD,SAAS,CAAC,8CAA8C,OAAO,UAAU,IAAI,SAAS,GAAG,EAAE,CAAC,CAAC;YAC7F,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;QAClE,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACL,CAAC;QAED,kDAAkD;QAClD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAErC,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC;YAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;YACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACvB,IAAI,CAAC;oBACD,MAAM,EAAE,GAAG,MAAM,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;wBAC9C,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CACnB,GAAG,KAAK,gBAAgB,IAAI,6DAA6D,EACzF;4BACI,MAAM,EAAE,MAAM;4BACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,CAAC,EAAE;4BACpE,kBAAkB,EAAE,KAAK;4BACzB,OAAO,EAAE,IAAI;yBAChB,EACD,CAAC,GAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,CAC1E,CAAC;wBACF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;wBACtC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;wBAC5D,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;wBAChB,GAAG,CAAC,GAAG,EAAE,CAAC;oBACd,CAAC,CAAC,CAAC;oBAEH,IAAI,EAAE,EAAE,CAAC;wBACL,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;wBAC9C,SAAS,CAAC,8CAA8C,IAAI,IAAI,KAAK,UAAU,IAAI,SAAS,GAAG,EAAE,CAAC,CAAC;wBACnG,OAAO,IAAI,CAAC;oBAChB,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;YAC9B,CAAC;QACL,CAAC;QAED,yCAAyC;QACzC,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QAChD,SAAS,CAAC,uCAAuC,OAAO,UAAU,IAAI,SAAS,GAAG,EAAE,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAChB,SAAS,CAAC,mBAAmB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5C,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,oFAAoF;AAEpF,2DAA2D;AAE3D,2FAA2F;AAIpF,KAAK,UAAU,QAAQ,CAAC,OAAgC;IAC3D,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAE7C,oBAAoB;IACpB,WAAW,GAAG,iBAAiB,EAAE,CAAC;IAClC,6FAA6F;IAC7F,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC;IAClD,YAAY,GAAG,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,4BAA4B,WAAW,gBAAgB,YAAY,GAAG,CAAC,CAAC;IAEpF,cAAc;IACd,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAS,YAAY,CAAC,CAAC;IACpD,UAAU,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;IAEvF,OAAO,CAAC,GAAG,CAAC,gCAAgC,UAAU,EAAE,CAAC,CAAC;IAE1D,iCAAiC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,MAAM,CAAC,GAAG,CAAS,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,MAAM,CAAC,GAAG,CAAS,kBAAkB,CAAC,IAAI,EAAE,CAAC;IACtG,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC7B,IAAI,MAAM,EAAE,CAAC;QACT,QAAQ,GAAG,IAAI,0BAAc,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE;YAChE,UAAU,EAAE,CAAC,IAAoB,EAAE,EAAE;gBACjC,SAAS,CAAC,iBAAiB,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,IAAI,CAAC,QAAQ,cAAc,IAAI,CAAC,SAAS,IAAI,QAAQ,EAAE,CAAC,CAAC;gBAClI,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;gBACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;gBAEtC,8DAA8D;gBAC9D,qEAAqE;gBACrE,uEAAuE;gBACvE,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;oBAC7B,SAAS,CAAC,0EAA0E,CAAC,CAAC;oBACtF,IAAA,qCAAwB,EAAC;wBACrB,UAAU,EAAE,IAAI,CAAC,UAAU;wBAC3B,QAAQ;wBACR,YAAY,EAAE,IAAI,CAAC,YAAY;wBAC/B,SAAS,EAAE,QAAQ;qBACtB,CAAC;yBACG,IAAI,CAAC,MAAM,CAAC,EAAE;wBACX,SAAS,CAAC,qCAAqC,MAAM,EAAE,CAAC,CAAC;wBACzD,IAAA,8BAAiB,GAAE,CAAC;oBACxB,CAAC,CAAC;yBACD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,oCAAoC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBAChF,OAAO;gBACX,CAAC;gBAED,0CAA0C;gBAC1C,MAAM,WAAW,GAAG,IAAA,+BAAkB,GAAE,CAAC;gBACzC,SAAS,CAAC,+CAA+C,QAAQ,YAAY,WAAW,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,aAAa,QAAQ,cAAc,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC;gBAC9K,IAAA,kCAAqB,EAAC,QAAQ,EAAE,WAAW,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,SAAS,CAAC;qBAClF,IAAI,CAAC,MAAM,CAAC,EAAE;oBACX,SAAS,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC;oBACtD,IAAA,8BAAiB,GAAE,CAAC;gBACxB,CAAC,CAAC;qBACD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,iCAAiC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACjF,CAAC;YACD,SAAS,EAAE,CAAC,IAAmB,EAAE,EAAE;gBAC/B,SAAS,CAAC,YAAY,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;gBACrD,IAAA,iCAAe,EAAC;oBACZ,UAAU,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,kBAAkB,EAAE,SAAS;oBACxE,oBAAoB,EAAE,CAAC,OAAgB,EAAE,EAAE,GAAG,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;oBAC7E,sBAAsB;oBACtB,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAA,+BAAqB,GAAE;iBACpD,EAAE,IAAI,CAAC,CAAC;YACb,CAAC;YACD,gBAAgB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;gBACnC,SAAS,CAAC,iBAAiB,KAAK,mBAAmB,CAAC,CAAC;YACzD,CAAC;YACD,WAAW,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE;gBACxC,SAAS,CAAC,mBAAmB,MAAM,cAAc,WAAW,EAAE,CAAC,CAAC;gBAChE,SAAS,CAAC,IAAI,GAAG,oBAAoB,CAAC;gBACtC,SAAS,CAAC,OAAO,GAAG,mBAAmB,WAAW,SAAS,WAAW,GAAG,CAAC;gBAC1E,0EAA0E;gBAC1E,IAAA,0CAA6B,GAAE,CAAC;YACpC,CAAC;YACD,cAAc,EAAE,GAAG,EAAE;gBACjB,SAAS,CAAC,yCAAyC,CAAC,CAAC;gBACrD,SAAS,CAAC,IAAI,GAAG,0BAA0B,CAAC;YAChD,CAAC;YACD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACb,SAAS,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;YACjC,CAAC;SACJ,EAAE,SAAS,CAAC,CAAC;QACd,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnB,SAAS,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;SAAM,CAAC;QACJ,SAAS,CAAC,sEAAsE,CAAC,CAAC;IACtF,CAAC;IAED,oFAAoF;IACpF,8EAA8E;IAC9E,SAAS,CAAC,mBAAmB,WAAW,SAAS,OAAO,CAAC,GAAG,iCAAiC,CAAC,CAAC;IAE/F,aAAa;IACb,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnF,SAAS,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACvC,SAAS,CAAC,OAAO,GAAG,mBAAmB,WAAW,EAAE,CAAC;IACrD,SAAS,CAAC,IAAI,EAAE,CAAC;IACjB,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtC,iBAAiB;IACjB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAExC,IAAI,QAAQ,EAAE,CAAC;QACX,qCAAqC;QACrC,8EAA8E;QAC9E,IAAI,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;YAC3E,SAAS,CAAC,iDAAiD,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YAC5E,6CAA6C;YAC7C,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;YACpH,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAC7C,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAC5D,CAAC;YACF,SAAS,CAAC,8CAA8C,YAAY,CAAC,MAAM,IAAI,CAAC,CAAC;YACjF,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;gBAC7B,SAAS,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;YAC5C,CAAC;YACD,4CAA4C;YAC5C,SAAS,CAAC,6CAA6C,CAAC,CAAC;YACzD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;gBACvB,SAAS,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;YAC1C,CAAC;QACL,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,SAAS,CAAC,0BAA0B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,qDAAqD;QACrD,IAAA,0BAAa,EAAC;YACV,UAAU;YACV,WAAW;YACX,GAAG;YACH,QAAQ;YACR,eAAe;YACf,cAAc,EAAE,KAAK;YACrB,oBAAoB,EAAE,CAAC,CAAC;YACxB,WAAW,EAAE,KAAK;YAClB,sBAAsB,EAAE,IAAI;YAC5B,eAAe,EAAE,CAAC,MAA4B,EAAE,EAAE;gBAC9C,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;gBACjE,UAAU,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC;YACD,SAAS;YACT,YAAY;YACZ,kBAAkB,EAAE,IAAI,GAAG,EAAE;YAC7B,sBAAsB;YACtB,iBAAiB;YACjB,0BAA0B;YAC1B,eAAe;SACD,CAAC,CAAC;QACpB,8EAA8E;QAC9E,MAAM,aAAa,GAAsB;YACrC,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS;YAC5C,IAAI,eAAe,KAAK,OAAO,IAAA,gCAAmB,GAAE,CAAC,eAAe,CAAC,CAAC,CAAC;YACvE,IAAI,cAAc,KAAK,OAAO,IAAA,gCAAmB,GAAE,CAAC,cAAc,CAAC,CAAC,CAAC;YACrE,IAAI,oBAAoB,KAAK,OAAO,IAAA,gCAAmB,GAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC;YACjF,iBAAiB;SACpB,CAAC;QACF,MAAM,UAAU,GAAG,MAAM,IAAA,6BAAe,EAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7D,IAAI,SAAS,GAAG,UAAU,CAAC;QAC3B,IAAI,UAAU,EAAE,CAAC;YACb,IAAI,CAAC;gBACD,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,oBAAoB,UAAU,EAAE,CAAC,CAAC,CAAC;gBACvG,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBACrD,IAAI,KAAK,EAAE,CAAC;oBAAC,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAAC,CAAC;YACtD,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBACd,SAAS,CAAC,oCAAoC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/D,CAAC;YACD,MAAM,IAAA,oCAAqB,EAAC,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACJ,SAAS,CAAC,yDAAyD,CAAC,CAAC;QACzE,CAAC;QACD,SAAS,CAAC,IAAI,GAAG,iBAAiB,CAAC;QACnC,SAAS,CAAC,OAAO,GAAG,iCAAiC,SAAS,cAAc,WAAW,EAAE,CAAC;QAE1F,gCAAgC;QAChC,OAAO,CAAC,aAAa,CAAC,IAAI,CACtB,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;YAChE,IAAI,CAAC;gBACD,MAAM,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;gBAC/B,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,+BAA+B,CAAC,CAAC;YAC1E,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBACd,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YACnE,CAAC;QACL,CAAC,CAAC,EACF,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;YAC/D,IAAI,CAAC;gBACD,MAAM,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;gBAC/B,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,+BAA+B,CAAC,CAAC;YAC1E,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBACd,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAClE,CAAC;QACL,CAAC,CAAC,CACL,CAAC;IACN,CAAC;SAAM,CAAC;QACJ,SAAS,CAAC,IAAI,GAAG,4BAA4B,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IAC3E,CAAC;IAED,2BAA2B;IAC3B,IAAA,kCAAgB,EAAC;QACb,UAAU,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,kBAAkB,EAAE,SAAS;QACxE,oBAAoB,EAAE,CAAC,OAAgB,EAAE,EAAE,GAAG,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;QAC7E,sBAAsB;QACtB,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAA,+BAAqB,GAAE;KACpD,CAAC,CAAC;IAEH,+DAA+D;IAC/D,0BAA0B;IAC1B,OAAO,CAAC,aAAa,CAAC,IAAI,CACtB,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACxD,QAAQ,GAAG,IAAI,CAAC;QAChB,SAAS,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAC;QAC1E,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,+BAA+B,WAAW,GAAG,CAAC,CAAC;IACxF,CAAC,CAAC,EACF,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,oBAAoB,EAAE,GAAG,EAAE;QACvD,QAAQ,GAAG,KAAK,CAAC;QACjB,2CAA2C;QAC3C,SAAS,CAAC,IAAI,GAAG,4BAA4B,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,wBAAwB,CAAC,CAAC;IACnE,CAAC,CAAC,EACF,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QAChE,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;YACtD,OAAO;QACX,CAAC;QACD,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;gBACpC,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,UAAU;gBAC5B,WAAW,EAAE,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;gBAC7D,SAAS,EAAE,CAAC,CAAC,EAAE;aAClB,CAAC,CAAC,CAAC;YACJ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE;gBAClD,WAAW,EAAE,kCAAkC;aAClD,CAAC,CAAC;YACH,IAAI,IAAI,EAAE,CAAC;gBACP,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,CAAE,IAAY,CAAC,SAAS,CAAC,CAAC;gBACxD,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,iBAAkB,IAAY,CAAC,KAAK,EAAE,CAAC,CAAC;YACjF,CAAC;QACL,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACnE,CAAC;IACL,CAAC,CAAC,CACL,CAAC;IAEF,UAAU;IACV,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC;QACvB,OAAO,EAAE,GAAG,EAAE;YACV,IAAI,GAAG,EAAE,CAAC;gBAAC,IAAI,CAAC;oBAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,CAAC;YAAC,CAAC;YAC7C,IAAI,OAAO,EAAE,CAAC;gBAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAAC,CAAC;YACjC,IAAA,wCAAsB,GAAE,CAAC;QAC7B,CAAC;KACJ,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC3C,QAAQ,GAAG,IAAI,CAAC;AACpB,CAAC;AAED,SAAgB,UAAU;IACtB,uBAAuB;IACvB,IAAI,QAAQ,EAAE,CAAC;QACX,QAAQ,CAAC,UAAU,EAAE,CAAC;QACtB,QAAQ,GAAG,IAAI,CAAC;IACpB,CAAC;IACD,yDAAyD;IACzD,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;QAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;YAChE,IAAI,QAAQ,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;gBAC/B,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IAAC,MAAM,CAAC,CAAC,CAAC;IACX,IAAI,GAAG,EAAE,CAAC;QACN,IAAI,CAAC;YAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,CAAC;IACpC,CAAC;AACL,CAAC"} \ No newline at end of file diff --git a/extension/package-lock.json b/extension/package-lock.json index 2969844..89f305a 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,82 +1,380 @@ { - "name": "gravity-bridge", - "version": "0.5.25", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "gravity-bridge", - "version": "0.5.25", - "dependencies": { - "ws": "^8.19.0" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@types/vscode": "^1.100.0", - "typescript": "^5.3.0" - }, - "engines": { - "vscode": "^1.100.0" - } - }, - "node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/vscode": { - "version": "1.100.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz", - "integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } + "name": "gravity-bridge", + "version": "0.5.34", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gravity-bridge", + "version": "0.5.34", + "dependencies": { + "cheerio": "^1.2.0", + "ws": "^8.19.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/vscode": "^1.100.0", + "typescript": "^5.3.0" + }, + "engines": { + "vscode": "^1.100.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz", + "integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } + } } diff --git a/extension/package.json b/extension/package.json index 886a260..f97b5cd 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "gravity-bridge", "displayName": "Gravity Bridge", "description": "Discord-based unified approval system for Antigravity AI interactions.", - "version": "0.5.30", + "version": "0.5.36", "publisher": "variet", "engines": { "vscode": "^1.100.0" @@ -84,6 +84,7 @@ } }, "dependencies": { + "cheerio": "^1.2.0", "ws": "^8.19.0" } -} \ No newline at end of file +} diff --git a/extension/src/brain-watcher.ts b/extension/src/brain-watcher.ts new file mode 100644 index 0000000..87db297 --- /dev/null +++ b/extension/src/brain-watcher.ts @@ -0,0 +1,96 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { WSBridgeClient } from './ws-client'; + +export interface BrainWatcherContext { + logToFile: (msg: string) => void; + wsBridge: WSBridgeClient; + projectName: string; +} + +export class BrainWatcher { + private brainDir: string; + private ctx: BrainWatcherContext; + private currentSessionId: string = ''; + private watcher: fs.FSWatcher | null = null; + private lastEventTimes: Map = new Map(); + + constructor(ctx: BrainWatcherContext) { + this.ctx = ctx; + // The bridgePath is ~/.gemini/antigravity/bridge, so brain is sibling + this.brainDir = path.join(os.homedir(), '.gemini', 'antigravity', 'brain'); + } + + public updateSession(sessionId: string) { + if (!sessionId || this.currentSessionId === sessionId) { + return; + } + this.currentSessionId = sessionId; + this.startWatching(sessionId); + } + + private startWatching(sessionId: string) { + this.stop(); + + const sessionDir = path.join(this.brainDir, sessionId); + if (!fs.existsSync(sessionDir)) { + // It might not be created yet, poll gently + setTimeout(() => this.startWatching(sessionId), 2000); + return; + } + + try { + this.watcher = fs.watch(sessionDir, { persistent: false }, (eventType, filename) => { + if (!filename || !filename.endsWith('.md')) return; + + // Dedup rapid events + const now = Date.now(); + const last = this.lastEventTimes.get(filename) || 0; + if (now - last < 500) return; // 500ms debounce + this.lastEventTimes.set(filename, now); + + this.handleFileChange(sessionDir, filename, eventType); + }); + this.ctx.logToFile(`[BRAIN-WATCHER] Started watching session: ${sessionId.substring(0, 8)}`); + } catch (e: any) { + this.ctx.logToFile(`[BRAIN-WATCHER] Failed to watch ${sessionId}: ${e.message}`); + } + } + + private handleFileChange(dir: string, filename: string, rawEventType: string) { + const filePath = path.join(dir, filename); + let content = ''; + let eventType = 'file_changed'; + + try { + if (fs.existsSync(filePath)) { + content = fs.readFileSync(filePath, 'utf-8'); + } else { + eventType = 'file_deleted'; + } + } catch (e) { + // File might be locked or deleted during read + return; + } + + if (this.ctx.wsBridge && this.ctx.wsBridge.isConnected()) { + this.ctx.wsBridge.sendBrainEvent({ + event_type: eventType, + conversation_id: this.currentSessionId, + file_name: filename, + content: content, + timestamp: Date.now() / 1000, + project_name: this.ctx.projectName, + }); + this.ctx.logToFile(`[BRAIN-WATCHER] Sent ${eventType} for ${filename}`); + } + } + + public stop() { + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + } +} diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 37b9ba9..7757b01 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -86,13 +86,7 @@ function detectProjectName(): string { // ─── Bridge File I/O ─── -function ensureBridgeDir() { - const dirs = ['', 'response', 'commands', 'chat_snapshots']; - for (const d of dirs) { - const p = path.join(bridgePath, d); - if (!fs.existsSync(p)) { fs.mkdirSync(p, { recursive: true }); } - } -} + // Module-level activeSessionId so writeChatSnapshot can register sessions lazily let activeSessionId = ''; @@ -102,34 +96,15 @@ const recentDiscordSentTexts: Map = new Map(); function writeChatSnapshot(text: string) { try { - // WS route (preferred) — skip file write to prevent duplicate Discord delivery if (wsBridge && wsBridge.isConnected()) { wsBridge.sendChat({ content: text, - conversation_id: activeSessionId, + conversation_id: getStepProbeSessionId(), project_name: projectName, }); logToFile(`[SNAPSHOT-WS] sent (${text.length} chars)`); - if (activeSessionId) { writeRegistration(activeSessionId); } - return; + if (getStepProbeSessionId()) { writeRegistration(getStepProbeSessionId()); } } - // File route (fallback — only when WS is NOT connected) - const snapshotDir = path.join(bridgePath, 'chat_snapshots'); - if (!fs.existsSync(snapshotDir)) { fs.mkdirSync(snapshotDir, { recursive: true }); } - const id = Date.now().toString(); - const data = { - id, - project_name: projectName, - content: text, - timestamp: Date.now() / 1000, - }; - const filePath = path.join(snapshotDir, `${id}.json`); - fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); - console.log(`Gravity Bridge: chat snapshot written (${text.length} chars) → ${id}.json`); - logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars)`); - logToFile(`[SNAPSHOT] content: ${text.substring(0, 200)}`); - // Lazily register session → project mapping (correct because projectName is per-window) - if (activeSessionId) { writeRegistration(activeSessionId); } } catch (e: any) { console.log(`Gravity Bridge: snapshot write error: ${e.message}`); } @@ -137,33 +112,16 @@ function writeChatSnapshot(text: string) { function writeChatSnapshotWithFiles(text: string, files: Array<{ name: string, content: string }>) { try { - // WS route (preferred) — skip file write to prevent duplicate Discord delivery if (wsBridge && wsBridge.isConnected()) { wsBridge.sendChat({ content: text, attached_files: files, - conversation_id: activeSessionId, + conversation_id: getStepProbeSessionId(), project_name: projectName, }); logToFile(`[SNAPSHOT-WS] sent with ${files.length} files (${text.length} chars)`); - if (activeSessionId) { writeRegistration(activeSessionId); } - return; + if (getStepProbeSessionId()) { writeRegistration(getStepProbeSessionId()); } } - // File route (fallback — only when WS is NOT connected) - const snapshotDir = path.join(bridgePath, 'chat_snapshots'); - if (!fs.existsSync(snapshotDir)) { fs.mkdirSync(snapshotDir, { recursive: true }); } - const id = Date.now().toString(); - const data = { - id, - project_name: projectName, - content: text, - attached_files: files, - timestamp: Date.now() / 1000, - }; - const filePath = path.join(snapshotDir, `${id}.json`); - fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); - logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars, ${files.length} files)`); - if (activeSessionId) { writeRegistration(activeSessionId); } } catch (e: any) { console.log(`Gravity Bridge: snapshot+files write error: ${e.message}`); } @@ -383,7 +341,7 @@ export async function activate(context: vscode.ExtensionContext) { const config = vscode.workspace.getConfiguration('gravityBridge'); const configPath = config.get('bridgePath'); bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge'); - ensureBridgeDir(); + console.log(`Gravity Bridge: bridge path: ${bridgePath}`); // ── WebSocket Hub Connection ── @@ -527,6 +485,7 @@ export async function activate(context: vscode.ExtensionContext) { get activeSessionId() { return getStepProbeContext().activeSessionId; }, get sessionStalled() { return getStepProbeContext().sessionStalled; }, get lastPendingStepIndex() { return getStepProbeContext().lastPendingStepIndex; }, + writeChatSnapshot, }; const bridgePort = await startHttpBridge(httpBridgeCtx, sdk); let localPort = bridgePort; diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts index 1800ac4..a9b2416 100644 --- a/extension/src/http-bridge.ts +++ b/extension/src/http-bridge.ts @@ -13,6 +13,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { WSBridgeClient } from './ws-client'; +let lastFilePermissionTime = 0; + // ─── Context interface (shared state from extension.ts) ─── export interface HttpBridgeContext { @@ -127,7 +129,7 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise = { ...data, request_id: rid, @@ -265,22 +264,13 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { if (cmdLower.includes('allow') && !pending.buttons) { // Dedup: skip if another file_permission pending was created within 10s const nowMs = Date.now(); - try { - const existingFiles = fs.readdirSync(pendingDir).filter((f: string) => f.endsWith('.json')); - for (const ef of existingFiles) { - const existing = JSON.parse(fs.readFileSync(path.join(pendingDir, ef), 'utf-8')); - if (existing.step_type === 'file_permission' && existing.status === 'pending' - && existing.project_name === ctx.projectName) { - const age = nowMs - (existing.timestamp * 1000); - if (age < 10_000 && age >= 0) { - ctx.logToFile(`[HTTP] filtered duplicate file_permission (${age}ms old): ${ef}`); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ ok: false, filtered: true, reason: 'dedup_file_permission' })); - return; - } - } - } - } catch { } + if (nowMs - lastFilePermissionTime < 10000) { + ctx.logToFile(`[HTTP] filtered duplicate file_permission (${nowMs - lastFilePermissionTime}ms old)`); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ ok: false, filtered: true, reason: 'dedup_file_permission' })); + return; + } + lastFilePermissionTime = nowMs; pending.buttons = [ { text: 'Allow Once', index: 0 }, @@ -292,8 +282,7 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { const rawDesc = (data.description || data.command || '').replace(/Deny|Allow Once|Allow This Conversation/gi, '').trim(); pending.command = `파일 접근 권한${rawDesc ? ': ' + rawDesc : ''}`; } - fs.writeFileSync(path.join(pendingDir, `${rid}.json`), JSON.stringify(pending, null, 2)); - // WS dual-write + // WS dispatch if (ctx.wsBridge && ctx.wsBridge.isConnected()) { ctx.wsBridge.sendPending({ request_id: rid, diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index 8e63d72..e220c2a 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -1,6 +1,6 @@ export function generateApprovalObserverScript(_port: number): string { return ` -// ── Gravity Bridge v4: React Tailwind UI Observer ── +// ── Gravity Bridge v5: Context-First DOM Extraction ── (function(){ 'use strict'; var BASE='',_obs=false,_sent={},_ready=false; @@ -9,7 +9,7 @@ export function generateApprovalObserverScript(_port: number): string { var CLEANUP_MS=300000; function log(m){console.log('[GB Observer] '+m);} - log('v4 Script loaded — deep Tailwind DOM traversal enabled'); + log('v5 Script loaded — Context-First Tailored Extraction'); // React-Compatible Synthetic Clicker function dispatchReactClick(el){ @@ -21,19 +21,10 @@ export function generateApprovalObserverScript(_port: number): string { el.dispatchEvent(new MouseEvent('mouseup', {bubbles:true, cancelable:true, view:window, composed:true})); el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window, composed:true})); } catch(e) { - el.click(); // fallback + el.click(); } } - // ── Find common container for the step ── - function findButtonContainer(btn){ - return btn.closest('.p-1') - || btn.closest('.bg-agent-convo-background') - || btn.closest('[class*="border-gray-500/10"]') - || btn.closest('.monaco-list-row') - || btn.parentElement; - } - function cleanButtonText(btn) { if (!btn) return ''; var clone = btn.cloneNode(true); @@ -43,10 +34,9 @@ export function generateApprovalObserverScript(_port: number): string { } var tr = clone.querySelector('.truncate'); var txt = (tr ? tr.textContent : clone.textContent) || ''; - return txt.trim().replace(/^[\s\u200B-\u200D\uFEFF\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\+.*/i,'').trim(); + return txt.trim().replace(/^[\s\u200B-\u200D\uFEFF\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\\\+.*/i,'').trim(); } - // ── Stable button fingerprint ── function btnId(b,type){ var txt = cleanButtonText(b); var parent = b.parentElement; @@ -58,104 +48,78 @@ export function generateApprovalObserverScript(_port: number): string { return type+'|'+txt+'|'+idx; } - // ── Context extraction — target BOTH chat history and command payload ── function extractCommandContext(b){ - var container = findButtonContainer(b); + var container = b.closest('.p-1') || b.parentElement.parentElement; if (!container) return ""; - var titleSpans = container.querySelectorAll('span[title^="command("]'); if (titleSpans && titleSpans.length > 0) { var t = titleSpans[0].getAttribute('title'); if (t && t.length > 5) return t.substring(0, 800); } - var preEls = container.querySelectorAll('pre'); if (preEls && preEls.length > 0) { var t2 = (preEls[preEls.length-1].textContent || '').trim(); if (t2.length > 2) return t2.substring(0, 800); } - - var codeText = ''; - var codes = container.querySelectorAll('code, [class*="command"]'); - for(var i=0; i 2) return codeText.trim().substring(0, 800); - var fallback = (container.textContent || '').replace(cleanButtonText(b), '').trim(); return fallback.substring(0, 500); } + function extractChatContextFromNode(botTurn) { + if (!botTurn) return ''; + + var res = ''; + // Use innerText if available on the markdown container (preserves spacing perfectly) + var md = botTurn.querySelector('.markdown-body') || botTurn.querySelector('.prose'); + if (md && md.innerText && md.innerText.trim().length > 10) { + res = md.innerText.trim(); + return res.substring(0, 3500); + } + + var toolContainer = botTurn.querySelector('.bg-agent-convo-background') || botTurn.querySelector('.bg-ide-background-color'); + var textParts = []; + function walk(node) { + if (toolContainer && node === toolContainer) return; + if (node.id === 'antigravity.agentSidePanelInputBox') return; + if (node.nodeType === 1) { + var tag = node.tagName.toUpperCase(); + if (tag==='BUTTON' || tag==='SVG' || tag==='STYLE' || tag==='SCRIPT') return; + // Skip tool action blocks aggressively if they masquerade as normal divs + if (node !== botTurn && node.classList && (node.classList.contains('bg-ide-background-color') || node.classList.contains('bg-agent-convo-background'))) return; + } + if (node.nodeType === 3) { + var val = node.nodeValue; + if (val && val.trim()) textParts.push(val.trim()); + } else { + if (node.childNodes && node.childNodes.length > 0) { + for(var i=0; i 0) { var text = items[0].getAttribute('aria-label') || items[0].getAttribute('title') || ''; - var m = text.match(/port:(\d+)/); + var m = text.match(/port:(\\d+)/); if (m && m[1]) { - var domPort = parseInt(m[1], 10); clearInterval(timer); - tryPingAsync(domPort).then(function(ok){ - if(ok) cb(domPort); else cb(HARDCODED_PORT); - }); + tryPingAsync(parseInt(m[1], 10)).then(function(ok){ cb(ok ? parseInt(m[1],10) : HARDCODED_PORT); }); return; } } - // If we are in the webview, the status bar is invisible. Skip quickly. if(attempts>1){ clearInterval(timer); - tryPingAsync(HARDCODED_PORT).then(function(ok){ cb(HARDCODED_PORT); }); // Assume HARDCODED_PORT works! + tryPingAsync(HARDCODED_PORT).then(function(){ cb(HARDCODED_PORT); }); } - },500); // Wait 500ms * 2 = 1 second total + },500); } discoverPort(function(port){ BASE='http://127.0.0.1:'+port; - fetch(BASE+'/ping').then(function(r){return r.text();}).then(function(t){ - if(t==='pong'){_ready=true;startObserver();} - }).catch(function(e){}); + _ready=true; + startObserver(); }); - var _chatSnapshots = []; - var _firstChatScan = true; + var _lastText = ""; + var _lastTextTime = 0; + var _lastTextSent = false; + function scanChatBodies() { if(!_ready)return; var botTurns = document.querySelectorAll('.text-ide-message-block-bot-color'); - for (var i = 0; i < botTurns.length; i++) { - var turn = botTurns[i]; - if (turn.dataset.agChatScraped === "true" || turn.dataset.agChatScraped === "pending") continue; - - if (_firstChatScan) { - turn.dataset.agChatScraped = "true"; - continue; - } - - var currentText = turn.textContent || ''; - var found = -1; - for (var j = 0; j < _chatSnapshots.length; j++) { - if (_chatSnapshots[j].node === turn) { found = j; break; } - } - - if (found === -1) { - _chatSnapshots.push({ node: turn, text: currentText, lastChanged: Date.now() }); - } else { - if (_chatSnapshots[found].text !== currentText) { - _chatSnapshots[found].text = currentText; - _chatSnapshots[found].lastChanged = Date.now(); + if (botTurns.length === 0) return; + + var lastTurn = botTurns[botTurns.length - 1]; + if (lastTurn.dataset.agChatScraped === "true" || lastTurn.dataset.agChatScraped === "pending") return; + + var currentText = lastTurn.textContent || ''; + if (currentText.length < 5) return; + + if (_lastText !== currentText) { + _lastText = currentText; + _lastTextTime = Date.now(); + _lastTextSent = false; + } else if (!_lastTextSent) { + if (Date.now() - _lastTextTime > 3000) { + _lastTextSent = true; + lastTurn.dataset.agChatScraped = "pending"; + var finalTxt = extractChatContextFromNode(lastTurn); + if (finalTxt && finalTxt.length > 5 && finalTxt !== "Review Changes") { + fetch(BASE+'/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: finalTxt }) + }).then(function(){ + lastTurn.dataset.agChatScraped = "true"; + }).catch(function(){ + lastTurn.dataset.agChatScraped = "false"; + }); } else { - if (Date.now() - _chatSnapshots[found].lastChanged > 3500) { - turn.dataset.agChatScraped = "pending"; // prevent re-entry - var finalTxt = extractChatContextFromNode(turn); - if (finalTxt && finalTxt.length > 5) { - fetch(BASE+'/chat', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text: finalTxt }) - }).then(function(){ - turn.dataset.agChatScraped = "true"; - }).catch(function(){ - turn.dataset.agChatScraped = "false"; // retry - }); - } else { - turn.dataset.agChatScraped = "true"; - } - } + lastTurn.dataset.agChatScraped = "true"; } } } - _firstChatScan = false; } function scan(){ @@ -301,26 +247,17 @@ export function generateApprovalObserverScript(_port: number): string { if(b.disabled||b.hidden||(!b.offsetParent&&b.style.display!=='fixed'))continue; var txt=cleanButtonText(b); - console.log("[JSDOM] Button scan:", txt); - if(txt.length <= 1) continue; // Icon + if(txt.length <= 1) continue; - var matchedType=null; - for(var p=0;p(); const PENDING_MEMORY_TTL_MS = 60_000; @@ -276,15 +278,16 @@ function setupMonitor() { } catch (e: any) { if (pollCount <= 30) ctx.logToFile(`[POLL] Fallback 2 error for sid=${sid}: ${e.message}`); // If trajectory explicitly does not exist, it might be an Antigravity or non-Cascade session directory. - if (e.message?.includes('trajectory not found')) { - continue; - } - // FIXED: known-issues "AI Response Missing for New Sessions" -> Force register to prevent session loss on proto/UTF-8 parse errors + // We MUST register it so activeSessionId tracks it properly. + // To prevent old ghost sessions from hijacking, we only mark it RUNNING if it was recently modified. + const ageMs = Date.now() - brainDirs[i].time; + const isFresh = ageMs < 120_000; // updated within 2 mins + allTraj.trajectorySummaries[sid] = { - status: 'CASCADE_RUN_STATUS_RUNNING', + status: isFresh ? 'CASCADE_RUN_STATUS_RUNNING' : 'CASCADE_RUN_STATUS_IDLE', stepCount: 1, // Assume progressing to allow loop delta>0 trigger lastModifiedTime: new Date(brainDirs[i].time).toISOString(), - summary: 'Discovered via brain/ scan (Fallback Error)', + summary: 'Discovered via brain/ scan (Antigravity Native)', trajectoryMetadata: { workspaces: [{ workspaceFolderAbsoluteUri: ctx.workspaceUri.replace(/\\/g, '/') }] } }; } @@ -381,6 +384,9 @@ function setupMonitor() { // Session changed? if (bestSessionId !== ctx.activeSessionId) { ctx.activeSessionId = bestSessionId; + if (brainWatcher) { + brainWatcher.updateSession(bestSessionId); + } activeTrajectoryId = (bestSession as any).trajectoryId || ''; activeSessionTitle = currentTitle; lastKnownStepCount = currentCount; @@ -1261,6 +1267,13 @@ export function writePendingApproval(data: { conversation_id: string; command: s */ export function initStepProbe(context: BridgeContext) { ctx = context; + if (ctx.wsBridge) { + brainWatcher = new BrainWatcher({ + logToFile: ctx.logToFile, + wsBridge: ctx.wsBridge, + projectName: ctx.projectName + }); + } initApprovalHandler(context, () => activeTrajectoryId); setupMonitor(); setupResponseWatcher(); diff --git a/main.py b/main.py index ed61ab2..01b4a92 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,6 @@ 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) @@ -51,45 +50,32 @@ async def main(): # Get the running loop loop = asyncio.get_running_loop() - # ── 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 + WebSocket Hub + from gateway import GatewayAPI + from hub import WSHub + from auth import TokenManager - # Start Gateway HTTP API + WebSocket Hub (gateway mode) - if Config.BOT_MODE == 'gateway': - from gateway import GatewayAPI - from hub import WSHub - from auth import TokenManager + # Initialize Hub + token_mgr = TokenManager( + secret=Config.GRAVITY_HUB_SECRET, + registration_code=Config.GRAVITY_REGISTRATION_CODE, + ) + hub = WSHub(token_mgr) - # Initialize Hub - token_mgr = TokenManager( - secret=Config.GRAVITY_HUB_SECRET, - registration_code=Config.GRAVITY_REGISTRATION_CODE, - ) - hub = WSHub(token_mgr) - - gateway_port = int(os.environ.get('GATEWAY_PORT', '8585')) - gateway = GatewayAPI( - bot, port=gateway_port, - api_key=Config.GATEWAY_API_KEY, - hub=hub, - ) - bot.gateway = gateway # Enable _write_command → gateway.push_command - bot.hub = hub # Enable Hub-based message routing - await gateway.start() - logger.info(f"Gateway API + WS Hub running on port {gateway_port}") + gateway_port = int(os.environ.get('GATEWAY_PORT', '8585')) + gateway = GatewayAPI( + bot, port=gateway_port, + api_key=Config.GATEWAY_API_KEY, + hub=hub, + ) + bot.gateway = gateway # Enable _write_command → gateway.push_command + bot.hub = hub # Enable Hub-based message routing + await gateway.start() + logger.info(f"Gateway API + WS Hub running on port {gateway_port}") # Run Discord bot (blocks until bot disconnects) await bot.start(Config.DISCORD_TOKEN) @@ -100,8 +86,6 @@ async def main(): 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") diff --git a/models.py b/models.py new file mode 100644 index 0000000..fcf37ab --- /dev/null +++ b/models.py @@ -0,0 +1,55 @@ +import time +from dataclasses import dataclass, field +from enum import Enum + + +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 + project_name: str = "" # Project routing key + step_type: str = "" # e.g. 'diff_review', passed through to response + safe_to_auto_run: bool = False # Allows bot to silently auto-approve + + +@dataclass +class UserResponse: + """A user response from Discord.""" + request_id: str + approved: bool + user_input: str = "" + timestamp: float = field(default_factory=time.time) + button_index: int = -1 # -1 = legacy (approve/reject), 0+ = specific button index + step_type: str = "" # pass through from pending for extension routing + project_name: str = "" # for multi-project: extension uses this when pending file is missing + + +class EventType(Enum): + """Types of brain events.""" + SESSION_START = "session_start" # New conversation directory created + FILE_CHANGED = "file_changed" # Watched file modified + FILE_CREATED = "file_created" # Watched file first created + + +@dataclass +class BrainEvent: + """An event from the brain directory.""" + event_type: EventType + conversation_id: str + file_name: str = "" + file_path: str = None + content: str = "" + timestamp: float = field(default_factory=time.time) diff --git a/scratch_test.js b/scratch_test.js deleted file mode 100644 index 8b49b42..0000000 --- a/scratch_test.js +++ /dev/null @@ -1,4 +0,0 @@ -const fs = require('fs'); -const lsPath = "C:\\Users\\Variet-Worker\\.gemini\\antigravity\\sdk\\ls.js"; -// Dummy script to just read allTraj.trajectorySummaries by using the SDK directly? -// Actually simpler: I can just grep the log if I log it. diff --git a/start_bot.bat b/start_bot.bat deleted file mode 100644 index b22dba0..0000000 --- a/start_bot.bat +++ /dev/null @@ -1,63 +0,0 @@ -@echo off -chcp 65001 >nul 2>&1 -title Gravity Bridge Bot - -echo ╔══════════════════════════════════════╗ -echo ║ Gravity Bridge Bot Launcher ║ -echo ╚══════════════════════════════════════╝ -echo. - -echo [INFO] 로컬 테스트 (BOT_MODE=local)를 시작합니다. -echo [INFO] 서버 배포는 BOT_MODE=gateway로 실행하세요. -echo. -echo 시작하려면 아무 키나 누르세요... -pause >nul - -REM — Find Python (conda first, then system) -set PYTHON= -if exist "C:\ProgramData\miniforge3\envs\gravity_control\python.exe" ( - set PYTHON=C:\ProgramData\miniforge3\envs\gravity_control\python.exe -) -if "%PYTHON%"=="" ( - where python >nul 2>&1 && set PYTHON=python -) -if "%PYTHON%"=="" ( - echo [ERROR] Python not found. Install Python 3.10+ or set path. - pause - exit /b 1 -) - -REM — Check .env -if not exist "%~dp0.env" ( - echo [SETUP] .env not found. Creating from .env.example... - if exist "%~dp0.env.example" ( - copy "%~dp0.env.example" "%~dp0.env" >nul - echo [SETUP] .env created — edit it with your Discord token and Guild ID. - echo. - notepad "%~dp0.env" - echo Press any key after saving .env... - pause >nul - ) else ( - echo [ERROR] .env.example not found. - pause - exit /b 1 - ) -) - -REM — Install dependencies (first run) -if not exist "%~dp0.deps_installed" ( - echo [SETUP] Installing dependencies... - %PYTHON% -m pip install -r "%~dp0requirements.txt" -q - echo. > "%~dp0.deps_installed" - echo [SETUP] Dependencies installed. -) - -echo [START] Starting bot with %PYTHON%... -echo [START] Press Ctrl+C to stop. -echo. - -%PYTHON% "%~dp0main.py" - -echo. -echo [STOP] Bot stopped. -pause diff --git a/watcher.py b/watcher.py deleted file mode 100644 index 6e2bce3..0000000 --- a/watcher.py +++ /dev/null @@ -1,290 +0,0 @@ -"""Brain directory watcher — monitors Antigravity's brain/ for file changes. - -Uses watchdog to detect file creation/modification events in the brain directory. -Emits events to an asyncio queue for the Discord bot to consume. - -Key design: ONLY emits events for meaningful content changes using hash dedup. -""" - -import asyncio -import hashlib -import time -import logging -from pathlib import Path -from dataclasses import dataclass, field -from enum import Enum -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler, FileSystemEvent - -from config import Config - -logger = logging.getLogger(__name__) - - -class EventType(Enum): - """Types of brain events.""" - SESSION_START = "session_start" # New conversation directory created - FILE_CHANGED = "file_changed" # Watched file modified - FILE_CREATED = "file_created" # Watched file first created - - -@dataclass -class BrainEvent: - """An event from the brain directory.""" - event_type: EventType - conversation_id: str - file_name: str = "" - file_path: Path = None - content: str = "" - timestamp: float = field(default_factory=time.time) - - -class BrainEventHandler(FileSystemEventHandler): - """Watchdog handler that filters, debounces, and deduplicates brain events. - - Phase 2 FIX: Only emits events for sessions belonging to the current project - (Config.PROJECT_NAME), using bridge/register/ files for session→project mapping. - """ - - def __init__(self, event_queue: asyncio.Queue, loop: asyncio.AbstractEventLoop): - super().__init__() - self.event_queue = event_queue - self.loop = loop - self._last_events: dict[str, float] = {} # path -> timestamp (debounce) - self._content_hashes: dict[str, str] = {} # path -> md5 hash (dedup) - self._known_sessions: set[str] = set() - # Phase 2: project filter - self._session_project_map: dict[str, str] = {} # conv_id → project_name - self._project_map_ts: float = 0 # last load timestamp - self._PROJECT_MAP_TTL: float = 60.0 # reload every 60s - self._initialize_known_sessions() - - def _initialize_known_sessions(self): - """Scan existing brain directories to establish baseline (no events emitted). - Also pre-loads content hashes for watched files to prevent spurious events. - """ - brain_path = Config.BRAIN_PATH - hash_count = 0 - if brain_path.exists(): - for entry in brain_path.iterdir(): - if entry.is_dir() and self._is_conversation_id(entry.name): - self._known_sessions.add(entry.name) - # Pre-load content hashes for watched files - for watched in Config.WATCHED_FILES: - fpath = entry / watched - if fpath.exists(): - try: - content = fpath.read_text(encoding="utf-8") - h = hashlib.md5(content.encode()).hexdigest() - self._content_hashes[str(fpath)] = h - hash_count += 1 - except (OSError, UnicodeDecodeError): - pass - logger.info( - f"Found {len(self._known_sessions)} existing sessions, " - f"pre-loaded {hash_count} content hashes" - ) - - def _load_session_project_map(self) -> dict[str, str]: - """Load session→project mapping from bridge/register/ files (cached).""" - now = time.time() - if now - self._project_map_ts < self._PROJECT_MAP_TTL: - return self._session_project_map - - import json - register_dir = Config.BRAIN_PATH.parent / "bridge" / "register" - if not register_dir.exists(): - self._project_map_ts = now - return self._session_project_map - - new_map: dict[str, str] = {} - for f in register_dir.glob("*.json"): - try: - data = json.loads(f.read_text(encoding="utf-8-sig")) - conv_id = data.get("conversation_id", "") - project = data.get("project_name", "") - if conv_id and project: - new_map[conv_id] = project - except (json.JSONDecodeError, OSError): - pass - - self._session_project_map = new_map - self._project_map_ts = now - return self._session_project_map - - def _is_my_session(self, conv_id: str) -> bool: - """Check if a session belongs to the current project. - - Returns True for: - - Sessions registered to Config.PROJECT_NAME - - Unknown sessions (not in any register file — allow to avoid blocking) - Returns False for sessions registered to OTHER projects. - """ - session_map = self._load_session_project_map() - project = session_map.get(conv_id) - if project is None: - return True # Unknown → allow (newly started, not yet registered) - return project == Config.PROJECT_NAME - - def dispatch(self, event: FileSystemEvent): - """Early filter: skip events for files/dirs we don't care about. - - This runs BEFORE on_created/on_modified, avoiding unnecessary - method dispatch overhead for the majority of file events. - """ - path = Path(event.src_path) - - # Skip .system_generated and logs subdirectories immediately - path_parts = path.parts - if '.system_generated' in path_parts or 'logs' in path_parts: - return - - # For file events, skip non-watched files immediately - if not event.is_directory: - file_name = path.name - if not self._is_watched_file(file_name): - return - - super().dispatch(event) - - def _is_conversation_id(self, name: str) -> bool: - parts = name.split("-") - return len(parts) == 5 and all(len(p) >= 4 for p in parts) - - def _get_conversation_id(self, path: Path) -> str | None: - brain_path = Config.BRAIN_PATH - try: - relative = path.relative_to(brain_path) - parts = relative.parts - if parts and self._is_conversation_id(parts[0]): - return parts[0] - except ValueError: - pass - return None - - def _should_debounce(self, path_str: str) -> bool: - now = time.time() - last = self._last_events.get(path_str, 0) - if now - last < Config.DEBOUNCE_SECONDS: - return True - self._last_events[path_str] = now - return False - - def _content_changed(self, path_str: str, content: str) -> bool: - """Check if content actually changed using MD5 hash.""" - new_hash = hashlib.md5(content.encode()).hexdigest() - old_hash = self._content_hashes.get(path_str) - if old_hash == new_hash: - return False - self._content_hashes[path_str] = new_hash - return True - - def _is_watched_file(self, file_name: str) -> bool: - """Filter: watch primary artifact files + any file matching watched extensions.""" - if file_name in Config.WATCHED_FILES: - return True - # Extension-based matching (e.g., any .md file in conversation dir) - ext = Path(file_name).suffix - if ext and ext in Config.WATCHED_EXTENSIONS: - return True - return False - - def _emit(self, event: BrainEvent): - self.loop.call_soon_threadsafe(self.event_queue.put_nowait, event) - - def on_created(self, event: FileSystemEvent): - if event.is_directory: - self._handle_directory_created(Path(event.src_path)) - else: - self._handle_file_event(Path(event.src_path), EventType.FILE_CREATED) - - def on_modified(self, event: FileSystemEvent): - if not event.is_directory: - self._handle_file_event(Path(event.src_path), EventType.FILE_CHANGED) - - def _handle_directory_created(self, path: Path): - conv_id = self._get_conversation_id(path) - if conv_id and conv_id not in self._known_sessions: - if path.parent == Config.BRAIN_PATH: - self._known_sessions.add(conv_id) - logger.info(f"New session detected: {conv_id}") - self._emit(BrainEvent( - event_type=EventType.SESSION_START, - conversation_id=conv_id, - )) - - def _handle_file_event(self, path: Path, event_type: EventType): - conv_id = self._get_conversation_id(path) - if not conv_id: - return - - # Phase 2 FIX: only emit events for MY project's sessions - if not self._is_my_session(conv_id): - return - - # Exclude files in .system_generated subdirectory (AG internal logs) - try: - relative = path.relative_to(Config.BRAIN_PATH / conv_id) - if '.system_generated' in relative.parts: - return - except ValueError: - pass - - file_name = path.name - - # Filter: watched files by name or extension - if not self._is_watched_file(file_name): - return - - # Debounce: skip rapid-fire events for same file - if self._should_debounce(str(path)): - return - - # Read file content - try: - content = path.read_text(encoding="utf-8") - except (OSError, UnicodeDecodeError) as e: - logger.warning(f"Failed to read {path}: {e}") - return - - # Content hash dedup: skip if content hasn't actually changed - if not self._content_changed(str(path), content): - return - - logger.info(f"File event: {event_type.value} {conv_id[:8]}/{file_name}") - self._emit(BrainEvent( - event_type=event_type, - conversation_id=conv_id, - file_name=file_name, - file_path=path, - content=content, - )) - - -class BrainWatcher: - """Manages the watchdog observer for the brain directory.""" - - def __init__(self, event_queue: asyncio.Queue, loop: asyncio.AbstractEventLoop): - self.event_queue = event_queue - self.loop = loop - self.observer = Observer() - self.handler = BrainEventHandler(event_queue, loop) - - def start(self): - brain_path = Config.BRAIN_PATH - if not brain_path.exists(): - logger.error(f"Brain path does not exist: {brain_path}") - return - - self.observer.schedule(self.handler, str(brain_path), recursive=True) - self.observer.start() - logger.info(f"Watching brain directory: {brain_path}") - - def stop(self): - self.observer.stop() - self.observer.join() - logger.info("Brain watcher stopped") - - @property - def known_sessions(self) -> set[str]: - return self.handler._known_sessions