From 353265bed8016836857271751d84830c152a3942 Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Sun, 12 Apr 2026 07:05:57 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20AG=20Native=20bundle=20reverse=20engine?= =?UTF-8?q?ering=20analysis=20=E2=80=94=20plannerResponse/Whi=20renderer?= =?UTF-8?q?=20structure,=20V8=20cache=20fix,=20known-issues=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/references/known-issues.md | 12 +++ docs/devlog/2026-04-12.md | 3 +- docs/devlog/entries/20260412-002.md | 22 +++++ scratch_bundle_deep.py | 63 ++++++++++++++ scratch_bundle_deep2.js | 65 +++++++++++++++ scratch_bundle_deep3.js | 75 +++++++++++++++++ scratch_bundle_deep4.js | 72 ++++++++++++++++ scratch_bundle_deep5.js | 79 ++++++++++++++++++ scratch_bundle_scan.js | 77 +++++++++++++++++ scratch_bundle_search.py | 64 ++++++++++++++ scratch_devlog.py | 41 ++------- scratch_dom_classes.py | 94 +++++++++++++++++++++ scratch_dom_inspector.js | 125 ++++++++++++++++++++++++++++ scratch_dom_probe.py | 90 ++++++++++++++++++++ scratch_jsx_struct.py | 53 ++++++++++++ scratch_ki_update.py | 40 +++++++++ scratch_reload_ag.js | 26 ++++++ scratch_rpc_dom.json | 6 ++ scratch_rpc_exec.js | 41 +++++++++ scratch_rpc_test.py | 71 ++++++++++++++++ scratch_rpc_test2.js | 52 ++++++++++++ test_hub.py | 27 ++++++ test_hub_remote.py | 47 +++++++++++ test_view.py | 12 +++ test_view2.py | 12 +++ 25 files changed, 1236 insertions(+), 33 deletions(-) create mode 100644 docs/devlog/entries/20260412-002.md create mode 100644 scratch_bundle_deep.py create mode 100644 scratch_bundle_deep2.js create mode 100644 scratch_bundle_deep3.js create mode 100644 scratch_bundle_deep4.js create mode 100644 scratch_bundle_deep5.js create mode 100644 scratch_bundle_scan.js create mode 100644 scratch_bundle_search.py create mode 100644 scratch_dom_classes.py create mode 100644 scratch_dom_inspector.js create mode 100644 scratch_dom_probe.py create mode 100644 scratch_jsx_struct.py create mode 100644 scratch_ki_update.py create mode 100644 scratch_reload_ag.js create mode 100644 scratch_rpc_dom.json create mode 100644 scratch_rpc_exec.js create mode 100644 scratch_rpc_test.py create mode 100644 scratch_rpc_test2.js create mode 100644 test_hub.py create mode 100644 test_hub_remote.py create mode 100644 test_view.py create mode 100644 test_view2.py diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index a0fc716..afafd03 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -10,6 +10,18 @@ --- +### [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 재시작만으로는 부족 + +### [2026-04-12] [DOM] text-ide-message-block-bot-color는 AI 응답 컨테이너가 아닌 NUX tooltip 전용 +- **증상**: observer-script가 `.text-ide-message-block-bot-color`를 AI 응답 컨테이너로 사용하지만, 실제 AI 텍스트를 추출하지 못함 +- **원인**: 번들 분석(jetskiAgent/main.js 10.8MB)으로 확인 결과, 이 클래스는 `hsn` 컴포넌트(NUX Tooltip 텍스트 색상)에서만 사용. AI 응답 텍스트는 `plannerResponse` step의 `Whi` 렌더러 → `div.px-2.py-1` → `MarkdownRenderer` 내부에 렌더링됨 +- **해결**: observer-script에서 `.text-ide-message-block-bot-color` 의존성 제거 필요. `markdown-body` 클래스도 AG Native에 존재하지 않음 +- **주의**: AI 응답 마크다운은 `prose` 관련 클래스나 MarkdownRenderer 내부 구조로 타겟팅해야 함. 실제 DOM 덤프로 정확한 셀렉터 확인 필요 + ### [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에 전혀 등록되지 않는 별도 시스템 diff --git a/docs/devlog/2026-04-12.md b/docs/devlog/2026-04-12.md index 00540f1..3dad129 100644 --- a/docs/devlog/2026-04-12.md +++ b/docs/devlog/2026-04-12.md @@ -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용임 발견 | — | 🔧 | diff --git a/docs/devlog/entries/20260412-002.md b/docs/devlog/entries/20260412-002.md new file mode 100644 index 0000000..f6ac503 --- /dev/null +++ b/docs/devlog/entries/20260412-002.md @@ -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 검증 diff --git a/scratch_bundle_deep.py b/scratch_bundle_deep.py new file mode 100644 index 0000000..2da390b --- /dev/null +++ b/scratch_bundle_deep.py @@ -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)}") diff --git a/scratch_bundle_deep2.js b/scratch_bundle_deep2.js new file mode 100644 index 0000000..e59e22a --- /dev/null +++ b/scratch_bundle_deep2.js @@ -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)); + } +} diff --git a/scratch_bundle_deep3.js b/scratch_bundle_deep3.js new file mode 100644 index 0000000..3def719 --- /dev/null +++ b/scratch_bundle_deep3.js @@ -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)); + } +} diff --git a/scratch_bundle_deep4.js b/scratch_bundle_deep4.js new file mode 100644 index 0000000..1290f67 --- /dev/null +++ b/scratch_bundle_deep4.js @@ -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)); +} diff --git a/scratch_bundle_deep5.js b/scratch_bundle_deep5.js new file mode 100644 index 0000000..90eff91 --- /dev/null +++ b/scratch_bundle_deep5.js @@ -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 (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++; +} diff --git a/scratch_bundle_scan.js b/scratch_bundle_scan.js new file mode 100644 index 0000000..7b910c6 --- /dev/null +++ b/scratch_bundle_scan.js @@ -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); +} diff --git a/scratch_bundle_search.py b/scratch_bundle_search.py new file mode 100644 index 0000000..2b668df --- /dev/null +++ b/scratch_bundle_search.py @@ -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}") diff --git a/scratch_devlog.py b/scratch_devlog.py index b5d23eb..d2e99e1 100644 --- a/scratch_devlog.py +++ b/scratch_devlog.py @@ -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") diff --git a/scratch_dom_classes.py b/scratch_dom_classes.py new file mode 100644 index 0000000..4b7c909 --- /dev/null +++ b/scratch_dom_classes.py @@ -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}") diff --git a/scratch_dom_inspector.js b/scratch_dom_inspector.js new file mode 100644 index 0000000..2a6f546 --- /dev/null +++ b/scratch_dom_inspector.js @@ -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('────────────────────────────────────────'); diff --git a/scratch_dom_probe.py b/scratch_dom_probe.py new file mode 100644 index 0000000..3ab4bcf --- /dev/null +++ b/scratch_dom_probe.py @@ -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}") diff --git a/scratch_jsx_struct.py b/scratch_jsx_struct.py new file mode 100644 index 0000000..2927be6 --- /dev/null +++ b/scratch_jsx_struct.py @@ -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() diff --git a/scratch_ki_update.py b/scratch_ki_update.py new file mode 100644 index 0000000..f655f19 --- /dev/null +++ b/scratch_ki_update.py @@ -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") diff --git a/scratch_reload_ag.js b/scratch_reload_ag.js new file mode 100644 index 0000000..7121221 --- /dev/null +++ b/scratch_reload_ag.js @@ -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(); diff --git a/scratch_rpc_dom.json b/scratch_rpc_dom.json new file mode 100644 index 0000000..acc541b --- /dev/null +++ b/scratch_rpc_dom.json @@ -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})" + } +} diff --git a/scratch_rpc_exec.js b/scratch_rpc_exec.js new file mode 100644 index 0000000..d64d14c --- /dev/null +++ b/scratch_rpc_exec.js @@ -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(); diff --git a/scratch_rpc_test.py b/scratch_rpc_test.py new file mode 100644 index 0000000..b9c0f3d --- /dev/null +++ b/scratch_rpc_test.py @@ -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]}") diff --git a/scratch_rpc_test2.js b/scratch_rpc_test2.js new file mode 100644 index 0000000..0c0e767 --- /dev/null +++ b/scratch_rpc_test2.js @@ -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)); +})(); diff --git a/test_hub.py b/test_hub.py new file mode 100644 index 0000000..7942025 --- /dev/null +++ b/test_hub.py @@ -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()) diff --git a/test_hub_remote.py b/test_hub_remote.py new file mode 100644 index 0000000..44d2352 --- /dev/null +++ b/test_hub_remote.py @@ -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()) diff --git a/test_view.py b/test_view.py new file mode 100644 index 0000000..ec7ce91 --- /dev/null +++ b/test_view.py @@ -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) diff --git a/test_view2.py b/test_view2.py new file mode 100644 index 0000000..ac680d7 --- /dev/null +++ b/test_view2.py @@ -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)