docs: AG Native bundle reverse engineering analysis — plannerResponse/Whi renderer structure, V8 cache fix, known-issues update

This commit is contained in:
Variet Worker
2026-04-12 07:05:57 +09:00
parent eef59e6bb2
commit 353265bed8
25 changed files with 1236 additions and 33 deletions

View File

@@ -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 재시작만으로는 부족

View File

@@ -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용임 발견 | — | 🔧 |

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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}")

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)