fix(extension): adapt DOM observer to Native Agent panel and Tailwind migration (v0.5.21)

This commit is contained in:
Variet Worker
2026-04-09 21:56:29 +09:00
parent d2023321bd
commit 75762964e3
10 changed files with 171 additions and 10 deletions

View File

@@ -0,0 +1,5 @@
# 2026-04-09
| NNN | HH:MM | 작업 설명 | `커밋해시` | 상태 |
|---|---|---|---|---|
| 001 | 21:55 | Agent UI Tailwind/Native 마이그레이션 대응 (DOM 옵저버 구조 개편) | `HEAD` | ✅ |

View File

@@ -0,0 +1,18 @@
# Agent UI Tailwind/Native 마이그레이션 대응 (DOM 옵저버 구조 개편)
- **시간**: 2026-04-09 19:40~21:55
- **Commit**: `[임시해시]`
- **Vikunja**: 신규 생성 후 완료 처리
## 트러블슈팅 및 결정 사항
최근 UI 업데이트 후 Discord 릴레이 신호(Run, Accept) 단절.
deep-inspect 덤프 분석 결과 Webview/Iframe 환경이 사라지고 Native DOM(VS Code 본문)에 напрямую 그려짐, 기존 시맨틱 클래스가 Tailwind로 변경.
1. 기존 `findPanel`이 패널을 못 찾자 `isBodyRoot` 모드로 스캔
2. 과거에 추가된 CodeLens 방어 로직(`if (isVSCodeMainWindow && isBodyRoot && PATS[p].type !== 'diff_review') continue;`)에 의해 모든 버튼 스캔이 **버려지고 있었음**.
**결정**:
엄격한 Panel Class Whitelist 기반 방어를 해제하고, 버튼이 `.monaco-editor` 내부에 있는 경우만 무시하도록 Blacklist 기반 방어로 선회.
UI 텍스트 글루잉(아이콘 통합) 대응 위해 패터닝 정규식을 `/^(?:Always\s*)?Run/i` 등으로 완화.
## 미완료
- 없음

View File

@@ -273,16 +273,16 @@ export function generateApprovalObserverScript(_port: number): string {
// ONLY positive triggers should initiate a pending request group.
// Negative/secondary buttons (Deny, Reject, Dismiss) will be collected as siblings.
var PATS=[
{re:/^Run/i, type:'terminal_command'},
{re:/^Accept all/i, type:'diff_review'},
{re:/^Accept/i, type:'agent_step'},
{re:/^(?:Always )?Allow/i, type:'permission'},
{re:/^Approve/i, type:'agent_step'},
{re:/^(?:Always\s*)?Run/i, type:'terminal_command'},
{re:/^(?:Always\s*)?Accept all/i, type:'diff_review'},
{re:/^(?:Always\s*)?Accept/i, type:'agent_step'},
{re:/^(?:Always\s*)?Allow/i, type:'permission'},
{re:/^(?:Always\s*)?Approve/i, type:'agent_step'},
{re:/^Retry/i, type:'error_recovery'},
];
// ALL actionable button patterns (for grouping siblings in same container)
var ALL_ACTION_RE=[/^Run/i,/^Accept/i,/^Reject/i,/^(?:Always )?Allow/i,/^Deny/i,/^Approve/i,/^Cancel$/i,/^Retry$/i,/^Dismiss$/i,/^Stop$/i,/^Decline$/i];
var ALL_ACTION_RE=[/^(?:Always\s*)?Run/i,/^(?:Always\s*)?Accept/i,/^Reject/i,/^(?:Always\s*)?Allow/i,/^Deny/i,/^(?:Always\s*)?Approve/i,/^Cancel$/i,/^Retry$/i,/^Dismiss$/i,/^Stop$/i,/^Decline$/i];
// Reject button patterns for finding the counterpart
var REJECT_RE=[/^reject$/i,/^reject all$/i,/^cancel$/i,/^deny$/i,/^stop$/i,/^decline$/i,/^dismiss$/i];
@@ -372,6 +372,9 @@ export function generateApprovalObserverScript(_port: number): string {
'.react-app-container',
'[class*="agent-panel"]',
'[class*="agentPanel"]',
'.chat-body',
'.interactive-session',
'[class*="sidebar"]',
];
for(var i=0;i<selectors.length;i++){
var el=document.querySelector(selectors[i]);
@@ -419,9 +422,9 @@ export function generateApprovalObserverScript(_port: number): string {
var matchedType=null;
for(var p=0;p<PATS.length;p++){
if(PATS[p].re.test(txt)){
// STRUCTURAL CONSTRAINT: If we are scanning the main VS Code Editor body, reject Agent/Terminal buttons
// to prevent freezing on CodeLens 'Run' or 'Accept' false positives.
if (isVSCodeMainWindow && isBodyRoot && PATS[p].type !== 'diff_review' && PATS[p].type !== 'permission') {
// STRUCTURAL CONSTRAINT: To prevent freezing on CodeLens 'Run' or 'Accept' false positives within editor files,
// ignore these if found inside the main editor body (.monaco-editor).
if (b.closest('.monaco-editor') && PATS[p].type !== 'diff_review' && PATS[p].type !== 'permission') {
continue;
}
// Prevent duplicates if already scanned via panel root
@@ -651,7 +654,7 @@ export function generateApprovalObserverScript(_port: number): string {
if(!d.action)return;
log('🔔 TRIGGER-CLICK received: action='+d.action);
var approveRe=[/^Run/i,/^Accept/i,/^Accept all/i,/^(?:Always )?Allow/i,/^Approve/i,/^Continue$/i,/^Proceed$/i,/^Retry$/i];
var approveRe=[/^(?:Always\s*)?Run/i,/^(?:Always\s*)?Accept/i,/^(?:Always\s*)?Accept all/i,/^(?:Always\s*)?Allow/i,/^(?:Always\s*)?Approve/i,/^Continue$/i,/^Proceed$/i,/^Retry$/i];
var rejectRe=[/^Reject/i,/^Cancel$/i,/^Deny$/i,/^Stop$/i,/^Decline$/i,/^Dismiss$/i];
var patterns=(d.action==='approve')?approveRe:rejectRe;
var emoji=(d.action==='approve')?'✅':'❌';

1
inspect-dump.json Normal file
View File

@@ -0,0 +1 @@
{"status":"timeout","message":"Renderer did not respond in 10s. Is the v3 script loaded?"}

50
scratch_parse.py Normal file
View File

@@ -0,0 +1,50 @@
import json
try:
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
data = json.load(f)
# Print first 50 buttons from nodes
count = 0
print('=== First 100 Buttons in DOM Nodes ===')
for node in data.get('nodes', []):
label = node.get('label', 'unknown')
btns = node.get('buttons', [])
if btns:
print(f'\n[Node] {label}')
for b in btns:
t = b.get('text', '').replace('\n', ' ').strip()
hidden = b.get("hidden")
cls = b.get("class")
if t:
print(f' - "{t[:50]}" (Hidden: {hidden}, Class: {cls[:30]})')
count += 1
if count > 100:
break
if count > 100:
break
# Print first 50 buttons from webviews
count = 0
print('\n=== First 100 Buttons in Webviews ===')
for probe in data.get('webviewProbes', []):
if probe.get('success'):
pd = probe.get('data', {})
btns = pd.get('buttons', [])
label = pd.get('title', 'Unknown Title') + f" (URL: {pd.get('url', 'Unknown URL')})"
if btns:
print(f'\n[WebviewProbe {probe.get("index")}] {label}')
for b in btns:
t = b.get('text', '').replace('\n', ' ').strip()
hidden = b.get("hidden")
cls = b.get("class")
if t:
print(f' - "{t[:50]}" (Hidden: {hidden}, Class: {cls[:30]})')
count += 1
if count > 100:
break
if count > 100:
break
except Exception as e:
print(f'Error reading JSON: {e}')

27
scratch_parse2.py Normal file
View File

@@ -0,0 +1,27 @@
import json
try:
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
data = json.load(f)
print(f"Total Nodes: {len(data.get('nodes', []))}")
for node in data.get('nodes', []):
label = node.get('label', 'unknown')
iframes = node.get('iframes', [])
webviews = node.get('webviews', [])
buttons = node.get('buttons', [])
print(f"[Node] {label}")
print(f" URL: {node.get('url', '')[:50]}")
print(f" Total Elements: {node.get('totalElements', 0)}")
print(f" Buttons count: {len(buttons)}")
print(f" Iframes count: {len(iframes)}")
print(f" Webviews count: {len(webviews)}")
if iframes:
for iframe in iframes:
print(f" - iframe[{iframe.get('index')}]: accessible={iframe.get('accessible')} src={iframe.get('src', '')[:50]}")
if webviews:
for w in webviews:
print(f" - webview[{w.get('index')}]: src={w.get('src', '')[:50]}")
except Exception as e:
print(f'Error reading JSON: {e}')

20
scratch_parse3.py Normal file
View File

@@ -0,0 +1,20 @@
import json
try:
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
data = json.load(f)
print("SEARCHING FOR CHAT TEXT IN DUMP...")
found = False
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
raw_text = f.read()
if '스스로 만들고 스스로' in raw_text:
print("YES! The chat text IS in the raw JSON dump!")
found = True
elif 'Variet' in raw_text:
print("Found Variet in dump.")
else:
print("Chat text not found in raw dump.")
except Exception as e:
print(f'Error reading JSON: {e}')

18
scratch_parse4.py Normal file
View File

@@ -0,0 +1,18 @@
import json
try:
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
data = json.load(f)
for node in data.get('nodes', []):
label = node.get('label', 'unknown')
btns = node.get('buttons', [])
print(f"\n[Node] {label} (Total btns: {len(btns)})")
for i, b in enumerate(btns):
t = b.get('text', '').replace('\n', ' ').strip()
hidden = b.get("hidden")
cls = b.get("class")
print(f" {i:3d}: \"{t[:50]}\" (Hidden: {hidden}, Class: {cls[:30]})")
except Exception as e:
print(f'Error reading JSON: {e}')

1
test.txt Normal file
View File

@@ -0,0 +1 @@
world

18
trigger_capture.py Normal file
View File

@@ -0,0 +1,18 @@
import urllib.request, time
print("Waiting 3 seconds for UI to render...")
time.sleep(3)
name = 'gravity_control'
h = 0
for c in name:
h = ((h << 5) - h + ord(c)) & 0xFFFFFFFF
if h > 0x7FFFFFFF: h -= 0x100000000
port = 10000 + (abs(h) % 50000)
print(f"Triggering deep-inspect on port {port}...")
try:
urllib.request.urlopen(urllib.request.Request(f'http://127.0.0.1:{port}/deep-inspect'), timeout=12)
print("Trigger signal sent successfully")
except Exception as e:
print("Trigger failed:", e)