From ae0509fbb5af76ebbf5ab418ae6efab094c161b1 Mon Sep 17 00:00:00 2001 From: CD Date: Sun, 15 Mar 2026 10:51:22 +0900 Subject: [PATCH] =?UTF-8?q?perf(bridge):=203=20optimizations=20=E2=80=94?= =?UTF-8?q?=20pollResponseGroup=201500ms,=20renderer=20adaptive=20idle,=20?= =?UTF-8?q?Bot=20single-pass=20scanner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 108 ++++++++++++++++--------------------- extension/src/extension.ts | 32 +++++++---- 2 files changed, 68 insertions(+), 72 deletions(-) diff --git a/bot.py b/bot.py index 29e76ce..320fc4e 100644 --- a/bot.py +++ b/bot.py @@ -611,12 +611,14 @@ class GravityBot(commands.Bot): self._sent_commands[req.request_id] = req.command await self._send_approval_request(channel, req) - # ── Check for auto_resolved pendings (approved directly in AG) ── + # ── Single-pass: handle auto_resolved, expired, and MERGE in one glob ── for f in self.bridge.pending_dir.glob("*.json"): try: data = json.loads(f.read_text(encoding="utf-8-sig")) - if data.get("status") == "auto_resolved": - rid = data.get("request_id", "") + 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) @@ -630,25 +632,18 @@ class GravityBot(commands.Bot): description=f"```\n{data.get('command', '')[:500]}\n```", color=discord.Color.green(), ) - embed.set_footer(text=f"ID: {data.get('request_id', '')}") + embed.set_footer(text=f"ID: {rid}") await msg.edit(embed=embed, view=None) except discord.NotFound: pass f.unlink() - self._deferred_ids.pop(data.get("request_id", ""), None) - self._sent_commands.pop(data.get("request_id", ""), None) - self._approval_messages.pop(data.get("request_id", ""), None) - self._sent_approval_ids.discard(data.get("request_id", "")) - except (json.JSONDecodeError, OSError): - pass + self._deferred_ids.pop(rid, None) + self._sent_commands.pop(rid, None) + self._approval_messages.pop(rid, None) + self._sent_approval_ids.discard(rid) - # ── Check for expired pendings — update Discord card ── - for f in self.bridge.pending_dir.glob("*.json"): - try: - data = json.loads(f.read_text(encoding="utf-8-sig")) - if data.get("status") == "expired": + elif status == "expired": msg_id = data.get("discord_message_id", 0) - rid = data.get("request_id", "") project = data.get("project_name", Config.PROJECT_NAME) if msg_id: channel = await self._get_channel(project) @@ -668,52 +663,43 @@ class GravityBot(commands.Bot): self._deferred_ids.pop(rid, None) self._sent_commands.pop(rid, None) self._sent_approval_ids.discard(rid) - except (json.JSONDecodeError, OSError): - pass - # ── Check for MERGE updates (step_probe updated command in already-sent pending) ── - for f in self.bridge.pending_dir.glob("*.json"): - try: - data = json.loads(f.read_text(encoding="utf-8-sig")) - rid = data.get("request_id", "") - if rid not in self._sent_approval_ids: - continue - if data.get("status") != "pending": - continue - msg_id = data.get("discord_message_id", 0) - if not msg_id: - continue - # Check if command was updated via MERGE - 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): - # MERGE detected — edit Discord message - 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) - # Rebuild embed with full command - 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 + 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 diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 808b9ab..683ecac 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -1429,7 +1429,7 @@ function generateApprovalObserverScript(_port: number): string { // ── Poll for Discord response (multi-button group aware) ── function pollResponseGroup(rid,btnRefs,bidList,groupKey){ var polls=0; - var maxPolls=600; // 5 minutes at 500ms interval + var maxPolls=200; // 5 minutes at 1500ms interval var timer=setInterval(function(){ polls++; // Check if ANY button in the group is still in DOM @@ -1474,7 +1474,7 @@ function generateApprovalObserverScript(_port: number): string { delete _sent[groupKey]; for(var ri=0;ri_idleThreshold)?10000:2000;} + // ── DEEP-INSPECT POLLING: curl→Bridge→Renderer→Results ── - setInterval(function(){ - if(!_ready||!BASE)return; - fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){ - if(d.inspect){log('🔍 Deep inspect triggered via HTTP');runDeepInspect();} - }).catch(function(){}); - },2000); + (function pollDeepInspect(){ + if(_ready&&BASE){ + fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){ + if(d.inspect){log('🔍 Deep inspect triggered via HTTP');runDeepInspect();} + }).catch(function(){}); + } + setTimeout(pollDeepInspect,getAdaptiveInterval()); + })(); // ── TRIGGER-CLICK: Extension→Renderer bridge for programmatic button clicks ── // Extension sets clickTrigger via tryApprovalStrategies → renderer polls and clicks // v3: uses deepFindButtons() to traverse iframes, webviews, shadow DOMs - setInterval(function(){ - if(!_ready||!BASE)return; + (function pollTriggerClick(){ + if(_ready&&BASE){ fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){ if(!d.action)return; log('🔔 TRIGGER-CLICK received: action='+d.action); @@ -1652,7 +1660,9 @@ function generateApprovalObserverScript(_port: number): string { log('⚠️ iframes='+document.querySelectorAll('iframe').length+' webviews='+document.querySelectorAll('webview').length); } }).catch(function(){}); - },1000); + } + setTimeout(pollTriggerClick,getAdaptiveInterval()); + })(); _obs=true; log('v3 Observer active — deep DOM traversal + MutationObserver + trigger-click polling');