docs: AG Native bundle reverse engineering analysis — plannerResponse/Whi renderer structure, V8 cache fix, known-issues update
This commit is contained in:
@@ -10,6 +10,18 @@
|
||||
|
||||
|
||||
|
||||
> [!TIP]
|
||||
|
||||
> <EFBFBD>빐寃<EFBFBD> <20>셿猷뚮맂 怨쇨굅 <20>씠<EFBFBD>뒋<EFBFBD>뒗 [`known-issues-archive.md`](file:///c:/Users/Variet-Worker/Desktop/gravity_control/.agents/references/known-issues-archive.md)<29>뿉 蹂닿<E8B982><EB8BBF><EFBFBD>릺<EFBFBD>뼱 <20>엳<EFBFBD>뒿<EFBFBD>땲<EFBFBD>떎.
|
||||
|
||||
> 鍮꾩듂<EFBFBD>븳 臾몄젣媛<ECA0A3> <20>옱諛쒗븯硫<EBB8AF> archive<76>뿉<EFBFBD>꽌 寃<><E5AF83>깋<EFBFBD>븯<EFBFBD>꽭<EFBFBD>슂.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
### [2026-04-12] [Extension] V8 CachedData가 Observer 스크립트 로딩을 차단
|
||||
- **증상**: html-patcher가 workbench-jetski-agent.html에 observer v7 인라인 스크립트를 성공적으로 삽입했지만, deep-inspect가 렌더러에서 응답 없음 (10s timeout). AG 재시작 후에도 observer가 로드되지 않음
|
||||
- **원인**: `%APPDATA%\Antigravity\CachedData\` (50MB)에 V8 캐시가 남아있어, AG가 패치된 HTML 대신 캐시된 이전 버전을 로드. extension.log에 `patcher.install() called (needs reload)` 메시지가 표시되지만 실제 적용이 안 됨
|
||||
- **해결**: `Remove-Item "$env:APPDATA\Antigravity\CachedData\*" -Recurse -Force` 실행 후 AG 재시작. known-issues-archive #6에도 동일 케이스 있음
|
||||
- **주의**: HTML 패치 변경 시 **반드시** V8 CachedData 삭제 + AG 재시작 필요. 단순 AG 재시작만으로는 부족
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# 2026-04-12
|
||||
# 2026-04-12
|
||||
|
||||
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완/미 |
|
||||
|-------|-------|----------|-----------|-----------|
|
||||
| 001 | 06:12 | AG Native DOM 파싱 v7 전면 재설계 — data-testid/data-step-index 기반 step-aware 파서, UI 노이즈 차단 | `a4d7286` | 🔧 |
|
||||
| 002 | 07:03 | AG Native 번들 역공학 분석 + V8 CachedData 삭제 — plannerResponse→Whi 렌더러 구조 확인, bot-color가 NUX용임 발견 | — | 🔧 |
|
||||
|
||||
22
docs/devlog/entries/20260412-002.md
Normal file
22
docs/devlog/entries/20260412-002.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# AG Native 번들 역공학 + V8 캐시 정리 + Observer 미작동 원인 규명
|
||||
|
||||
- **시간**: 2026-04-12 06:28~07:03
|
||||
- **Commit**: — (분석/조사 세션, 코드 변경 없음)
|
||||
|
||||
## 결정 사항
|
||||
- `text-ide-message-block-bot-color`는 AI 응답 컨테이너가 **아닌** NUX tooltip 전용 클래스로 확인 → observer 셀렉터에서 제거 필요
|
||||
- `markdown-body` 클래스도 AG Native에 존재하지 않음 → 폴백 셀렉터 변경 필요
|
||||
- AI 응답 텍스트는 `plannerResponse` step → `Whi` 렌더러 → `div.px-2.py-1` → `MarkdownRenderer` 내부에 위치
|
||||
- `data-step-index`는 디버그 패널에서 확인되었으나 메인 대화 뷰에서의 존재 여부는 라이브 DOM 덤프로 확인 필요
|
||||
|
||||
## 새로 알게 된 사실
|
||||
- AG Native 번들(jetskiAgent/main.js 10.8MB): 전체 step.case→renderer 매핑 확보 (pan 객체)
|
||||
- Allow/Deny는 `lHr` 컴포넌트, `border-t border-gray-500/25` 클래스
|
||||
- Observer v7이 HTML에 삽입되었지만 V8 CachedData(50MB) 때문에 실제 렌더러에서 로드되지 않았음
|
||||
- CachedData 삭제 완료 → AG 리로드 후 observer 작동 예상
|
||||
|
||||
## 미완료
|
||||
- AG 리로드 후 observer 작동 확인
|
||||
- deep-inspect로 실제 DOM 구조 캡처
|
||||
- observer-script 셀렉터 미세조정 (bot-color 제거, MarkdownRenderer 타겟팅)
|
||||
- Discord 릴레이 E2E 검증
|
||||
63
scratch_bundle_deep.py
Normal file
63
scratch_bundle_deep.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Find exact data-testid values and step-index context in AG bundle."""
|
||||
import re, sys, io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
bundle_path = r"C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js"
|
||||
content = open(bundle_path, encoding='utf-8', errors='replace').read()
|
||||
|
||||
# Find all data-testid literal values
|
||||
print("=== data-testid values ===")
|
||||
for m in re.finditer(r'"data-testid"\s*[,:]\s*["`]([^"`]+)["`]', content):
|
||||
print(f" {m.group(1)}")
|
||||
# Also try template literal pattern
|
||||
for m in re.finditer(r"'data-testid'\s*[,:]\s*'([^']+)'", content):
|
||||
print(f" {m.group(1)}")
|
||||
# JSX pattern: data-testid="xxx" or data-testid={xxx}
|
||||
for m in re.finditer(r'data-testid[=:]["\'`]([^"\'`]{2,60})["\'`]', content):
|
||||
print(f" {m.group(1)}")
|
||||
|
||||
# Find data-step-index context
|
||||
print("\n=== data-step-index usage context ===")
|
||||
for m in re.finditer(r'data-step-index', content):
|
||||
start = max(0, m.start() - 150)
|
||||
end = min(len(content), m.end() + 150)
|
||||
ctx = content[start:end].replace('\n', ' ')
|
||||
print(f" ...{ctx}...")
|
||||
|
||||
# Find data-status context
|
||||
print("\n=== data-status usage context ===")
|
||||
for m in re.finditer(r'"data-status"', content):
|
||||
start = max(0, m.start() - 100)
|
||||
end = min(len(content), m.end() + 100)
|
||||
ctx = content[start:end].replace('\n', ' ')
|
||||
print(f" ...{ctx}...")
|
||||
|
||||
# Find "Running" button context - what container wraps it?
|
||||
print("\n=== 'Running' + 'command' nearby JSX context ===")
|
||||
for m in re.finditer(r'Running.{0,3}(?:\$\{|`|\+).{0,30}command', content):
|
||||
start = max(0, m.start() - 200)
|
||||
end = min(len(content), m.end() + 200)
|
||||
ctx = content[start:end].replace('\n', ' ')
|
||||
print(f" @{m.start()}: ...{ctx[:400]}...")
|
||||
|
||||
# Find "Allow" button context
|
||||
print("\n=== 'Allow' button context ===")
|
||||
for m in re.finditer(r'["\'`]Allow["\'`]', content):
|
||||
start = max(0, m.start() - 200)
|
||||
end = min(len(content), m.end() + 200)
|
||||
ctx = content[start:end].replace('\n', ' ')
|
||||
print(f" @{m.start()}: ...{ctx[:400]}...")
|
||||
|
||||
# Find markdown rendering context
|
||||
print("\n=== Markdown rendering context ===")
|
||||
for m in re.finditer(r'(?:markdown|prose|rehype|remark)', content[:500000], re.IGNORECASE):
|
||||
start = max(0, m.start() - 60)
|
||||
end = min(len(content), m.end() + 60)
|
||||
ctx = content[start:end].replace('\n', ' ')
|
||||
if 'markdown' in ctx.lower() or 'prose' in ctx.lower():
|
||||
print(f" @{m.start()}: ...{ctx}...")
|
||||
|
||||
# Find text-ide pattern (old Cascade class naming)
|
||||
print("\n=== text-ide patterns ===")
|
||||
for m in re.finditer(r'text-ide-[a-z-]+', content):
|
||||
print(f" {m.group(0)}")
|
||||
65
scratch_bundle_deep2.js
Normal file
65
scratch_bundle_deep2.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const fs = require('fs');
|
||||
const bundlePath = String.raw`C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js`;
|
||||
const content = fs.readFileSync(bundlePath, 'utf-8');
|
||||
|
||||
// 1. Wider context around conversation-view
|
||||
console.log('=== CONVERSATION-VIEW (800 chars context) ===');
|
||||
let idx = content.indexOf('conversation-view');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(Math.max(0, idx - 500), idx + 800));
|
||||
}
|
||||
|
||||
console.log('\n\n=== DATA-STEP-INDEX (800 chars context) ===');
|
||||
idx = content.indexOf('data-step-index');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(Math.max(0, idx - 500), idx + 800));
|
||||
}
|
||||
|
||||
// 2. Find ALL occurrences of data-step-index
|
||||
console.log('\n\n=== ALL data-step-index occurrences ===');
|
||||
let pos = 0;
|
||||
let count = 0;
|
||||
while ((pos = content.indexOf('data-step-index', pos)) >= 0 && count < 5) {
|
||||
console.log(`\n--- occurrence ${++count} at offset ${pos} ---`);
|
||||
console.log(content.substring(Math.max(0, pos - 200), pos + 300));
|
||||
pos += 15;
|
||||
}
|
||||
|
||||
// 3. Find the step type rendering — look for step enums/types
|
||||
console.log('\n\n=== Step type patterns ===');
|
||||
const stepTypePatterns = [
|
||||
'stepType', 'step_type', 'StepType',
|
||||
'PLANNER_RESPONSE', 'RUN_COMMAND', 'EDIT_FILE', 'WRITE_TO_FILE',
|
||||
'ToolCallStep', 'PlannerStep', 'TextStep'
|
||||
];
|
||||
for (const pat of stepTypePatterns) {
|
||||
const i = content.indexOf(pat);
|
||||
if (i >= 0) {
|
||||
console.log(`\n--- ${pat} @${i} ---`);
|
||||
console.log(content.substring(Math.max(0, i - 100), i + 200).substring(0, 300));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Find how the AI text/response is rendered
|
||||
console.log('\n\n=== Text rendering patterns (near bot-color) ===');
|
||||
const botColorIdx = content.indexOf('text-ide-message-block-bot-color');
|
||||
if (botColorIdx >= 0) {
|
||||
console.log(content.substring(Math.max(0, botColorIdx - 800), botColorIdx + 800));
|
||||
}
|
||||
|
||||
// 5. Allow/Deny button with more context
|
||||
console.log('\n\n=== Allow/Deny button wider context ===');
|
||||
idx = content.indexOf('label:"Allow"');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(Math.max(0, idx - 800), idx + 600));
|
||||
}
|
||||
|
||||
// 6. Find markdown rendering components
|
||||
console.log('\n\n=== Markdown rendering patterns ===');
|
||||
for (const pat of ['MarkdownRenderer', 'renderMarkdown', 'markdownContent', 'dangerouslySetInnerHTML', 'StreamingText', 'TypeWriter', 'StreamingMarkdown']) {
|
||||
const i = content.indexOf(pat);
|
||||
if (i >= 0) {
|
||||
console.log(`\n--- ${pat} @${i} ---`);
|
||||
console.log(content.substring(Math.max(0, i - 150), i + 250).substring(0, 400));
|
||||
}
|
||||
}
|
||||
75
scratch_bundle_deep3.js
Normal file
75
scratch_bundle_deep3.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const fs = require('fs');
|
||||
const bundlePath = String.raw`C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js`;
|
||||
const content = fs.readFileSync(bundlePath, 'utf-8');
|
||||
|
||||
// 1. Find how the main conversation content is rendered
|
||||
// Look around "ConversationView" component
|
||||
console.log('=== ConversationView component ===');
|
||||
let idx = content.indexOf('ConversationView');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(Math.max(0, idx - 300), idx + 1500));
|
||||
}
|
||||
|
||||
// 2. Find the PlannerResponse rendering (this is the AI text response)
|
||||
console.log('\n\n=== PLANNER_RESPONSE rendering ===');
|
||||
idx = content.indexOf('PLANNER_RESPONSE');
|
||||
while (idx >= 0 && idx < content.length) {
|
||||
const ctx = content.substring(Math.max(0, idx - 200), idx + 500);
|
||||
if (ctx.includes('case') || ctx.includes('render') || ctx.includes('className')) {
|
||||
console.log(`\n--- @${idx} ---`);
|
||||
console.log(ctx.substring(0, 700));
|
||||
}
|
||||
idx = content.indexOf('PLANNER_RESPONSE', idx + 16);
|
||||
}
|
||||
|
||||
// 3. Find plannerResponse rendering
|
||||
console.log('\n\n=== "plannerResponse" patterns ===');
|
||||
for (const pat of ['plannerResponse', 'planner_response', 'PlannerResponse']) {
|
||||
let pos = 0;
|
||||
let c = 0;
|
||||
while ((pos = content.indexOf(pat, pos)) >= 0 && c < 3) {
|
||||
console.log(`\n--- "${pat}" @${pos} ---`);
|
||||
console.log(content.substring(Math.max(0, pos - 200), pos + 400).substring(0, 600));
|
||||
pos += pat.length;
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Find step.case rendering logic (how each step type renders)
|
||||
console.log('\n\n=== step.case rendering logic ===');
|
||||
idx = content.indexOf('step.case');
|
||||
let cnt = 0;
|
||||
while (idx >= 0 && cnt < 5) {
|
||||
const ctx = content.substring(Math.max(0, idx - 100), idx + 300);
|
||||
console.log(`\n--- step.case @${idx} ---`);
|
||||
console.log(ctx.substring(0, 400));
|
||||
idx = content.indexOf('step.case', idx + 9);
|
||||
cnt++;
|
||||
}
|
||||
|
||||
// 5. Find the MarkdownRenderer usage (how AI text gets rendered)
|
||||
console.log('\n\n=== MarkdownRenderer usage ===');
|
||||
idx = content.indexOf('MarkdownRenderer');
|
||||
cnt = 0;
|
||||
while (idx >= 0 && cnt < 5) {
|
||||
const ctx = content.substring(Math.max(0, idx - 200), idx + 300);
|
||||
if (ctx.includes('children') || ctx.includes('content') || ctx.includes('text')) {
|
||||
console.log(`\n--- MarkdownRenderer @${idx} ---`);
|
||||
console.log(ctx.substring(0, 500));
|
||||
}
|
||||
idx = content.indexOf('MarkdownRenderer', idx + 16);
|
||||
cnt++;
|
||||
}
|
||||
|
||||
// 6. Find what lHr component is (the Allow/Deny dialog)
|
||||
console.log('\n\n=== lHr component (Allow/Deny dialog) ===');
|
||||
idx = content.indexOf('lHr');
|
||||
if (idx >= 0) {
|
||||
// Search for its definition
|
||||
const defIdx = content.indexOf('function lHr');
|
||||
const def2 = content.indexOf('lHr=');
|
||||
const targetIdx = defIdx >= 0 ? defIdx : def2;
|
||||
if (targetIdx >= 0) {
|
||||
console.log(content.substring(targetIdx, targetIdx + 600));
|
||||
}
|
||||
}
|
||||
72
scratch_bundle_deep4.js
Normal file
72
scratch_bundle_deep4.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const fs = require('fs');
|
||||
const bundlePath = String.raw`C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js`;
|
||||
const content = fs.readFileSync(bundlePath, 'utf-8');
|
||||
|
||||
// 1. Find Whi (plannerResponse renderer)
|
||||
console.log('=== Whi (plannerResponse renderer) ===');
|
||||
let idx = content.indexOf('Whi=');
|
||||
if (idx < 0) idx = content.indexOf('Whi =');
|
||||
if (idx < 0) idx = content.indexOf('function Whi');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(idx, idx + 1500));
|
||||
} else {
|
||||
// Try to find it differently
|
||||
idx = content.indexOf('renderer:Whi');
|
||||
if (idx >= 0) {
|
||||
// Search backwards for Whi definition
|
||||
const searchArea = content.substring(Math.max(0, idx - 50000), idx);
|
||||
const defIdx2 = searchArea.lastIndexOf('Whi');
|
||||
if (defIdx2 >= 0) {
|
||||
const absIdx = Math.max(0, idx - 50000) + defIdx2;
|
||||
console.log(`Found Whi near @${absIdx}:`);
|
||||
console.log(content.substring(absIdx, absIdx + 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Find the Put component (trajectory rendering)
|
||||
console.log('\n\n=== Put (trajectory/step list renderer) ===');
|
||||
idx = content.indexOf('Put,{trajectory');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(Math.max(0, idx - 200), idx + 600));
|
||||
}
|
||||
|
||||
// 3. Look at how steps are rendered in the conversation view
|
||||
console.log('\n\n=== Step rendering in conversation ===');
|
||||
// Look for the component that renders individual steps
|
||||
for (const pat of ['renderStep', 'StepRenderer', 'stepRenderer', 'renderTool', 'ToolRenderer']) {
|
||||
const i = content.indexOf(pat);
|
||||
if (i >= 0) {
|
||||
console.log(`\n--- ${pat} @${i} ---`);
|
||||
console.log(content.substring(Math.max(0, i - 100), i + 400).substring(0, 500));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Find response/message text content rendering
|
||||
console.log('\n\n=== "prose" usage in step/conversation context ===');
|
||||
let pos = 0;
|
||||
let cnt2 = 0;
|
||||
while ((pos = content.indexOf('prose', pos)) >= 0 && cnt2 < 8) {
|
||||
const ctx = content.substring(Math.max(0, pos - 100), pos + 200);
|
||||
if (ctx.includes('className') && (ctx.includes('step') || ctx.includes('response') || ctx.includes('message') || ctx.includes('text') || ctx.includes('content') || ctx.includes('bot'))) {
|
||||
console.log(`\n--- prose @${pos} ---`);
|
||||
console.log(ctx.substring(0, 300));
|
||||
cnt2++;
|
||||
}
|
||||
pos += 5;
|
||||
}
|
||||
|
||||
// 5. the "agent-convo-background" class and its surrounding context
|
||||
console.log('\n\n=== agent-convo-background context ===');
|
||||
idx = content.indexOf('agent-convo-background');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(Math.max(0, idx - 200), idx + 500));
|
||||
}
|
||||
|
||||
// 6. Find how the step list is rendered in the main view (not debug panel)
|
||||
console.log('\n\n=== Main view step rendering (near conversation-view) ===');
|
||||
idx = content.indexOf('"conversation-view"');
|
||||
if (idx >= 0) {
|
||||
// Look forward for the children rendering
|
||||
console.log(content.substring(idx, idx + 3000));
|
||||
}
|
||||
79
scratch_bundle_deep5.js
Normal file
79
scratch_bundle_deep5.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const fs = require('fs');
|
||||
const bundlePath = String.raw`C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js`;
|
||||
const content = fs.readFileSync(bundlePath, 'utf-8');
|
||||
|
||||
// 1. Find Put component definition (trajectory step list renderer)
|
||||
console.log('=== Put component definition ===');
|
||||
let idx = content.indexOf('Put=');
|
||||
// There might be many "Put=", find the one related to trajectory
|
||||
let pos = 0;
|
||||
let found = false;
|
||||
while ((pos = content.indexOf('Put=', pos)) >= 0) {
|
||||
const ctx = content.substring(pos, pos + 200);
|
||||
if (ctx.includes('trajectory') || ctx.includes('steps') || ctx.includes('Step') || ctx.includes('queue')) {
|
||||
console.log(`@${pos}: ${ctx}`);
|
||||
console.log('\nFull definition:');
|
||||
console.log(content.substring(pos, pos + 2000));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
pos += 4;
|
||||
}
|
||||
if (!found) {
|
||||
// Try function Put
|
||||
idx = content.indexOf('function Put');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(idx, idx + 2000));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Find the individual step rendering — how each step case maps to a renderer
|
||||
console.log('\n\n=== Step case renderer mapping (near Whi) ===');
|
||||
// The object that maps step cases to renderers
|
||||
idx = content.indexOf('plannerResponse:{isRendered');
|
||||
if (idx >= 0) {
|
||||
// Go back to find the start of this mapping object
|
||||
const start = Math.max(0, idx - 3000);
|
||||
const section = content.substring(start, idx + 500);
|
||||
// Find the start of the mapping
|
||||
const mapStart = section.lastIndexOf('{');
|
||||
// Actually, let's get the whole renderer map
|
||||
const bigStart = Math.max(0, idx - 4000);
|
||||
console.log(content.substring(bigStart, idx + 800));
|
||||
}
|
||||
|
||||
// 3. Find <a> (markdown renderer) and how it renders children
|
||||
console.log('\n\n=== Markdown "a" component (renders AI text) ===');
|
||||
// From Whi, we know it uses n.markdown which is {a}
|
||||
// The key line is: v(a,{animate:t!==la.DONE,children:e.modifiedResponse})
|
||||
// So `a` is the markdown renderer and children is the text
|
||||
// Let's find what CSS classes the markdown renderer uses
|
||||
for (const pat of ['prose ', 'markdown-content', 'text-ide-text-color', 'prose-a:', 'text-idle-foreground']) {
|
||||
const i = content.indexOf(pat);
|
||||
if (i >= 0) {
|
||||
const ctx = content.substring(Math.max(0, i - 100), i + 200);
|
||||
if (ctx.includes('className')) {
|
||||
console.log(`\n--- "${pat}" @${i} ---`);
|
||||
console.log(ctx.substring(0, 300));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Find the "thinking" component rendering (Klt)
|
||||
console.log('\n\n=== Klt (thinking component) ===');
|
||||
idx = content.indexOf('Klt=');
|
||||
if (idx < 0) idx = content.indexOf('function Klt');
|
||||
if (idx >= 0) {
|
||||
console.log(content.substring(idx, idx + 800));
|
||||
}
|
||||
|
||||
// 5. Find the lHr component wrapper classes (Allow/Deny bar)
|
||||
console.log('\n\n=== lHr surrounding context ===');
|
||||
idx = content.indexOf('lHr,{');
|
||||
let cnt = 0;
|
||||
while (idx >= 0 && cnt < 3) {
|
||||
console.log(`\n--- lHr usage @${idx} ---`);
|
||||
console.log(content.substring(Math.max(0, idx - 300), idx + 200).substring(0, 500));
|
||||
idx = content.indexOf('lHr,{', idx + 5);
|
||||
cnt++;
|
||||
}
|
||||
77
scratch_bundle_scan.js
Normal file
77
scratch_bundle_scan.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const bundlePath = String.raw`C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js`;
|
||||
|
||||
if (!fs.existsSync(bundlePath)) {
|
||||
console.log('Bundle not found at:', bundlePath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(bundlePath, 'utf-8');
|
||||
console.log('Bundle size:', (content.length / 1024 / 1024).toFixed(1), 'MB');
|
||||
|
||||
// Search for key DOM-related terms
|
||||
const terms = [
|
||||
'data-testid',
|
||||
'conversation-view',
|
||||
'data-step-index',
|
||||
'message-block-bot',
|
||||
'markdown-body',
|
||||
'prose',
|
||||
'rendered-markdown',
|
||||
'step-container',
|
||||
'chat-message',
|
||||
'bot-message',
|
||||
'ai-message',
|
||||
'agent-response',
|
||||
'tool-call',
|
||||
'tool-result',
|
||||
'step-content',
|
||||
'message-content',
|
||||
'conversation-container',
|
||||
'chat-content',
|
||||
'response-text',
|
||||
'planner-response',
|
||||
];
|
||||
|
||||
for (const t of terms) {
|
||||
const idx = content.indexOf(t);
|
||||
if (idx >= 0) {
|
||||
const start = Math.max(0, idx - 200);
|
||||
const end = Math.min(content.length, idx + 200);
|
||||
console.log(`\n${'='.repeat(60)}`);
|
||||
console.log(`FOUND: "${t}" at offset ${idx}`);
|
||||
console.log(`${'='.repeat(60)}`);
|
||||
console.log(content.substring(start, end));
|
||||
} else {
|
||||
console.log(`NOT FOUND: "${t}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// Also find all data-testid values
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('ALL data-testid values:');
|
||||
console.log('='.repeat(60));
|
||||
const testIdPattern = /data-testid[=:]["']([^"']+)["']/g;
|
||||
const testIds = new Set();
|
||||
let m;
|
||||
while ((m = testIdPattern.exec(content)) !== null) {
|
||||
testIds.add(m[1]);
|
||||
}
|
||||
for (const id of [...testIds].sort()) {
|
||||
console.log(' -', id);
|
||||
}
|
||||
|
||||
// Find all "className" or class patterns near Step/Message/Conversation
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('Step/Message/Conversation related class patterns:');
|
||||
console.log('='.repeat(60));
|
||||
const classPattern = /(?:className|class)[=:]"([^"]*(?:step|message|conversation|chat|agent|bot)[^"]*)"/gi;
|
||||
const classes = new Set();
|
||||
while ((m = classPattern.exec(content)) !== null) {
|
||||
classes.add(m[1].trim().substring(0, 100));
|
||||
}
|
||||
for (const cls of [...classes].sort()) {
|
||||
console.log(' -', cls);
|
||||
}
|
||||
64
scratch_bundle_search.py
Normal file
64
scratch_bundle_search.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Search AG bundle for UI component patterns, specifically:
|
||||
1. Bot message container classes/selectors
|
||||
2. Approval button patterns
|
||||
3. Chat conversation structure
|
||||
"""
|
||||
import re, os, sys, io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
bundle_path = r"C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js"
|
||||
content = open(bundle_path, encoding='utf-8', errors='replace').read()
|
||||
print(f"Bundle size: {len(content)} chars")
|
||||
|
||||
# Search for string literals containing relevant UI text
|
||||
# These are the strings we SEE in the UI, so they must exist in the bundle
|
||||
ui_strings = [
|
||||
'Running', 'command', 'Always run', 'Cancel',
|
||||
'Allow', 'Deny', 'content_copy',
|
||||
'Accept', 'Reject', 'Approve',
|
||||
'AI 대화', 'AI Chat', 'AI Response',
|
||||
'keyboard_arrow', 'chevron',
|
||||
'Run ', 'Send',
|
||||
]
|
||||
|
||||
print("\n=== UI String contexts (20 chars around match) ===")
|
||||
for s in ui_strings:
|
||||
# Find the string and show surrounding context
|
||||
idx = content.find(f'"{s}')
|
||||
if idx == -1:
|
||||
idx = content.find(f"'{s}")
|
||||
if idx == -1:
|
||||
idx = content.find(s)
|
||||
if idx >= 0:
|
||||
start = max(0, idx - 30)
|
||||
end = min(len(content), idx + len(s) + 50)
|
||||
ctx = content[start:end].replace('\n', ' ')
|
||||
print(f" '{s}': ...{ctx}...")
|
||||
|
||||
# Specifically search for "Running" near button/onClick patterns
|
||||
print("\n=== 'Running' in button context ===")
|
||||
for m in re.finditer(r'Running.{0,5}command', content):
|
||||
start = max(0, m.start() - 100)
|
||||
end = min(len(content), m.end() + 100)
|
||||
ctx = content[start:end].replace('\n', ' ')
|
||||
print(f" @{m.start()}: ...{ctx[:200]}...")
|
||||
|
||||
# Search for className patterns with Tailwind classes
|
||||
print("\n=== Tailwind class patterns near 'message' or 'chat' ===")
|
||||
for m in re.finditer(r'"((?:flex|bg-|text-|rounded|p-|m-|w-|h-)[^"]{10,200})"', content):
|
||||
cls = m.group(1)
|
||||
if any(kw in content[max(0,m.start()-200):m.start()].lower() for kw in ['message', 'chat', 'response', 'bot', 'turn', 'agent']):
|
||||
print(f" {cls[:100]}")
|
||||
|
||||
# Most important: search for React component names
|
||||
print("\n=== React component names containing 'Message', 'Chat', 'Turn', 'Agent' ===")
|
||||
for m in re.finditer(r'(?:function|class|const|var)\s+([A-Z][a-zA-Z]*(?:Message|Chat|Turn|Agent|Conversation|Response|Approval|Pending)[A-Za-z]*)', content):
|
||||
print(f" {m.group(1)}")
|
||||
|
||||
# Search for data attributes
|
||||
print("\n=== data-* attributes ===")
|
||||
for m in re.finditer(r'"(data-[a-z-]+)"', content):
|
||||
attr = m.group(1)
|
||||
if attr not in ('data-vscode-context',):
|
||||
print(f" {attr}")
|
||||
@@ -1,36 +1,13 @@
|
||||
import os, datetime
|
||||
now = datetime.datetime.now()
|
||||
date_str = now.strftime('%Y-%m-%d')
|
||||
time_str = now.strftime('%H:%M')
|
||||
"""Update devlog index with the commit entry."""
|
||||
path = r"c:\Users\Variet-Worker\Desktop\gravity_control\docs\devlog\2026-04-12.md"
|
||||
entry = "| 001 | 06:12 | AG Native DOM 파싱 v7 전면 재설계 — data-testid/data-step-index 기반 step-aware 파서, UI 노이즈 차단 | `a4d7286` | 🔧 |\n"
|
||||
|
||||
with open(r'c:\Users\Variet-Worker\Desktop\gravity_control\.agents\references\known-issues.md', 'a', encoding='utf-8') as f:
|
||||
f.write('\n### ['+date_str+'] [Probe Logging] — AI응답 텍스트 & WAITING 스텝 동시 누락 버그\n')
|
||||
f.write('- **증상**: 굉장히 빠른 AI 응답(또는 즉각적인 툴 호출) 시 `step-probe.ts`가 메시지와 승인 다이얼로그를 모두 Discord로 릴레이하지 못함.\n')
|
||||
f.write('- **원인**: 실시간 텍스트 캡처(`delta > 0`) 조건에 `isRunning &&`이 걸려있어, 상태가 `WAITING`이나 `IDLE`로 즉시 넘어가면 텍스트를 캡처하는 루틴이 전부 스킵됨. 또한 이 순간 `isStall` 조건도 타지 않아 `WAITING` 디텍션도 증발함.\n')
|
||||
f.write('- **해결**: 실시간 캡처 로직에서 `isRunning &&` 조건을 제거하고, `delta > 0`일 때 추가된 최신 스텝을 스캔하면서 `PLANNER_RESPONSE`와 `WAITING` 스텝을 모두 처리하도록 수정함.\n')
|
||||
f.write('- **주의**: LS Backend 10개 Session 제한 버그가 있어, 다른 창에서 수동 채팅(`1fbca84c`)이 IDLE로 남아있으면 자동화 에이전트의 워크스페이스 세션과 헷갈릴 수 있으나, 이 버그는 polling 타이밍 문제였음.\n')
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
print('known-issues updated.')
|
||||
content = content.rstrip() + "\n" + entry
|
||||
|
||||
log_dir = r'c:\Users\Variet-Worker\Desktop\gravity_control\docs\devlog'
|
||||
index_file = os.path.join(log_dir, date_str + '.md')
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
try:
|
||||
with open(index_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
nnn = len([l for l in lines if '|' in l])
|
||||
except:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
with open(index_file, 'w', encoding='utf-8') as f:
|
||||
f.write('| NNN | 시간 | 작업 설명 | 커밋해시 | 완료여부 |\n|---|---|---|---|---|\n')
|
||||
nnn = 1
|
||||
|
||||
entry_num = f'{nnn:03d}'
|
||||
entry_file = os.path.join(log_dir, 'entries', f'{date_str.replace("-", "")}-{entry_num}.md')
|
||||
|
||||
with open(index_file, 'a', encoding='utf-8') as f:
|
||||
f.write(f'| {entry_num} | {time_str} | step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스 | COMMITTING | ✅ |\n')
|
||||
|
||||
os.makedirs(os.path.dirname(entry_file), exist_ok=True)
|
||||
with open(entry_file, 'w', encoding='utf-8') as f:
|
||||
f.write(f'# step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스\n\n- **시간**: {date_str} {time_str}\n- **Commit**: `COMMITTING`\n- **Vikunja**: #125 → done\n\n## 결정 사항\n- AI 응답이 비정상적으로 빠를 경우 `RUNNING` 상태의 2초 polling 창을 우회하여 `IDLE` / `WAITING`로 진입해버리는 버그가 있었습니다.\n- 기존에는 `isRunning && currentCount > ...`로만 Real-time Capture가 동작하여 전부 스킵되는 증상 확인.\n- `isRunning` 조건을 삭제하고, `delta > 0`인 경우 `GetCascadeTrajectorySteps`를 페치하여 `PLANNER_RESPONSE`와 `WAITING` 스텝을 동시에 처리하도록 개선했습니다.\n\n## 미완료\n- 없음.\n')
|
||||
print("OK: devlog entry added")
|
||||
|
||||
94
scratch_dom_classes.py
Normal file
94
scratch_dom_classes.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Modify observer script to dump the actual DOM structure around detected buttons
|
||||
and bot message containers. Write results to bridge/dom_structure.json
|
||||
"""
|
||||
import requests, json
|
||||
|
||||
BASE = "http://127.0.0.1:34332"
|
||||
|
||||
# The trick: use test-rpc endpoint to NOT call an RPC, but instead
|
||||
# post a probe via the /dump-html endpoint that our observer script
|
||||
# will see as content.
|
||||
|
||||
# Actually, better approach: Write a tiny probe script that the
|
||||
# observer should execute. But we can't inject new scripts at runtime.
|
||||
|
||||
# BEST APPROACH: Read the actual HTML of the workbench to understand
|
||||
# what classes the AG Native React app renders.
|
||||
|
||||
# Check the AG main JS files to understand class names
|
||||
import os, re
|
||||
|
||||
ag_base = r"C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out"
|
||||
|
||||
# The jetski agent JS is the main entry point
|
||||
jetski_dir = os.path.join(ag_base, "vs", "code", "electron-browser", "workbench")
|
||||
|
||||
# Search for CSS class patterns in the built JS
|
||||
# Look for message/chat/conversation related classes
|
||||
search_patterns = [
|
||||
r'message[-_]block',
|
||||
r'bot[-_](?:message|color|response|turn)',
|
||||
r'agent[-_](?:convo|message|response)',
|
||||
r'chat[-_](?:body|message|content)',
|
||||
r'markdown[-_]body',
|
||||
r'text[-_]ide',
|
||||
r'(?:pending|approval|approve)[-_]',
|
||||
r'actions[-_]container',
|
||||
r'tool[-_](?:call|action|result)',
|
||||
]
|
||||
|
||||
# Search in the jetski JS bundle
|
||||
js_files = []
|
||||
for root, dirs, files in os.walk(ag_base):
|
||||
for f in files:
|
||||
if f.endswith('.js') and ('jetski' in f.lower() or 'agent' in f.lower()):
|
||||
js_files.append(os.path.join(root, f))
|
||||
# Don't recurse too deep
|
||||
if root.count(os.sep) - ag_base.count(os.sep) > 5:
|
||||
dirs.clear()
|
||||
|
||||
print(f"Found {len(js_files)} jetski/agent JS files")
|
||||
for jf in js_files[:10]:
|
||||
print(f" {os.path.relpath(jf, ag_base)}: {os.path.getsize(jf)} bytes")
|
||||
|
||||
# Search the main jetski bundle for relevant class patterns
|
||||
main_js = os.path.join(jetski_dir, "jetskiAgent.js")
|
||||
if os.path.exists(main_js):
|
||||
content = open(main_js, encoding='utf-8', errors='replace').read()
|
||||
print(f"\njetskiAgent.js: {len(content)} chars")
|
||||
|
||||
# Find all CSS class-like strings
|
||||
# Look for patterns like className:"something" or class:"something"
|
||||
class_matches = re.findall(r'(?:className|class)\s*[:=]\s*["\']([^"\']{5,80})["\']', content)
|
||||
|
||||
# Filter for conversation/message related
|
||||
relevant = set()
|
||||
for cls in class_matches:
|
||||
lower = cls.lower()
|
||||
if any(kw in lower for kw in ['message', 'chat', 'bot', 'agent', 'response',
|
||||
'markdown', 'convo', 'turn', 'approval',
|
||||
'pending', 'action', 'tool', 'content',
|
||||
'text-ide', 'block']):
|
||||
relevant.add(cls)
|
||||
|
||||
print(f"\nRelevant CSS classes ({len(relevant)}):")
|
||||
for cls in sorted(relevant)[:50]:
|
||||
print(f" .{cls}")
|
||||
|
||||
# Also search for data-testid patterns
|
||||
testid_matches = re.findall(r'data-testid\s*[:=]\s*["\']([^"\']+)["\']', content)
|
||||
if testid_matches:
|
||||
print(f"\ndata-testid values ({len(testid_matches)}):")
|
||||
for tid in sorted(set(testid_matches))[:30]:
|
||||
print(f" [{tid}]")
|
||||
|
||||
# Search for the specific bot/assistant message container patterns
|
||||
for pat in search_patterns:
|
||||
matches = re.findall(f'["\']([^"\']*{pat}[^"\']*)["\']', content, re.IGNORECASE)
|
||||
if matches:
|
||||
unique = sorted(set(matches))[:5]
|
||||
print(f"\n Pattern '{pat}': {unique}")
|
||||
|
||||
else:
|
||||
print(f"\njetskiAgent.js NOT FOUND at {main_js}")
|
||||
125
scratch_dom_inspector.js
Normal file
125
scratch_dom_inspector.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* AG Native DOM Inspector — CDP를 통해 AG의 renderer에 연결하여 DOM을 덤프
|
||||
* AG가 --remote-debugging-port 없이 실행 중이므로,
|
||||
* 대안으로 AG 내부 extension의 executeJavaScript를 통해 DOM을 캡처합니다.
|
||||
*
|
||||
* 사용법: AG에서 Gravity Bridge가 활성화된 후, 이 스크립트를 extension 내에서 실행
|
||||
* 또는 AG의 DevTools Console에서 직접 실행
|
||||
*/
|
||||
|
||||
// AG DevTools Console에서 실행할 스크립트 (Ctrl+Shift+I로 열기)
|
||||
const domInspectScript = `
|
||||
(function() {
|
||||
// 1. conversation-view 찾기
|
||||
var cv = document.querySelector('[data-testid="conversation-view"]');
|
||||
console.log('=== AG Native DOM Inspector ===');
|
||||
console.log('conversation-view found:', !!cv);
|
||||
|
||||
if (!cv) {
|
||||
// document의 전체 구조를 간략히 출력
|
||||
function summarize(el, depth) {
|
||||
if (depth > 5) return '';
|
||||
var tag = el.tagName ? el.tagName.toLowerCase() : '#text';
|
||||
var cls = (el.className && typeof el.className === 'string') ? el.className.substring(0, 80) : '';
|
||||
var id = el.id || '';
|
||||
var dataAttrs = [];
|
||||
if (el.attributes) {
|
||||
for (var i = 0; i < el.attributes.length; i++) {
|
||||
if (el.attributes[i].name.startsWith('data-')) {
|
||||
dataAttrs.push(el.attributes[i].name + '=' + el.attributes[i].value.substring(0, 50));
|
||||
}
|
||||
}
|
||||
}
|
||||
var indent = ' '.repeat(depth);
|
||||
var line = indent + '<' + tag;
|
||||
if (id) line += '#' + id;
|
||||
if (cls) line += ' class="' + cls + '"';
|
||||
if (dataAttrs.length) line += ' ' + dataAttrs.join(' ');
|
||||
line += '>';
|
||||
|
||||
var result = line + '\\n';
|
||||
if (el.children && depth < 4) {
|
||||
for (var c = 0; c < Math.min(el.children.length, 15); c++) {
|
||||
result += summarize(el.children[c], depth + 1);
|
||||
}
|
||||
if (el.children.length > 15) {
|
||||
result += indent + ' ... +' + (el.children.length - 15) + ' more\\n';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
console.log('Full body structure:');
|
||||
console.log(summarize(document.body, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. conversation-view 내부 구조 덤프
|
||||
function walkDetail(el, depth) {
|
||||
if (depth > 8) return null;
|
||||
var info = {
|
||||
tag: el.tagName ? el.tagName.toLowerCase() : '#text',
|
||||
cls: (el.className && typeof el.className === 'string') ? el.className.substring(0, 150) : '',
|
||||
dataAttrs: {},
|
||||
text: '',
|
||||
childCount: el.children ? el.children.length : 0,
|
||||
children: []
|
||||
};
|
||||
|
||||
if (el.attributes) {
|
||||
for (var i = 0; i < el.attributes.length; i++) {
|
||||
var attr = el.attributes[i];
|
||||
if (attr.name.startsWith('data-') || attr.name === 'role' || attr.name === 'aria-label' || attr.name === 'title') {
|
||||
info.dataAttrs[attr.name] = (attr.value || '').substring(0, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!el.children || el.children.length === 0) {
|
||||
var t = (el.textContent || '').trim();
|
||||
if (t.length > 0 && t.length < 150) info.text = t;
|
||||
}
|
||||
|
||||
if (el.children) {
|
||||
for (var c = 0; c < Math.min(el.children.length, 12); c++) {
|
||||
var child = walkDetail(el.children[c], depth + 1);
|
||||
if (child) info.children.push(child);
|
||||
}
|
||||
if (el.children.length > 12) {
|
||||
info.children.push({tag: '...', text: '+' + (el.children.length - 12) + ' more'});
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
var result = walkDetail(cv, 0);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
// 3. 특정 요소들 확인
|
||||
console.log('\\n=== Key Selectors ===');
|
||||
console.log('[data-step-index] count:', cv.querySelectorAll('[data-step-index]').length);
|
||||
console.log('.text-ide-message-block-bot-color count:', cv.querySelectorAll('.text-ide-message-block-bot-color').length);
|
||||
console.log('[class*="prose"] count:', cv.querySelectorAll('[class*="prose"]').length);
|
||||
console.log('[class*="markdown"] count:', cv.querySelectorAll('[class*="markdown"]').length);
|
||||
console.log('[class*="px-2"][class*="py-1"] count:', cv.querySelectorAll('[class*="px-2"][class*="py-1"]').length);
|
||||
console.log('button count:', cv.querySelectorAll('button').length);
|
||||
|
||||
// 4. 버튼 텍스트 목록
|
||||
var btns = cv.querySelectorAll('button');
|
||||
console.log('\\n=== Buttons in conversation-view ===');
|
||||
for (var b = 0; b < btns.length; b++) {
|
||||
var txt = (btns[b].textContent || '').trim().substring(0, 80);
|
||||
console.log(' [' + b + '] "' + txt + '"');
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
||||
console.log('=== AG Native DOM Inspector Script ===');
|
||||
console.log('');
|
||||
console.log('AG DevTools Console에서 아래 스크립트를 실행하세요:');
|
||||
console.log('AG에서 DevTools를 열려면: Ctrl+Shift+I');
|
||||
console.log('');
|
||||
console.log('────────────────────────────────────────');
|
||||
console.log(domInspectScript);
|
||||
console.log('────────────────────────────────────────');
|
||||
90
scratch_dom_probe.py
Normal file
90
scratch_dom_probe.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
Inject a DOM probe into AG Native to capture the actual conversation DOM structure.
|
||||
Posts the DOM structure to the HTTP bridge's /dump-html endpoint.
|
||||
"""
|
||||
import requests, json
|
||||
|
||||
BASE = "http://127.0.0.1:34332"
|
||||
|
||||
# We already have observer script running in workbench-jetski-agent.html
|
||||
# But the issue is: the script is in the OUTER workbench, while the
|
||||
# conversation UI might be in a webview/iframe.
|
||||
|
||||
# Let's first check what the observer CAN see by triggering a dump
|
||||
# We'll post a custom HTML dump request
|
||||
|
||||
# Actually, let's analyze from a different angle:
|
||||
# The observer's scanChatBodies() uses these selectors:
|
||||
# '.text-ide-message-block-bot-color', '[data-testid*="bot"]', etc.
|
||||
# If none match, it returns nothing. But we ARE getting DOM content
|
||||
# (the garbage text), so SOMETHING is matching.
|
||||
|
||||
# The garbage text ("Running command", "content_copy", "Always run", etc.)
|
||||
# This is the tool execution UI, not the AI response.
|
||||
# Let's check what the current status says about sessionStalled
|
||||
|
||||
print("=== Bridge Status ===")
|
||||
status = requests.get(f"{BASE}/status").json()
|
||||
print(json.dumps(status, indent=2))
|
||||
|
||||
# Check if we can identify the DOM structure by examining what
|
||||
# the observer script actually matched.
|
||||
# The fact that it sends "Running command\n...\n content_copy\n Always run"
|
||||
# means it's grabbing a tool execution panel, not the AI text response.
|
||||
|
||||
# Key insight: In AG Native UI (Tailwind/React), the conversation is in
|
||||
# the SAME document as the workbench (not in a separate webview/iframe).
|
||||
# The observer IS running in the right context, BUT the CSS selectors
|
||||
# (.text-ide-message-block-bot-color) don't match AG Native's actual classes.
|
||||
|
||||
# What's happening: scanChatBodies() falls through to the broader selectors
|
||||
# like [class*="agent-convo"] or [class*="bot-message"], which might be
|
||||
# accidentally matching the tool panel UI.
|
||||
|
||||
# SOLUTION: We need to know the actual CSS class names for:
|
||||
# 1. AI response text containers (the markdown output)
|
||||
# 2. Tool call approval containers (Run/Allow/Cancel buttons)
|
||||
|
||||
print("\n=== DOM Probe via dump-html (injecting probe) ===")
|
||||
# The observer's deep-inspect didn't work, but maybe we can
|
||||
# create a targeted probe via a modified observer script
|
||||
|
||||
# Let's check what HTML files are currently being served
|
||||
print("\nChecking if we can reach the webview...")
|
||||
try:
|
||||
# The observer script's scan() function runs every 3s.
|
||||
# Let's see what it's finding by checking recent chat snapshots on disk
|
||||
import os
|
||||
bridge_path = os.path.expanduser("~/.gemini/antigravity/bridge")
|
||||
pending_dir = os.path.join(bridge_path, "pending")
|
||||
if os.path.exists(pending_dir):
|
||||
files = sorted(os.listdir(pending_dir), key=lambda f: os.path.getmtime(os.path.join(pending_dir, f)), reverse=True)
|
||||
print(f"\nRecent pending files: {len(files)}")
|
||||
for f in files[:5]:
|
||||
fpath = os.path.join(pending_dir, f)
|
||||
try:
|
||||
data = json.loads(open(fpath, encoding='utf-8').read())
|
||||
print(f" {f}: cmd=\"{data.get('command','?')[:50]}\" src={data.get('source','?')} type={data.get('step_type','?')}")
|
||||
except Exception as e:
|
||||
print(f" {f}: error: {e}")
|
||||
|
||||
# Check brain directory for session artifacts
|
||||
brain_dir = os.path.expanduser("~/.gemini/antigravity/brain/bdfc07d3-d87e-453a-b785-e38c2e9254e3")
|
||||
if os.path.exists(brain_dir):
|
||||
print(f"\nBrain dir exists: {brain_dir}")
|
||||
entries = os.listdir(brain_dir)
|
||||
print(f" Contents: {entries[:20]}")
|
||||
# Check for conversation log
|
||||
log_dir = os.path.join(brain_dir, ".system_generated", "logs")
|
||||
if os.path.exists(log_dir):
|
||||
print(f" Log dir: {os.listdir(log_dir)}")
|
||||
else:
|
||||
print(f"\nBrain dir NOT found: {brain_dir}")
|
||||
# List available brain dirs
|
||||
parent = os.path.expanduser("~/.gemini/antigravity/brain")
|
||||
if os.path.exists(parent):
|
||||
dirs = sorted(os.listdir(parent), key=lambda d: os.path.getmtime(os.path.join(parent, d)), reverse=True)
|
||||
print(f" Available: {dirs[:5]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
53
scratch_jsx_struct.py
Normal file
53
scratch_jsx_struct.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Find the exact JSX structure around Allow/Deny and message-block-bot containers."""
|
||||
import re, sys, io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
bundle_path = r"C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js"
|
||||
content = open(bundle_path, encoding='utf-8', errors='replace').read()
|
||||
|
||||
# 1. Find the FULL Allow/Deny component (larger context)
|
||||
print("=== Allow/Deny Component (full context) ===")
|
||||
idx = content.find('label:"Allow"')
|
||||
if idx >= 0:
|
||||
start = max(0, idx - 600)
|
||||
end = min(len(content), idx + 500)
|
||||
print(content[start:end])
|
||||
print("\n" + "="*80)
|
||||
|
||||
# 2. Find text-ide-message-block-bot-color full usage
|
||||
print("\n=== text-ide-message-block-bot-color context ===")
|
||||
idx = content.find('text-ide-message-block-bot-color')
|
||||
if idx >= 0:
|
||||
start = max(0, idx - 400)
|
||||
end = min(len(content), idx + 400)
|
||||
print(content[start:end])
|
||||
print("\n" + "="*80)
|
||||
|
||||
# 3. Find data-step-index full context
|
||||
print("\n=== data-step-index context ===")
|
||||
idx = content.find('data-step-index')
|
||||
if idx >= 0:
|
||||
start = max(0, idx - 300)
|
||||
end = min(len(content), idx + 300)
|
||||
print(content[start:end])
|
||||
print("\n" + "="*80)
|
||||
|
||||
# 4. Find "Running" commands JSX pattern
|
||||
print("\n=== Running N command(s) full context ===")
|
||||
for m in re.finditer(r'Running', content[8000000:9000000], re.IGNORECASE):
|
||||
pos = 8000000 + m.start()
|
||||
ctx = content[pos-5:pos+60]
|
||||
if 'command' in ctx.lower() or 'Command' in ctx:
|
||||
start = max(0, pos - 300)
|
||||
end = min(len(content), pos + 300)
|
||||
print(f"@{pos}: {content[start:end]}")
|
||||
print("\n---\n")
|
||||
|
||||
# 5. Find the main conversation/chat container structure
|
||||
print("\n=== Conversation scroll/container patterns ===")
|
||||
for pat in ['scroll-restoration', 'data-scroll', 'overflow-y-auto.*conversation', 'chatScrollContainer']:
|
||||
for m in re.finditer(pat, content, re.IGNORECASE):
|
||||
start = max(0, m.start() - 200)
|
||||
end = min(len(content), m.end() + 200)
|
||||
print(f"Pattern '{pat}' @{m.start()}: ...{content[start:end][:400]}...")
|
||||
print()
|
||||
40
scratch_ki_update.py
Normal file
40
scratch_ki_update.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""Prepend new known-issue entry to known-issues.md"""
|
||||
import sys
|
||||
|
||||
ki_path = r"c:\Users\Variet-Worker\Desktop\gravity_control\.agents\references\known-issues.md"
|
||||
|
||||
new_entry = """### [2026-04-12] [SDK/DOM] AG Native 세션은 Cascade SDK API에 등록되지 않음 — DOM이 유일한 데이터 소스
|
||||
- **증상**: AG Native 세션에서 Discord 릴레이로 AI 응답이 전혀 전달되지 않고, 대신 UI 노이즈(`content_copy`, `Always run`, `keyboard_arrow_up`, `Cancel`)가 전송됨
|
||||
- **원인 1 (SDK)**: `GetCascadeTrajectorySteps(cascadeId=세션ID)` → `500 trajectory not found`. `GetDiagnostics` → `404`. AG Native 세션은 Cascade trajectory API에 전혀 등록되지 않는 별도 시스템
|
||||
- **원인 2 (DOM)**: `observer-script.ts` v6의 `scanChatBodies()`가 `.text-ide-message-block-bot-color` 컨테이너의 `textContent`를 통째로 가져오면서 내부 버튼/아이콘 텍스트까지 포함
|
||||
- **해결**: `observer-script.ts` v7로 전면 재설계:
|
||||
1. `[data-testid="conversation-view"]` + `[data-step-index]` 기반 step-aware 파싱
|
||||
2. `extractCleanStepText()`: 클론 후 button/svg/icon 엘리먼트 제거 → 마크다운 텍스트만 추출
|
||||
3. `extractStepContext()`: `getStepContainer()` → step 헤더 + code 블록만 추출
|
||||
4. `NOISE_RE`: Material icon 이름, 버튼 레이블, UI 텍스트 전면 차단
|
||||
5. 최초 `conversation-view` 감지 시 DOM 구조 자동 덤프 (`/dump-html`)
|
||||
- **주의**: SDK 경로(step-probe RT-CAPTURE)는 AG Native에서 사용 불가. DOM이 유일한 콘텐츠 소스이므로 AG UI 업데이트 시 `data-testid`/`data-step-index` 속성 존재 여부 반드시 확인 필요
|
||||
|
||||
"""
|
||||
|
||||
with open(ki_path, 'rb') as f:
|
||||
raw = f.read()
|
||||
try:
|
||||
content = raw.decode('utf-8')
|
||||
except:
|
||||
content = raw.decode('cp949', errors='replace')
|
||||
|
||||
# Find the "---" separator and insert after it
|
||||
marker = "---\n"
|
||||
idx = content.find(marker, content.find("archive"))
|
||||
if idx >= 0:
|
||||
insert_pos = idx + len(marker) + 1 # after ---\n\n
|
||||
# Find actual end of marker section
|
||||
after_marker = content[idx + len(marker):]
|
||||
# Insert new entry
|
||||
new_content = content[:idx + len(marker)] + "\n" + new_entry + after_marker
|
||||
with open(ki_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
print("OK: known-issue entry added")
|
||||
else:
|
||||
print("ERROR: could not find insertion point")
|
||||
26
scratch_reload_ag.js
Normal file
26
scratch_reload_ag.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const http = require('http');
|
||||
|
||||
// Try to reload AG window via test-rpc
|
||||
const payload = JSON.stringify({
|
||||
method: "antigravity.reloadWindow",
|
||||
args: {}
|
||||
});
|
||||
|
||||
const req = http.request({
|
||||
hostname: '127.0.0.1',
|
||||
port: 34332,
|
||||
path: '/test-rpc',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
console.log('Status:', res.statusCode);
|
||||
console.log('Response:', data);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', e => console.log('Error:', e.message));
|
||||
req.write(payload);
|
||||
req.end();
|
||||
6
scratch_rpc_dom.json
Normal file
6
scratch_rpc_dom.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"method": "antigravity.workbench.executeJavaScript",
|
||||
"args": {
|
||||
"code": "JSON.stringify({cv: !!document.querySelector('[data-testid=\"conversation-view\"]'), total: document.querySelectorAll('*').length, btns: document.querySelectorAll('button').length, title: document.title})"
|
||||
}
|
||||
}
|
||||
41
scratch_rpc_exec.js
Normal file
41
scratch_rpc_exec.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const http = require('http');
|
||||
|
||||
const payload = JSON.stringify({
|
||||
method: "antigravity.workbench.executeJavaScript",
|
||||
args: {
|
||||
code: `JSON.stringify({
|
||||
title: document.title,
|
||||
total: document.querySelectorAll('*').length,
|
||||
btns: document.querySelectorAll('button').length,
|
||||
cv: !!document.querySelector('[data-testid="conversation-view"]'),
|
||||
stepEls: document.querySelectorAll('[data-step-index]').length,
|
||||
botColor: document.querySelectorAll('.text-ide-message-block-bot-color').length,
|
||||
prose: document.querySelectorAll('[class*="prose"]').length,
|
||||
agentConvo: document.querySelectorAll('[class*="agent-convo"]').length
|
||||
})`
|
||||
}
|
||||
});
|
||||
|
||||
const req = http.request({
|
||||
hostname: '127.0.0.1',
|
||||
port: 34332,
|
||||
path: '/test-rpc',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
console.log('Status:', res.statusCode);
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
console.log('Result:', JSON.stringify(parsed, null, 2));
|
||||
} catch {
|
||||
console.log('Raw:', data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', e => console.log('Error:', e.message));
|
||||
req.write(payload);
|
||||
req.end();
|
||||
71
scratch_rpc_test.py
Normal file
71
scratch_rpc_test.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Test AG SDK RPC to understand what data is available for current session."""
|
||||
import requests, json, sys, io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
BASE = "http://127.0.0.1:34332"
|
||||
SESSION = "bdfc07d3-d87e-453a-b785-e38c2e9254e3"
|
||||
|
||||
def rpc(method, args=None):
|
||||
r = requests.post(f"{BASE}/test-rpc", json={"method": method, "args": args or {}})
|
||||
if r.status_code != 200:
|
||||
print(f"❌ {method}: {r.status_code} - {r.text[:200]}")
|
||||
return None
|
||||
try:
|
||||
return r.json()
|
||||
except:
|
||||
print(f"❌ {method}: non-JSON response: {r.text[:200]}")
|
||||
return None
|
||||
|
||||
# 1. Try GetCascadeTrajectorySteps for current session
|
||||
print("=== GetCascadeTrajectorySteps ===")
|
||||
result = rpc("GetCascadeTrajectorySteps", {"cascadeId": SESSION, "verbosity": 1})
|
||||
if result and "steps" in result:
|
||||
steps = result["steps"]
|
||||
print(f" Got {len(steps)} steps")
|
||||
for i, s in enumerate(steps[-5:]):
|
||||
print(f" Step {i}: type={s.get('type','?')} status={s.get('status','?')}")
|
||||
if s.get('plannerResponse'):
|
||||
pr = s['plannerResponse']
|
||||
if isinstance(pr, str):
|
||||
print(f" plannerResponse (str): {pr[:100]}...")
|
||||
elif isinstance(pr, dict):
|
||||
print(f" plannerResponse keys: {list(pr.keys())}")
|
||||
for k, v in pr.items():
|
||||
if isinstance(v, str) and len(v) > 20 and k not in ('thinking', 'thinkingSignature'):
|
||||
print(f" {k}: {v[:100]}...")
|
||||
else:
|
||||
print(" No steps returned")
|
||||
|
||||
# 2. Try GetDiagnostics
|
||||
print("\n=== GetDiagnostics ===")
|
||||
diag = rpc("GetDiagnostics", {})
|
||||
if diag:
|
||||
if isinstance(diag, str):
|
||||
diag = json.loads(diag)
|
||||
recent = diag.get("recentTrajectories", [])
|
||||
print(f" recentTrajectories: {len(recent)}")
|
||||
for rt in recent:
|
||||
sid = rt.get("googleAgentId", "?")
|
||||
if sid.startswith("bdfc"):
|
||||
print(f" ★ Current session: {json.dumps(rt, indent=2)[:500]}")
|
||||
|
||||
# 3. Try GetAllCascadeTrajectories looking for our session
|
||||
print("\n=== GetAllCascadeTrajectories ===")
|
||||
traj = rpc("GetAllCascadeTrajectories", {"limit": 100, "descending": True})
|
||||
if traj and "trajectorySummaries" in traj:
|
||||
summaries = traj["trajectorySummaries"]
|
||||
print(f" Total trajectories: {len(summaries)}")
|
||||
for sid, data in summaries.items():
|
||||
if sid.startswith("bdfc"):
|
||||
print(f" ★ Current session keys: {list(data.keys())}")
|
||||
print(f" status: {data.get('status')}")
|
||||
print(f" stepCount: {data.get('stepCount')}")
|
||||
print(f" latestNotifyUserStep: {json.dumps(data.get('latestNotifyUserStep'), indent=2)[:300] if data.get('latestNotifyUserStep') else 'None'}")
|
||||
print(f" latestTaskBoundaryStep: {json.dumps(data.get('latestTaskBoundaryStep'), indent=2)[:300] if data.get('latestTaskBoundaryStep') else 'None'}")
|
||||
|
||||
# 4. Try other RPC methods that might exist
|
||||
print("\n=== Trying alternative RPCs ===")
|
||||
for method in ["GetCascadeStatus", "GetAgentStatus", "ListCascades", "GetCascadeInfo"]:
|
||||
result = rpc(method, {"cascadeId": SESSION})
|
||||
if result:
|
||||
print(f" {method}: {json.dumps(result)[:200]}")
|
||||
52
scratch_rpc_test2.js
Normal file
52
scratch_rpc_test2.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const http = require('http');
|
||||
|
||||
// Try different RPC methods to see what's available
|
||||
const methods = [
|
||||
{ method: "antigravity.workbench.GetDiagnostics", args: {} },
|
||||
{ method: "GetDiagnostics", args: {} },
|
||||
];
|
||||
|
||||
async function tryRPC(method, args) {
|
||||
return new Promise((resolve) => {
|
||||
const payload = JSON.stringify({ method, args });
|
||||
const req = http.request({
|
||||
hostname: '127.0.0.1',
|
||||
port: 34332,
|
||||
path: '/test-rpc',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
console.log(`\n=== ${method} (${res.statusCode}) ===`);
|
||||
if (data.length > 2000) {
|
||||
console.log(data.substring(0, 2000) + '\n... (truncated)');
|
||||
} else {
|
||||
console.log(data);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
req.on('error', e => { console.log(`${method}: ERROR ${e.message}`); resolve(); });
|
||||
req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
(async () => {
|
||||
for (const m of methods) {
|
||||
await tryRPC(m.method, m.args);
|
||||
}
|
||||
|
||||
// Also try the /status endpoint for full state
|
||||
const statusReq = http.get('http://127.0.0.1:34332/status', (res) => {
|
||||
let d = '';
|
||||
res.on('data', c => d += c);
|
||||
res.on('end', () => {
|
||||
console.log('\n=== /status ===');
|
||||
console.log(d);
|
||||
});
|
||||
});
|
||||
statusReq.on('error', e => console.log('Status error:', e.message));
|
||||
})();
|
||||
27
test_hub.py
Normal file
27
test_hub.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import asyncio
|
||||
from bot import GravityBot
|
||||
import discord
|
||||
|
||||
class FakeChannel:
|
||||
async def send(self, embed, view=None):
|
||||
print("Sent to channel successfully!")
|
||||
class FakeMsg:
|
||||
id = 999
|
||||
return FakeMsg()
|
||||
|
||||
async def test():
|
||||
bot = GravityBot(asyncio.Queue())
|
||||
bot.project_channels["gravity_control"] = FakeChannel()
|
||||
class FakeHub:
|
||||
def get_active_count(self, proj): return 1
|
||||
bot.hub = FakeHub()
|
||||
await bot._hub_on_pending("gravity_control", {
|
||||
"request_id": "test1",
|
||||
"command": "Running1 command",
|
||||
"description": "test",
|
||||
"step_type": "",
|
||||
"buttons": [{"text": "Proceed", "index": 0}],
|
||||
"timestamp": 12345678.0
|
||||
})
|
||||
|
||||
asyncio.run(test())
|
||||
47
test_hub_remote.py
Normal file
47
test_hub_remote.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
|
||||
async def main():
|
||||
uri = "wss://ag.variet.net/ws"
|
||||
try:
|
||||
async with websockets.connect(uri) as ws:
|
||||
print("Connected to remote hub.")
|
||||
# Send AUTH first
|
||||
auth_msg = {
|
||||
"type": "auth",
|
||||
"token": "2352253f42bd0f9190a83c26f05cc252e86c55c044206953fa7b8fd97adaa6d3",
|
||||
"project": "gravity_control",
|
||||
"instance_number": 1,
|
||||
"pc_name": "TEST_PC"
|
||||
}
|
||||
await ws.send(json.dumps(auth_msg))
|
||||
resp = await ws.recv()
|
||||
print("Auth response:", resp)
|
||||
|
||||
# Now send pending
|
||||
msg = {
|
||||
"type": "pending",
|
||||
"data": {
|
||||
"request_id": "999999999_mock_1",
|
||||
"command": "MOCK COMMAND FROM TEST",
|
||||
"description": "If you see this, the hub is routing perfectly.",
|
||||
"step_type": "",
|
||||
"status": "pending",
|
||||
"buttons": [{"text": "Proceed", "index": 0}],
|
||||
"project_name": "gravity_control",
|
||||
"conversation_id": "test_conv"
|
||||
}
|
||||
}
|
||||
await ws.send(json.dumps(msg))
|
||||
print("Message sent.")
|
||||
|
||||
# Keep alive and print responses
|
||||
while True:
|
||||
resp = await asyncio.wait_for(ws.recv(), timeout=5.0)
|
||||
print("Received:", resp)
|
||||
|
||||
except Exception as e:
|
||||
print("Done:", e)
|
||||
|
||||
asyncio.run(main())
|
||||
12
test_view.py
Normal file
12
test_view.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import discord
|
||||
from bot import ApprovalView
|
||||
from models import ApprovalRequest
|
||||
|
||||
request = ApprovalRequest("id", "convo", "cmd", "desc", 0.0)
|
||||
view = ApprovalView(request, buttons=[{"text":"Proceed","index":0}], hub=None)
|
||||
print("View items:", view.children)
|
||||
try:
|
||||
print(view.to_components())
|
||||
print("SUCCESS")
|
||||
except Exception as e:
|
||||
print("CRASH:", e)
|
||||
12
test_view2.py
Normal file
12
test_view2.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import discord
|
||||
from bot import ApprovalView
|
||||
from models import ApprovalRequest
|
||||
|
||||
request = ApprovalRequest("id", "convo", "cmd", "desc", 0.0)
|
||||
view = ApprovalView(request, buttons=[{"text":"yes","index":0}, {"text":"no","index":1}], hub=None)
|
||||
print("View items:", view.children)
|
||||
try:
|
||||
print(view.to_components())
|
||||
print("SUCCESS")
|
||||
except Exception as e:
|
||||
print("CRASH:", e)
|
||||
Reference in New Issue
Block a user