From a99c2836563c9a0743c1484f480547e18b2a814a Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Fri, 10 Apr 2026 21:10:33 +0900 Subject: [PATCH] fix(extension): restore AI Response Content capture by patching DOM extraction, CSP connect-src, and TS regex literal serialization --- .agents/references/known-issues.md | 13 + diag_output.txt | 0 docs/devlog/2026-04-10.md | 1 + docs/devlog/entries/20260410-613.md | 15 + extension/package-lock.json | 4 +- extension/package.json | 6 +- extension/src/html-patcher.ts | 16 + extension/src/observer-script.ts | 803 ++++++++-------------------- extension/src/step-probe.ts | 39 ++ extension/src/step-utils.ts | 30 +- generate_mock.js | 36 ++ mock_output.html | 1 + scratch_btn.py | 8 + scratch_btn2.py | 6 + scratch_diag_7.py | 16 + scratch_diag_8.js | 20 + scratch_diag_9.js | 5 + scratch_test.js | 4 + scratch_trigger.ps1 | 5 + test_dom.js | 66 +++ test_dom_mock.js | 144 +++++ test_logic.js | 98 ++++ 22 files changed, 744 insertions(+), 592 deletions(-) create mode 100644 diag_output.txt create mode 100644 docs/devlog/entries/20260410-613.md create mode 100644 generate_mock.js create mode 100644 mock_output.html create mode 100644 scratch_btn.py create mode 100644 scratch_btn2.py create mode 100644 scratch_diag_7.py create mode 100644 scratch_diag_8.js create mode 100644 scratch_diag_9.js create mode 100644 scratch_test.js create mode 100644 scratch_trigger.ps1 create mode 100644 test_dom.js create mode 100644 test_dom_mock.js create mode 100644 test_logic.js diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index fa78fb9..beceb02 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -235,3 +235,16 @@ - **원인**: 실시간 텍스트 캡처(`delta > 0`) 조건에 `isRunning &&`이 걸려있어, 상태가 `WAITING`이나 `IDLE`로 즉시 넘어가면 텍스트를 캡처하는 루틴이 전부 스킵됨. 또한 이 순간 `isStall` 조건도 타지 않아 `WAITING` 디텍션도 증발함. - **해결**: 실시간 캡처 로직에서 `isRunning &&` 조건을 제거하고, `delta > 0`일 때 추가된 최신 스텝을 스캔하면서 `PLANNER_RESPONSE`와 `WAITING` 스텝을 모두 처리하도록 수정함. - **주의**: LS Backend 10개 Session 제한 버그가 있어, 다른 창에서 수동 채팅(`1fbca84c`)이 IDLE로 남아있으면 자동화 에이전트의 워크스페이스 세션과 헷갈릴 수 있으나, 이 버그는 polling 타이밍 문제였음. + +### [2026-04-10] [Extension] AI Response Missing for New Sessions (Session Tracking Failure) +- **증상**: 새로운 대화(Session) 시작 시 첫 AI 응답 텍스트가 디스코드에 전혀 전송되지 않는 현상. +- **원인**: 백엔드의 `GetAllCascadeTrajectories`가 10개 세션만 반환하여 새 세션이 누락됨. 이를 보완하기 위해 `brain/` 디렉토리를 스캔하는 Fallback 로직이 동작했으나, 신규 세션의 첫 단계에서 `GetCascadeTrajectorySteps`(stepOffset: 0) 호출 시 내부 응답(UTF-8 파싱 등) 에러로 인해 Exception이 발생, `trajectorySummaries`에 세션이 아예 등록되지 않음. 세션이 추적되지 않으니 `delta > 0` 기반의 응답 캡처가 발생하지 않음. +- **해결**: `step-probe.ts`의 Fallback 2 `catch` 블록에서 에러가 발생하더라도 강제로 `stepCount: 1`로 세션을 등록하도록 패치하여 세션 인식 유실 방지. +- **주의**: API 호출 실패를 조용히 `catch`로 넘기면 전체 파이프라인(여기서는 상태 폴링)이 해당 데이터를 영원히 무시하게 되는 치명적 버그가 발생함. 장애 허용 설계 시 기본값 복원(Fallback State) 설정 필수. + + +### [2026-04-10] [Extension] Trigger-Click False Positives & Button Matching Failure +- **증상**: 디스코드에서 승인(Approve)을 누르면, 에이전트 확장 프로그램이 알맞은 버튼(예: `Always run`)을 누르지 못하거나, 엉뚱한 버튼(예: 상단의 `Running1 command`)을 눌러버려 실제 승인 처리가 누락되는 현상. +- **원인**: 1) UI 버튼 텍스트에 `keyboard_arrow_up` 등 머티리얼 아이콘 텍스트가 접착(`Always runkeyboard_arrow_up`)되어 정규식이 실패할 것을 우려해 단어 경계(`\b`)를 제거한 패치가 원인. 단어 경계가 사라지면서 `/Run/i` 패턴이 `Running1 command` 같은 다른 상태 텍스트 버튼에 오탐(False Positive)됨. 2) DOM 순서상 상태 텍스트 버튼이 앞서 있으므로 오탐된 버튼이 우선 클릭됨. +- **해결**: `trigger-click` 로직 실행 전 버튼의 `textContent`에서 `keyboard_arrow_up` 등 알려진 꼬리 아이콘 문자열을 명시적으로 제거(strip)하고, 모든 트리거 정규식에 다시 단어 경계(`\b`)를 강제 삽입하여 오탐을 원천 차단함. +- **주의**: UI 요소를 DOM에서 긁어올 때는 텍스트에 숨겨진 아이콘/웹폰트 리거쳐(ligatures)가 없는지 검토해야 함. 패턴 매칭 시 꼬리표를 먼저 제거하고 명확한 경계를 부여할 것. diff --git a/diag_output.txt b/diag_output.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/devlog/2026-04-10.md b/docs/devlog/2026-04-10.md index f26c353..8348f27 100644 --- a/docs/devlog/2026-04-10.md +++ b/docs/devlog/2026-04-10.md @@ -1,3 +1,4 @@ | NNN | 시간 | 작업 설명 | 커밋해시 | 완료여부 | |---|---|---|---|---| | 001 | 17:11 | step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스 | COMMITTING | ✅ | +| #613 | 21:10 | Bridge Relay AI Chat Body DOM extraction and template literal Regex fix | TBD | ? | diff --git a/docs/devlog/entries/20260410-613.md b/docs/devlog/entries/20260410-613.md new file mode 100644 index 0000000..66afdf2 --- /dev/null +++ b/docs/devlog/entries/20260410-613.md @@ -0,0 +1,15 @@ +# Fix gravity bridge Discord Relay AI Chat Body by patching DOM extraction and Regex literals + +- **시간**: 2026-04-10 20:30~21:10 +- **Vikunja**: #613 → done + +## 트러블슈팅: Typescript 백틱 안의 정규식 리터럴 파괴 현상 +- **증상**: JSDOM 가상 모의 환경에서 테스트를 돌려보니, 렌더링 화면이나 타겟 Text가 정확히 매치됨에도 정규식이 조건문에서 `false`를 내뱉으며 Button Matching을 건너뛰는 현상 발생. +- **원인**: `observer-script.ts`를 `.js`로 변환할 때, Typescript 컴파일러가 `return \`...\`` 템플릿 리터럴 내부의 `/^(?:Always\s*)?Allow\b/i` 구문을 해석하면서, `\s`를 일반 문자 `s`로, `\b`를 아스키 특수문자 `Backspace(0x08)`로 직렬화하여 클라이언트에 꽂아버리는 문제가 있었음. 이로 인해 정규식 자체가 오염되어 어떠한 버튼도 매칭하지 못하고 있었음. +- **해결**: `observer-script` 내부의 정규식 리터럴 내부의 이스케이프 문자(`\s`, `\b` 등)를 전부 이중 백슬래시(`\\s`, `\\b`)로 패치하여 브라우저에서 스크립트가 실행될 때 올바른 정규식 파서가 열리도록 수정 보완함. + +## 결정 사항: 웹뷰 내 로컬 fetch CSP 패치 통과 +- `html-patcher.ts`에서 웹뷰 렌더링 시점에 CSP를 조작하여 `default-src 'none'` 방어막을 뚫고 `connect-src`에 `http://127.0.0.1:* wss://127.0.0.1:*`를 주입하도록 강제 적용함. 이를 통해 Bridge 서버로의 로컬 HTTP 통신이 활성화됨. + +## 완료 상태 +VSCode VSIX (0.5.27) 빌드 완료 및 릴리스 커밋 패키징 수행. diff --git a/extension/package-lock.json b/extension/package-lock.json index 8a888f2..2969844 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "gravity-bridge", - "version": "0.5.4", + "version": "0.5.25", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gravity-bridge", - "version": "0.5.4", + "version": "0.5.25", "dependencies": { "ws": "^8.19.0" }, diff --git a/extension/package.json b/extension/package.json index 65083f4..de14ba8 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,8 +1,8 @@ { "name": "gravity-bridge", "displayName": "Gravity Bridge", - "description": "Antigravity ↔ Discord 브리지 연동 확장", - "version": "0.5.23", + "description": "Discord-based unified approval system for Antigravity AI interactions.", + "version": "0.5.27", "publisher": "variet", "engines": { "vscode": "^1.100.0" @@ -86,4 +86,4 @@ "dependencies": { "ws": "^8.19.0" } -} \ No newline at end of file +} diff --git a/extension/src/html-patcher.ts b/extension/src/html-patcher.ts index c7fdd63..9b84c2c 100644 --- a/extension/src/html-patcher.ts +++ b/extension/src/html-patcher.ts @@ -246,6 +246,22 @@ function _patchHtmlFiles(scriptDir: string, combinedScript: string, logToFile: ( logToFile(`[OBSERVER] ${spec.name} CSP patched: added 'unsafe-inline' to script-src`); } + // CRITICAL: Patch CSP connect-src to allow HTTP bridge requests from the webview + // In Tailwind UI, connect-src is either missing (defaults to 'none') or strict. + if (!html.includes('connect-src') && html.includes('default-src')) { + html = html.replace( + /(default-src\s+'none'\s*;)/, + "$1\n\t\t\t\tconnect-src\n\t\t\t\t\t'self'\n\t\t\t\t\thttp://127.0.0.1:*\n\t\t\t\t\thttps://127.0.0.1:*\n\t\t\t\t\twss://127.0.0.1:*\n\t\t\t\t;" + ); + logToFile(`[OBSERVER] ${spec.name} CSP patched: injected connect-src for localhost API`); + } else if (html.includes('connect-src') && !html.match(/connect-src[^;]*127\.0\.0\.1/)) { + html = html.replace( + /(connect-src\s[^;]*?)('self'|vscode-remote-resource:|[a-z-]+:)/i, + "$1$2\n\t\t\t\t\thttp://127.0.0.1:*\n\t\t\t\t\thttps://127.0.0.1:*" + ); + logToFile(`[OBSERVER] ${spec.name} CSP patched: added localhost to existing connect-src`); + } + // Remove old external script tag if present (legacy, cannot be served) const extMarkerStart = ''; const extMarkerEnd = ''; diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index 432ce79..96df5e6 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -1,377 +1,159 @@ -/** - * Approval Observer Script — injected into AG's renderer process. - * - * This is a self-contained JavaScript string template that runs in the - * browser context (no Node.js APIs). It scans the DOM for approval buttons, - * reports them to the HTTP bridge, and handles trigger clicks. - * - * Extracted from extension.ts for maintainability. - */ - export function generateApprovalObserverScript(_port: number): string { - // Port is hardcoded as fallback, but renderer also reads ag-bridge-ports.json for multi-bridge return ` -// ── Gravity Bridge v3: Approval Observer (deep DOM traversal — iframes, webviews, shadow DOMs) ── +// ── Gravity Bridge v4: React Tailwind UI Observer ── (function(){ 'use strict'; var BASE='',_obs=false,_sent={},_ready=false; var _scanScheduled=false,_lastScanTs=0; - var THROTTLE_MS=100; + var THROTTLE_MS=500; var CLEANUP_MS=300000; - var _domDumped=false; - + function log(m){console.log('[GB Observer] '+m);} - log('v3 Script loaded — deep DOM traversal enabled'); + log('v4 Script loaded — deep Tailwind DOM traversal enabled'); - // ── Deep DOM Traversal: find buttons across ALL boundaries ── - // Searches: main document → iframes (contentDocument) → webview elements → shadow DOMs - function deepFindButtons(patterns){ - var results=[]; - // 1. Prioritize Agent panel - var panel=findPanel(); - if(panel){ - collectButtons(panel,results,patterns,'panel'); - if(results.length>0) return results; - } - // 2. Prioritize VS Code Toasts & Dialogs - var toasts=document.querySelectorAll('.notifications-toasts, .monaco-dialog-box'); - for(var t=0;t0) return results; - // 3. Main document fallback - collectButtons(document,results,patterns,'main'); - // 4. Iframe traversal (try contentDocument — works if same-origin or webSecurity off) - var iframes=document.querySelectorAll('iframe'); - for(var i=0;i tag — has executeJavaScript) - var webviews=document.querySelectorAll('webview'); - for(var w=0;wshadow'); - } - }catch(e){} - } - - // ── Deep DOM Inspector (recursive, POSTs results to bridge) ── - function runDeepInspect(){ - var result={timestamp:new Date().toISOString(),windowURL:window.location.href,windowOrigin:window.location.origin,windowProtocol:window.location.protocol,framesCount:window.frames.length,nodes:[]}; - log('DEEP-INSPECT: starting recursive DOM analysis...'); - - function inspectDoc(doc,depth,label){ - var node={label:label,depth:depth,accessible:true,url:'',buttons:[],roleBtns:[],iframes:[],webviews:[],shadowDOMs:0,totalElements:0}; - if(!doc){node.accessible=false;node.error='null document';result.nodes.push(node);return;} - try{node.url=(doc.URL||doc.documentURI||'unknown').substring(0,200);}catch(e){node.url='blocked';} - try{node.title=(doc.title||'').substring(0,100);}catch(e){} - try{node.readyState=doc.readyState;}catch(e){} - - // CSP - try{ - var csp=doc.querySelectorAll('meta[http-equiv="Content-Security-Policy"]'); - if(csp.length>0){node.csp=[];for(var c=0;c