fix(extension): adapt DOM observer to Native Agent panel and Tailwind migration (v0.5.21)
This commit is contained in:
5
docs/devlog/2026-04-09.md
Normal file
5
docs/devlog/2026-04-09.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 2026-04-09
|
||||||
|
|
||||||
|
| NNN | HH:MM | 작업 설명 | `커밋해시` | 상태 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 001 | 21:55 | Agent UI Tailwind/Native 마이그레이션 대응 (DOM 옵저버 구조 개편) | `HEAD` | ✅ |
|
||||||
18
docs/devlog/entries/20260409-001.md
Normal file
18
docs/devlog/entries/20260409-001.md
Normal 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` 등으로 완화.
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
- 없음
|
||||||
@@ -273,16 +273,16 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
// ONLY positive triggers should initiate a pending request group.
|
// ONLY positive triggers should initiate a pending request group.
|
||||||
// Negative/secondary buttons (Deny, Reject, Dismiss) will be collected as siblings.
|
// Negative/secondary buttons (Deny, Reject, Dismiss) will be collected as siblings.
|
||||||
var PATS=[
|
var PATS=[
|
||||||
{re:/^Run/i, type:'terminal_command'},
|
{re:/^(?:Always\s*)?Run/i, type:'terminal_command'},
|
||||||
{re:/^Accept all/i, type:'diff_review'},
|
{re:/^(?:Always\s*)?Accept all/i, type:'diff_review'},
|
||||||
{re:/^Accept/i, type:'agent_step'},
|
{re:/^(?:Always\s*)?Accept/i, type:'agent_step'},
|
||||||
{re:/^(?:Always )?Allow/i, type:'permission'},
|
{re:/^(?:Always\s*)?Allow/i, type:'permission'},
|
||||||
{re:/^Approve/i, type:'agent_step'},
|
{re:/^(?:Always\s*)?Approve/i, type:'agent_step'},
|
||||||
{re:/^Retry/i, type:'error_recovery'},
|
{re:/^Retry/i, type:'error_recovery'},
|
||||||
];
|
];
|
||||||
|
|
||||||
// ALL actionable button patterns (for grouping siblings in same container)
|
// 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
|
// 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];
|
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',
|
'.react-app-container',
|
||||||
'[class*="agent-panel"]',
|
'[class*="agent-panel"]',
|
||||||
'[class*="agentPanel"]',
|
'[class*="agentPanel"]',
|
||||||
|
'.chat-body',
|
||||||
|
'.interactive-session',
|
||||||
|
'[class*="sidebar"]',
|
||||||
];
|
];
|
||||||
for(var i=0;i<selectors.length;i++){
|
for(var i=0;i<selectors.length;i++){
|
||||||
var el=document.querySelector(selectors[i]);
|
var el=document.querySelector(selectors[i]);
|
||||||
@@ -419,9 +422,9 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
var matchedType=null;
|
var matchedType=null;
|
||||||
for(var p=0;p<PATS.length;p++){
|
for(var p=0;p<PATS.length;p++){
|
||||||
if(PATS[p].re.test(txt)){
|
if(PATS[p].re.test(txt)){
|
||||||
// STRUCTURAL CONSTRAINT: If we are scanning the main VS Code Editor body, reject Agent/Terminal buttons
|
// STRUCTURAL CONSTRAINT: To prevent freezing on CodeLens 'Run' or 'Accept' false positives within editor files,
|
||||||
// to prevent freezing on CodeLens 'Run' or 'Accept' false positives.
|
// ignore these if found inside the main editor body (.monaco-editor).
|
||||||
if (isVSCodeMainWindow && isBodyRoot && PATS[p].type !== 'diff_review' && PATS[p].type !== 'permission') {
|
if (b.closest('.monaco-editor') && PATS[p].type !== 'diff_review' && PATS[p].type !== 'permission') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Prevent duplicates if already scanned via panel root
|
// Prevent duplicates if already scanned via panel root
|
||||||
@@ -651,7 +654,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
if(!d.action)return;
|
if(!d.action)return;
|
||||||
log('🔔 TRIGGER-CLICK received: action='+d.action);
|
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 rejectRe=[/^Reject/i,/^Cancel$/i,/^Deny$/i,/^Stop$/i,/^Decline$/i,/^Dismiss$/i];
|
||||||
var patterns=(d.action==='approve')?approveRe:rejectRe;
|
var patterns=(d.action==='approve')?approveRe:rejectRe;
|
||||||
var emoji=(d.action==='approve')?'✅':'❌';
|
var emoji=(d.action==='approve')?'✅':'❌';
|
||||||
|
|||||||
1
inspect-dump.json
Normal file
1
inspect-dump.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"status":"timeout","message":"Renderer did not respond in 10s. Is the v3 script loaded?"}
|
||||||
50
scratch_parse.py
Normal file
50
scratch_parse.py
Normal 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
27
scratch_parse2.py
Normal 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
20
scratch_parse3.py
Normal 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
18
scratch_parse4.py
Normal 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}')
|
||||||
18
trigger_capture.py
Normal file
18
trigger_capture.py
Normal 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)
|
||||||
Reference in New Issue
Block a user