perf(bridge): 3 optimizations — pollResponseGroup 1500ms, renderer adaptive idle, Bot single-pass scanner

This commit is contained in:
2026-03-15 10:51:22 +09:00
parent f96203646e
commit ae0509fbb5
2 changed files with 68 additions and 72 deletions

40
bot.py
View File

@@ -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":
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,33 +663,23 @@ 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", "")
elif status == "pending":
# MERGE check: step_probe updated command in already-sent pending
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:
@@ -714,6 +699,7 @@ class GravityBot(commands.Bot):
logger.info(f"MERGE edit: {rid[:12]} cmd='{new_cmd[:60]}'")
except discord.NotFound:
pass
except (json.JSONDecodeError, OSError):
pass

View File

@@ -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<bidList.length;ri++){delete _sent[bidList[ri]];}
}).catch(function(){});
},500);
},1500);
}
// Legacy pollResponse for backward compatibility (single button)
@@ -1552,19 +1552,27 @@ function generateApprovalObserverScript(_port: number): string {
// FALLBACK: periodic scan every 3s for any missed mutations
setInterval(scheduleScan,3000);
// ── Adaptive idle detection for HTTP polls ──
var _lastActivity=Date.now();
var _idleThreshold=60000; // 60s without DOM changes → slow mode
new MutationObserver(function(){_lastActivity=Date.now();}).observe(document.body,{childList:true,subtree:true,attributes:true});
function getAdaptiveInterval(){return (Date.now()-_lastActivity>_idleThreshold)?10000:2000;}
// ── DEEP-INSPECT POLLING: curl→Bridge→Renderer→Results ──
setInterval(function(){
if(!_ready||!BASE)return;
(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(){});
},2000);
}
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');