Compare commits
57 Commits
a8d875167d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32cf69469c | ||
|
|
7c8891b99c | ||
|
|
3cc3442fda | ||
|
|
e95e7791f9 | ||
|
|
2bf1eb41d1 | ||
|
|
cf1352eefa | ||
|
|
6aea48e2e9 | ||
|
|
bd5a7ca8b9 | ||
|
|
8ada5f7daf | ||
|
|
4f2be831a1 | ||
|
|
cbfd137dcb | ||
|
|
a99a1e3f54 | ||
|
|
ad4ed623bd | ||
|
|
64800d3c20 | ||
|
|
70c83b4226 | ||
|
|
bb54802c06 | ||
|
|
bf53072f3c | ||
|
|
02b4b03699 | ||
|
|
db805c6fde | ||
|
|
7f33a20e43 | ||
|
|
ef788e6ecc | ||
|
|
cd00986274 | ||
|
|
12095f36a4 | ||
|
|
498683c977 | ||
|
|
1662ac4f6b | ||
|
|
d027562f17 | ||
|
|
cc261011d6 | ||
|
|
37fbb9657e | ||
|
|
965f619664 | ||
|
|
139ad3ee93 | ||
|
|
08fd08b9a6 | ||
|
|
326454be40 | ||
|
|
98b7585e3c | ||
|
|
c7f939ce85 | ||
|
|
7a1675fd5d | ||
|
|
6b9f1188c3 | ||
|
|
13e569f426 | ||
|
|
b2f17a086b | ||
|
|
7dbf73aa89 | ||
|
|
62ee081ffe | ||
|
|
60a2a97868 | ||
|
|
7ae43088e6 | ||
|
|
729875f3a6 | ||
|
|
a00d561e28 | ||
|
|
7ade31e4cf | ||
|
|
66233bd9cb | ||
|
|
ed90cbf874 | ||
|
|
2e32be96fe | ||
|
|
87c99c7243 | ||
|
|
01539e9bfb | ||
|
|
4684376c78 | ||
|
|
7ee5947b32 | ||
|
|
59ddcbb612 | ||
|
|
ed63f65975 | ||
|
|
02d9799f20 | ||
|
|
a9c64e6716 | ||
|
|
8e89209a27 |
@@ -1,9 +1,15 @@
|
|||||||
# Known Issues & Lessons Learned
|
# Known Issues & Lessons Learned
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> **<2A>씠 <20>뙆<EFBFBD>씪<EFBFBD><EC94AA><EFBFBD> SSOT(Single Source of Truth)<29>엯<EFBFBD>땲<EFBFBD>떎.**
|
> **<2A>씠 <20>뙆<EFBFBD>씪<EFBFBD><EC94AA><EFBFBD> SSOT(Single Source of Truth)<29>엯<EFBFBD>땲<EFBFBD>떎.**
|
||||||
|
|
||||||
> <EFBFBD>뵒踰꾧퉭<EFBFBD>씠<EFBFBD>굹 援ы쁽 <20>쟾<EFBFBD>뿉 **諛섎뱶<EC848E>떆** <20>씠 <20>뙆<EFBFBD>씪<EFBFBD>쓣 <20>솗<EFBFBD>씤<EFBFBD>븯<EFBFBD>꽭<EFBFBD>슂.
|
> <EFBFBD>뵒踰꾧퉭<EFBFBD>씠<EFBFBD>굹 援ы쁽 <20>쟾<EFBFBD>뿉 **諛섎뱶<EC848E>떆** <20>씠 <20>뙆<EFBFBD>씪<EFBFBD>쓣 <20>솗<EFBFBD>씤<EFBFBD>븯<EFBFBD>꽭<EFBFBD>슂.
|
||||||
|
|
||||||
> <EFBFBD>꽭<EFBFBD>뀡 醫낅즺 <20>떆 <20>깉濡<EAB989> 諛쒓껄<EC9293>맂 <20>씠<EFBFBD>뒋瑜<EB928B> <20>씠 <20>뙆<EFBFBD>씪<EFBFBD>뿉 異붽<E795B0><EBB6BD><EFBFBD>빀<EFBFBD>땲<EFBFBD>떎.
|
> <EFBFBD>꽭<EFBFBD>뀡 醫낅즺 <20>떆 <20>깉濡<EAB989> 諛쒓껄<EC9293>맂 <20>씠<EFBFBD>뒋瑜<EB928B> <20>씠 <20>뙆<EFBFBD>씪<EFBFBD>뿉 異붽<E795B0><EBB6BD><EFBFBD>빀<EFBFBD>땲<EFBFBD>떎.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Known Issues & Lessons Learned
|
# Known Issues & Lessons Learned
|
||||||
|
|
||||||
> **씠 뙆씪 SSOT(Single Source of Truth)엯땲떎.**
|
> **씠 뙆씪 SSOT(Single Source of Truth)엯땲떎.**
|
||||||
@@ -14,6 +20,111 @@
|
|||||||
> 빐寃 셿猷뚮맂 怨쇨굅 씠뒋뒗 [`known-issues-archive.md`](file:///c:/Users/Variet-Worker/Desktop/gravity_control/.agents/references/known-issues-archive.md)뿉 蹂닿릺뼱 엳뒿땲떎.
|
> 빐寃 셿猷뚮맂 怨쇨굅 씠뒋뒗 [`known-issues-archive.md`](file:///c:/Users/Variet-Worker/Desktop/gravity_control/.agents/references/known-issues-archive.md)뿉 蹂닿릺뼱 엳뒿땲떎.
|
||||||
> 鍮꾩듂븳 臾몄젣媛 옱諛쒗븯硫 archive뿉꽌 寃깋븯꽭슂.
|
> 鍮꾩듂븳 臾몄젣媛 옱諛쒗븯硫 archive뿉꽌 寃깋븯꽭슂.
|
||||||
|
|
||||||
|
|
||||||
|
### [2026-04-19] [Observer] ★ Accept all 버튼이 `<span>`으로 렌더링 — Observer 감지 실패 (v0.5.101)
|
||||||
|
- **증상**: "Accept all" diff review 버튼이 화면에 보이지만 Observer가 감지하지 못함. Discord에 "Accept all" 자동 승인 알림이 안 옴.
|
||||||
|
- **원인**: AG Native UI 업데이트로 "Accept all"이 `<button>`이 아닌 `<span class="cursor-pointer">`로 렌더링됨. Observer의 `allBtns = querySelectorAll('button, [role="button"]')`에 span이 포함되지 않음. ACCEPT-SCAN 로그: `tag=SPAN cls=hover:text-ide-button-hover-color cursor-po txt=Accept all`.
|
||||||
|
- **해결 (v0.5.101)**: `allBtns` 선택자에 `span.cursor-pointer` 추가.
|
||||||
|
- **주의**: observer-dev-guide 섹션 3.3 "Accept all — Observer 접근 불가"는 outdated. UI 변경으로 chat panel footer에 Accept all이 표시됨. 가이드 업데이트 필요.
|
||||||
|
|
||||||
|
### [2026-04-19] [Bridge] ★ auto-approve response 파일에 `_from_ws` 마커 누락 — Observer polling 실패 (v0.5.103)
|
||||||
|
- **증상**: Observer가 "Accept all"을 감지하고 bridge가 자동 승인했지만, Observer의 `pollResponseGroup` GET `/response/{rid}`가 항상 `{waiting: true}` 반환. 버튼 클릭이 실행되지 않음.
|
||||||
|
- **원인**: http-bridge의 auto-approve 경로에서 response JSON 파일에 `_from_ws: true` 마커가 없음 → `processResponseFile`(response watcher)이 Observer보다 먼저 파일을 읽고 삭제 → Observer polling 시 파일 부재. known-issues [2026-04-18] WS response 삭제 버그와 동일 패턴.
|
||||||
|
- **해결 (v0.5.103)**: auto-approve response에 `_from_ws: true` + `_auto_approve_ttl` 마커 추가.
|
||||||
|
- **주의**: **response 디렉토리에 파일을 쓰는 모든 경로**는 반드시 `_from_ws: true` 마커를 포함해야 함. processResponseFile이 먼저 소비하는 race condition 항상 존재.
|
||||||
|
|
||||||
|
### [2026-04-18] [Extension] ★ WS response 파일이 processResponseFile에 의해 삭제 → Observer pollResponseGroup 실패 (v0.5.78)
|
||||||
|
- **증상**: `!auto` Retry가 작동하지 않음. Observer가 `/response/{rid}`를 폴링하지만 항상 `{waiting: true}` 반환.
|
||||||
|
- **원인**: extension.ts의 WS 응답 핸들러가 `response/{rid}.json` 파일 작성 → 300ms 후 response watcher(`processResponseFile`)가 파일 감지 → pending 파일이 없어 `isDomObserver=false` → `fs.unlinkSync()` 실행 → Observer가 폴링할 때 파일이 이미 삭제됨.
|
||||||
|
- **해결 (v0.5.78)**: WS 응답 파일에 `_from_ws: true` 마커 추가. `processResponseFile`에서 `_from_ws` 파일 스킵 (WS 핸들러에서 이미 `tryApprovalStrategies` 실행하므로 중복 방지도 함께 해결).
|
||||||
|
- **주의**: http-bridge의 `_handlePending`는 pending 파일을 생성하지 않음 (WS 전송만 수행). 따라서 `processResponseFile`의 `isDomObserver` 판별이 실패함. WS 경로로 들어오는 모든 response는 반드시 마커로 구분해야 함.
|
||||||
|
|
||||||
|
### [2026-04-18] [Extension] ★ extractContextFromNearby 조상 탐색만으로는 명령어 추출 불가 (v0.5.79)
|
||||||
|
- **증상**: Discord auto-approve 알림에 "Always run"만 표시되고 실제 명령어가 안 보임. depth를 10으로 늘려도 동일.
|
||||||
|
- **원인**: AG Native DOM 구조에서 "Always run" 버튼은 `footer` 내부에 있고, 실제 명령어(`pre.font-mono`)는 `footer`의 **형제(sibling)** 요소에 있음. 조상 탐색(parentElement)으로는 도달 불가. trail: `d0:button → d1:div → d2:footer` (footer.parentElement가 null).
|
||||||
|
- **해결 (v0.5.79)**: `extractContextFromNearby`에 형제 탐색 로직 추가. 각 depth에서 `node.parentElement.children`을 순회하며 `pre.font-mono, pre, code`를 찾음 → `CONTEXT-OK src=sibling` 성공.
|
||||||
|
- **주의**: Observer 코드 변경은 HTML 인라인 스크립트이므로 AG 2번 재시작(Quit + Relaunch × 2) 필요.
|
||||||
|
|
||||||
|
### [2026-04-18] [Extension] Thinking 블록이 AI 응답으로 릴레이됨
|
||||||
|
- **증상**: AI의 내부 사고 과정(thinking/reasoning)이 Discord에 릴레이됨.
|
||||||
|
- **원인**: Observer의 `scanChatBodies`가 `.leading-relaxed.select-text` 블록을 전부 캡처하는데, thinking 블록도 이 셀렉터에 매칭됨.
|
||||||
|
- **해결**: thinking 블록의 조상에 `max-h-[200px]` 클래스가 있는지 확인하여 필터링.
|
||||||
|
- **주의**: AG UI 업데이트로 thinking 블록의 클래스가 변경될 수 있음.
|
||||||
|
|
||||||
|
|
||||||
|
- **증상**: Step Probe의 RT-CAPTURE, HB-CAPTURE가 현재 대화 중에 전혀 발동하지 않음. POLL에서 `status=IDLE, steps=928, delta=0` 고정. Heartbeat probe에서도 `real=928 known=928` 불변.
|
||||||
|
- **원인**: AG Native의 `GetCascadeTrajectorySteps` API는 **cascade가 완전히 종료(IDLE 전환)된 후에만** 새 step을 반환합니다. 진행 중인 cascade에서는 step count가 동결됩니다. `GetAllCascadeTrajectories`의 `stepCount`도 마찬가지.
|
||||||
|
- **영향**: Step Probe 기반의 모든 실시간 캡처(RT-CAPTURE, HB-CAPTURE, USER_INPUT 감지)가 **구조적으로 불가능**. Observer DOM이 유일한 실시간 데이터 경로.
|
||||||
|
- **해결**: Observer DOM 기반 chat relay를 재활성화 (v0.5.72). Step Probe는 cascade 완료 후 보완 용도로만 사용.
|
||||||
|
- **주의**: 이 제약은 AG Native 아키텍처의 근본적 특성. Polling 주기나 heartbeat 빈도를 변경해도 해결 불가. **실시간 릴레이는 반드시 Observer DOM 경로를 사용해야 함.**
|
||||||
|
- **참조**: `.agents/references/relay-architecture.md` (상세 분석)
|
||||||
|
|
||||||
|
### [2026-04-18] [Extension] Observer의 /pending POST에 명령어 컨텍스트가 없음 (Always run만 전달)
|
||||||
|
- **증상**: Discord auto-approve 알림에 "Always run"만 표시되고 실제 명령어가 안 보임.
|
||||||
|
- **원인**: Observer가 `/pending` POST 시 `command: "Always run"`, `description: "Always run"`만 보냄. `extractContextFromNearby(btn)`이 버튼 주변 DOM에서 유의미한 텍스트를 찾지 못함.
|
||||||
|
- **해결 (부분)**: v0.5.69에서 bridge/pending/ 디렉토리의 최신 Step Probe pending 파일에서 명령어를 읽는 fallback 추가. Step Probe pending이 있을 때만 작동.
|
||||||
|
- **주의**: Step Probe WAITING 감지가 진행 중 cascade에서 불가하므로 (위 이슈 참조), 현재 대부분의 경우 fallback도 실패. Observer의 DOM 컨텍스트 추출 개선이 필요.
|
||||||
|
|
||||||
|
### [2026-04-18] [Extension] Observer의 사용자 메시지 셀렉터 미매칭
|
||||||
|
- **증상**: 사용자가 AG에서 입력한 메시지가 Discord에 전혀 전달되지 않음.
|
||||||
|
- **원인**: Observer의 셀렉터(`.text-ide-message-block-user-color`, `[data-message-role="user"]` 등)가 AG Native DOM에서 매칭되지 않음. AI 응답만 `.leading-relaxed.select-text`로 매칭됨.
|
||||||
|
- **해결**: DOM 덤프에서 사용자 메시지 블록의 실제 CSS 클래스를 식별 후 셀렉터 추가 필요.
|
||||||
|
- **주의**: Step Probe의 USER_INPUT 캡처도 진행 중 cascade에서 불가 (위 이슈 참조).
|
||||||
|
|
||||||
|
### [2026-04-16] [Extension] ★ AG Native 세션 AI 응답이 Discord에 CSS로 전달됨 (v0.5.52 수정, #632)
|
||||||
|
- **증상**: Discord에 AI 대화 응답 대신 **CSS 스타일시트 코드** (`remark-github-blockquote-alert/alert.css`)가 전달됨. `scanChatBodies()` → POST /chat 경로는 작동하지만 내용이 CSS.
|
||||||
|
- **원인**: `extractCleanStepText()`에서 clone한 DOM에서 버튼/SVG/아이콘은 제거하지만 **`<style>` 태그는 제거하지 않음**. AG Native 마크다운 렌더러가 `.leading-relaxed.select-text` 내부에 `<style>` 블록을 주입하는데, 이 CSS textContent가 AI 응답 텍스트로 추출됨.
|
||||||
|
- **해결 (v0.5.52)**: `extractCleanStepText()` 최상단에 `clone.querySelectorAll('style, script, noscript, link[rel="stylesheet"]')` 제거 로직 추가. CSS/JS가 텍스트로 포함되는 것을 원천 차단.
|
||||||
|
- **배포**: v0.5.52 VSIX 설치 + v0.5.50/out/ JS 직접 복사 + V8 CachedData 삭제. AG File→Quit 재시작 필요.
|
||||||
|
- **이전 블로커 해결 이력**:
|
||||||
|
- SDK 경로: AG Native는 Cascade trajectory API 미등록 → step-probe RT-CAPTURE 불가 (구조적 한계)
|
||||||
|
- DOM 경로: v15에서 `#conversation` + `.leading-relaxed.select-text` 셀렉터 추가로 해결
|
||||||
|
- BEACON=0: AG 프로세스 완전 재시작으로 해결 (Reload Window로는 렌더러 HTML 캐시 유지)
|
||||||
|
- **주의**: AG Native 마크다운 렌더링은 `<style>` 블록을 응답 DOM 내부에 인라인으로 삽입함. DOM 텍스트 추출 시 반드시 style/script 태그를 먼저 제거해야 함.
|
||||||
|
- **Vikunja**: #632
|
||||||
|
|
||||||
|
### [2026-04-16] [Extension] ★ AG Native Observer innerText로 인한 마크다운 포맷 유실 및 사용자 요청 누락
|
||||||
|
- **증상**: Discord로 릴레이되는 AI 응답에서 리스트 번호(`1. 2.`)나 불릿(`-`) 등 마크다운 포맷이 완전히 유실되고 텍스트만 이어져서 나옴. 그리고 사용자가 입력한 메시지(요청)는 아예 릴레이되지 않음.
|
||||||
|
- **원인 1**: `extractCleanStepText()`에서 `innerText`를 사용하여 텍스트를 추출할 때, 렌더링되지 않은 DOM이나 CSS 카운터로 생성된 list-item 마커가 무시됨.
|
||||||
|
- **원인 2**: `scanChatBodies()` 로직이 `.leading-relaxed.select-text` (AI 응답 블록)만을 타겟팅하여 사용자의 메시지 박스(예: `.text-ide-message-block-user-color`)는 추출 대상에서 제외됨.
|
||||||
|
- **해결 (계획 중)**: `innerText` 대신 HTML DOM 노드를 순회하며 `convertNodeToMarkdown` 변환 함수를 통해 마크다운 문법을 보존하도록 개선. User 블록도 함께 감지하여 브릿지에 `role: 'user'` 플래그를 추가 전송하도록 수정 예정.
|
||||||
|
- **주의**: Webview 내에서 텍스트 노드를 파싱할 때 `innerText`는 브라우저 레이아웃 엔진에 의존하므로 완전한 마크다운 보존을 위해서는 Node Type 순회를 활용한 구조 복원이 보장되어야 함.
|
||||||
|
|
||||||
|
### [2026-04-16] [Extension] 터미널 출력(stdout) 텍스트가 명령어로 Discord에 전송 (v0.5.50)
|
||||||
|
- **증상**: Discord에 `cmd="No extension.log found"`, `cmd="AG CLI not found..."`, `cmd="Log found: C:\..."` 등 터미널 **출력** 텍스트가 명령어로 전송됨
|
||||||
|
- **원인**: Observer가 code 블록 2개를 감지: (1) 프롬프트+명령어 → JUNK_CODE_RE로 스킵, (2) 터미널 출력 → 유효한 code로 판단 → description에 포함. http-bridge enrichment에서 description에 prompt marker(`>`)가 없으면 rawDesc 전체를 enrichedCmd로 채택
|
||||||
|
- **해결 (v0.5.50)**: `promptMatch` 실패 시(description에 `>` 없음) → 터미널 OUTPUT으로 판단하여 `terminal_output` 사유로 즉시 필터. 실제 명령어는 항상 `…\project > command` 프롬프트를 포함
|
||||||
|
- **주의**: enrichment은 반드시 prompt marker가 있는 경우에만 수행. description에 `>` 없으면 code 블록의 출력 텍스트
|
||||||
|
|
||||||
|
### [2026-04-15] [Extension] Observer fallback 컨텍스트가 채팅/UI 텍스트를 명령어로 추출 (v0.5.46)
|
||||||
|
- **증상**: Discord에 `cmd="실 동작검증을 해봐야하는데"`, `cmd="variet.gravity-bridge"` 등 사용자 채팅/AI 응답 텍스트가 명령어로 전송됨
|
||||||
|
- **원인**: v0.5.45에서 `PROMPT_ONLY_RE`가 `code/pre` 요소 스킵 성공했으나, `extractContextFromNearby()`의 fallback(`span/div/p` 수집)이 여전히 작동하여 DOM 트리의 채팅 본문/UI 라벨을 명령어로 추출
|
||||||
|
- **해결 (v0.5.46)**: Observer v13에 `_promptOnlySkipped` 플래그 — code 요소가 모두 prompt-only이면 fallback 비활성화. http-bridge에 generic button + no-context일 때 무조건 필터
|
||||||
|
- **주의**: 프롬프트 스킵과 fallback 비활성화는 항상 연동해야 함. VSIX 설치 누락 방지를 위해 빌드 후 즉시 `code --install-extension` 확인 필수
|
||||||
|
|
||||||
|
### [2026-04-15] [Extension] PROMPT_ONLY_RE regex fixes — prompt-only terminal text leaking as enriched cmd (v0.5.45)
|
||||||
|
- **증상**: Discord에 `cmd="…\gravity_control >"` (실제 명령어 없는 빈 터미널 프롬프트)가 전송됨. 명령어가 포함된 경우는 정상 작동
|
||||||
|
- **원인 1 (Observer)**: `PROMPT_ONLY_RE` 의 `\\\\s`(4중 backslash)가 template literal 안의 regex 리터럴에서 "literal backslash+s"가 되어 whitespace class가 아닌 문자열 매칭
|
||||||
|
- **원인 2 (http-bridge)**: `PROMPT_ONLY_RE = /^[\s\\\/]*[\w_.-]+\s*[>»$#]\s*$/` — AG 터미널 프롬프트가 `…`(U+2026 ellipsis)로 시작하는데 첫 문자 클래스에 포함 안 됨
|
||||||
|
- **해결 (v0.5.45, `d2c44fe`)**: Observer `\\s`→`\s`, http-bridge 패턴을 `/^.*[>»$#]\s*$/`로 단순화
|
||||||
|
- **검증**: 11개 테스트 케이스 전체 통과
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [2026-04-14] [Extension] Observer template literal 정규식 이스케이핑 오류 — "Running N commands" 스킵 미작동
|
||||||
|
- **증상**: v9에서 `Running N commands` 그룹 헤더를 스킵하는 정규식 `/^Running\s*\d+\s*commands?$/i`를 추가했으나, 실제로 "Running2 commands" 텍스트를 전혀 매칭하지 못하여 여전히 Discord에 `cmd="Running2 commands"`가 전송됨
|
||||||
|
- **원인**: `observer-script.ts`의 전체 코드가 TypeScript template literal (`` ` `` ... `` ` ``) 안에 있으므로, 정규식 리터럴의 백슬래시가 2중 해석됨:
|
||||||
|
- TS 소스 `\\\\s` (4중) → template literal 출력 `\\s` → **브라우저에서 regex 리터럴 `\\s` = 리터럴 백슬래시+s** ❌
|
||||||
|
- TS 소스 `\\s` (2중) → template literal 출력 `\s` → **브라우저에서 regex 리터럴 `\s` = whitespace class** ✅
|
||||||
|
- TS 소스 `\s` (1중) → template literal에서 이스케이프 소멸 → **출력 `s`** ❌
|
||||||
|
- **영향 범위**: `NOISE_CODE_RE`, `cleanLines()` (word boundary `\b`, newline `\n`), `cleanButtonText()` (whitespace `\s`), port 탐색 regex `\d`, "Running N commands" 스킵 regex 전부 미작동이었음
|
||||||
|
- 단, `NOISE_RE` (new RegExp() 사용)는 문자열 이스케이핑이므로 4중 백슬래시가 올바름 (string → RegExp는 별도 이스케이핑 레이어)
|
||||||
|
- **해결**: 모든 정규식 리터럴 (`/pattern/flags`) 안의 `\\\\s` → `\\s`, `\\\\d` → `\\d`, `\\\\b` → `\\b`, `\\\\n` → `\\n` 으로 수정 (v0.5.41, `8e89209`)
|
||||||
|
- **주의**: **template literal 안에서 정규식 특수문자를 쓸 때 반드시 구분:**
|
||||||
|
- `new RegExp('pattern')` 문자열: `\\\\s` (4중) — string 이스케이핑(1번)+regex 이스케이핑(1번) = 총 2번
|
||||||
|
- `/pattern/` 정규식 리터럴: `\\s` (2중) — template literal 이스케이핑(1번)만 = 총 1번
|
||||||
|
- **검증법**: `node -e "var f = require('./extension/out/observer-script.js').generateApprovalObserverScript; require('fs').writeFileSync('tmp.js', f(0)); console.log(require('fs').readFileSync('tmp.js','utf8').match(/Running.{10}/g));"` 으로 생성된 코드 확인
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### [2026-04-13] [Extension] Observer v8 "Running N commands" 그룹 헤더를 승인 버튼으로 오인 — Discord 빈 내용+잘못된 버튼
|
### [2026-04-13] [Extension] Observer v8 "Running N commands" 그룹 헤더를 승인 버튼으로 오인 — Discord 빈 내용+잘못된 버튼
|
||||||
@@ -78,199 +189,389 @@
|
|||||||
- **<2A>빐寃<EBB990>**: `step-probe.ts` <20>궡<EFBFBD>뿉 `formatStepProbeCommand` <20>뿬<EFBFBD>띁 <20>븿<EFBFBD>닔瑜<EB8B94> 異붽<E795B0><EBB6BD><EFBFBD>븯<EFBFBD>뿬, WAITING <20>긽<EFBFBD>깭 <20>뒪<EFBFBD>뀦<EFBFBD>쓽 `argumentsJson` <20>뜲<EFBFBD>씠<EFBFBD>꽣瑜<EABDA3> 吏곸젒 <20>뙆<EFBFBD>떛<EFBFBD>븯怨<EBB8AF> `CommandLine`, `TargetFile` <20>벑 <20>떎<EFBFBD>젣 紐낅졊<EB8285>뼱<EFBFBD><EBBCB1><EFBFBD> <20>긽<EFBFBD>꽭 <20>씤<EFBFBD>옄/肄붾뱶瑜<EBB1B6> `command`<EFBFBD><EFBFBD><EFBFBD> `description`<EFBFBD>쑝濡<EFBFBD> <20>븷<EFBFBD>떦<EFBFBD>븯<EFBFBD>뿬 釉뚮┸吏<E294B8>濡<EFBFBD> <20>꽆湲곕룄濡<EBA384> <20>뙣移섑븿. DOM <20>샃<EFBFBD><EC8383><EFBFBD>踰꾩쓽 遺덉븞<EB8D89>젙<EFBFBD>꽦怨<EABDA6> 愿<>怨꾩뾾<EABEA9>씠 <20>씪愿<EC94AA><E684BF>맂 蹂몃Ц <20>쟾<EFBFBD>떖 蹂댁옣.
|
- **<2A>빐寃<EBB990>**: `step-probe.ts` <20>궡<EFBFBD>뿉 `formatStepProbeCommand` <20>뿬<EFBFBD>띁 <20>븿<EFBFBD>닔瑜<EB8B94> 異붽<E795B0><EBB6BD><EFBFBD>븯<EFBFBD>뿬, WAITING <20>긽<EFBFBD>깭 <20>뒪<EFBFBD>뀦<EFBFBD>쓽 `argumentsJson` <20>뜲<EFBFBD>씠<EFBFBD>꽣瑜<EABDA3> 吏곸젒 <20>뙆<EFBFBD>떛<EFBFBD>븯怨<EBB8AF> `CommandLine`, `TargetFile` <20>벑 <20>떎<EFBFBD>젣 紐낅졊<EB8285>뼱<EFBFBD><EBBCB1><EFBFBD> <20>긽<EFBFBD>꽭 <20>씤<EFBFBD>옄/肄붾뱶瑜<EBB1B6> `command`<EFBFBD><EFBFBD><EFBFBD> `description`<EFBFBD>쑝濡<EFBFBD> <20>븷<EFBFBD>떦<EFBFBD>븯<EFBFBD>뿬 釉뚮┸吏<E294B8>濡<EFBFBD> <20>꽆湲곕룄濡<EBA384> <20>뙣移섑븿. DOM <20>샃<EFBFBD><EC8383><EFBFBD>踰꾩쓽 遺덉븞<EB8D89>젙<EFBFBD>꽦怨<EABDA6> 愿<>怨꾩뾾<EABEA9>씠 <20>씪愿<EC94AA><E684BF>맂 蹂몃Ц <20>쟾<EFBFBD>떖 蹂댁옣.
|
||||||
- **二쇱쓽**: UI <20>뒪<EFBFBD>겕<EFBFBD>옒<EFBFBD>븨<EFBFBD>뿉 <20>쓽議댄븯<EB8C84>뒗 DOM Observer 諛⑹떇<E291B9><EB9687><EFBFBD> UI <20>젅<EFBFBD>씠<EFBFBD>븘<EFBFBD>썐, <20>븘<EFBFBD>씠肄<EC94A0> <20>궫<EFBFBD>엯 <20>벑<EFBFBD>뿉 痍⑥빟<E291A5>븯誘<EBB8AF>濡<EFBFBD>, <20>긽<EFBFBD>꽭 <20>럹<EFBFBD>씠濡쒕뱶 異붿텧<EBB6BF><ED85A7><EFBFBD> <20>빆<EFBFBD>긽 100% <20>떊猶<EB968A> 媛<><E5AA9B>뒫<EFBFBD>븳 SDK RPC(`step-probe.ts`) <20>뜲<EFBFBD>씠<EFBFBD>꽣瑜<EABDA3> <20>슦<EFBFBD>꽑 <20>궗<EFBFBD>슜<EFBFBD>븯<EFBFBD>룄濡<EBA384> 援ъ꽦<D18A>빐<EFBFBD>빞 <20>븿.
|
- **二쇱쓽**: UI <20>뒪<EFBFBD>겕<EFBFBD>옒<EFBFBD>븨<EFBFBD>뿉 <20>쓽議댄븯<EB8C84>뒗 DOM Observer 諛⑹떇<E291B9><EB9687><EFBFBD> UI <20>젅<EFBFBD>씠<EFBFBD>븘<EFBFBD>썐, <20>븘<EFBFBD>씠肄<EC94A0> <20>궫<EFBFBD>엯 <20>벑<EFBFBD>뿉 痍⑥빟<E291A5>븯誘<EBB8AF>濡<EFBFBD>, <20>긽<EFBFBD>꽭 <20>럹<EFBFBD>씠濡쒕뱶 異붿텧<EBB6BF><ED85A7><EFBFBD> <20>빆<EFBFBD>긽 100% <20>떊猶<EB968A> 媛<><E5AA9B>뒫<EFBFBD>븳 SDK RPC(`step-probe.ts`) <20>뜲<EFBFBD>씠<EFBFBD>꽣瑜<EABDA3> <20>슦<EFBFBD>꽑 <20>궗<EFBFBD>슜<EFBFBD>븯<EFBFBD>룄濡<EBA384> 援ъ꽦<D18A>빐<EFBFBD>빞 <20>븿.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <20>룷留<EBA3B7>
|
## <20>룷留<EBA3B7>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-23] [Extension] Cross-Project DOM Observer Leakage
|
### [2026-03-23] [Extension] Cross-Project DOM Observer Leakage
|
||||||
|
|
||||||
- **利앹긽**: <20>떎以<EB968E> <20>썝寃<EC8D9D> 而댄벂<EB8C84>꽣<EFBFBD>뿉<EFBFBD>꽌 <20>룞<EFBFBD>씪<EFBFBD>븳 <20>봽濡쒖젥<EC9296>듃紐낆쑝濡<EC919D> <20>떎<EFBFBD>뻾<EFBFBD>맂 VS Code<64>뱾<EFBFBD>씠 <20>꽌濡쒖쓽 `execute JavaScript` (Allow) <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇瑜<EC8387> 媛<>濡쒖콈嫄곕굹 <20>뿁<EFBFBD>슧<EFBFBD>븳 <20>꽌踰꾨줈 蹂대깂.
|
- **利앹긽**: <20>떎以<EB968E> <20>썝寃<EC8D9D> 而댄벂<EB8C84>꽣<EFBFBD>뿉<EFBFBD>꽌 <20>룞<EFBFBD>씪<EFBFBD>븳 <20>봽濡쒖젥<EC9296>듃紐낆쑝濡<EC919D> <20>떎<EFBFBD>뻾<EFBFBD>맂 VS Code<64>뱾<EFBFBD>씠 <20>꽌濡쒖쓽 `execute JavaScript` (Allow) <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇瑜<EC8387> 媛<>濡쒖콈嫄곕굹 <20>뿁<EFBFBD>슧<EFBFBD>븳 <20>꽌踰꾨줈 蹂대깂.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: Extension<6F>씠 `workbench.html`<EFBFBD>뿉 <20>뒪<EFBFBD>겕由쏀듃瑜<EB9383> 二쇱엯<EC87B1>븷 <20>븣 寃곗젙濡좎쟻 <20>룷<EFBFBD>듃瑜<EB9383> <20>븯<EFBFBD>뱶肄붾뵫<EBB6BE>뻽<EFBFBD>뒗<EFBFBD>뜲, <20>쟾<EFBFBD>뿭 罹먯떆<EBA8AF>맂 HTML <20>뙆<EFBFBD>씪<EFBFBD>쓣 紐⑤뱺 濡쒖뺄/<2F>썝寃<EC8D9D> <20>뿰寃곗씠 怨듭쑀<EB93AD>븯硫댁꽌 留덉<EFA78D><EB8D89>留됱뿉 <20>뿴由<EBBFB4> <20>봽濡쒖젥<EC9296>듃<EFBFBD>쓽 <20>룷<EFBFBD>듃 踰덊샇濡<EC8387> <20>뜮<EFBFBD>뼱<EFBFBD>뵆<EFBFBD>썙吏<EC8D99>.
|
- **<2A>썝<EFBFBD>씤**: Extension<6F>씠 `workbench.html`<EFBFBD>뿉 <20>뒪<EFBFBD>겕由쏀듃瑜<EB9383> 二쇱엯<EC87B1>븷 <20>븣 寃곗젙濡좎쟻 <20>룷<EFBFBD>듃瑜<EB9383> <20>븯<EFBFBD>뱶肄붾뵫<EBB6BE>뻽<EFBFBD>뒗<EFBFBD>뜲, <20>쟾<EFBFBD>뿭 罹먯떆<EBA8AF>맂 HTML <20>뙆<EFBFBD>씪<EFBFBD>쓣 紐⑤뱺 濡쒖뺄/<2F>썝寃<EC8D9D> <20>뿰寃곗씠 怨듭쑀<EB93AD>븯硫댁꽌 留덉<EFA78D><EB8D89>留됱뿉 <20>뿴由<EBBFB4> <20>봽濡쒖젥<EC9296>듃<EFBFBD>쓽 <20>룷<EFBFBD>듃 踰덊샇濡<EC8387> <20>뜮<EFBFBD>뼱<EFBFBD>뵆<EFBFBD>썙吏<EC8D99>.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: `extension.ts`<EFBFBD>뿉<EFBFBD>꽌 <20>긽<EFBFBD>깭 <20>몴<EFBFBD>떆以<EB9686>(Status Bar) `tooltip`<EFBFBD>뿉 <20>룷<EFBFBD>듃瑜<EB9383> 二쇱엯<EC87B1>븯怨<EBB8AF>, `observer-script.ts`<EFBFBD>뿉<EFBFBD>꽌 DOM 荑쇰━瑜<E29481> <20>넻<EFBFBD>빐 <20>룞<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> <20>옄<EFBFBD>떊<EFBFBD>쓽 李<>(Window)<29>뿉 <20>븷<EFBFBD>떦<EFBFBD>맂 <20>룷<EFBFBD>듃瑜<EB9383> 李얠븘<EC96A0>궡<EFBFBD>룄濡<EBA384> <20>닔<EFBFBD>젙. `vscode.env.asExternalUri`瑜<EFBFBD> <20>궗<EFBFBD>슜<EFBFBD>븯<EFBFBD>뿬 <20>룷<EFBFBD>듃 異⑸룎 <20>떆 <20>슦<EFBFBD>쉶<EFBFBD>맂 二쇱냼源뚯<E6BA90><EB9AAF> 濡쒖뺄 <20>룷<EFBFBD>썙<EFBFBD>뵫<EFBFBD>뿉 留ㅽ븨<E385BD>릺<EFBFBD>룄濡<EBA384> 吏<><EFA79E>썝.
|
- **<2A>빐寃<EBB990>**: `extension.ts`<EFBFBD>뿉<EFBFBD>꽌 <20>긽<EFBFBD>깭 <20>몴<EFBFBD>떆以<EB9686>(Status Bar) `tooltip`<EFBFBD>뿉 <20>룷<EFBFBD>듃瑜<EB9383> 二쇱엯<EC87B1>븯怨<EBB8AF>, `observer-script.ts`<EFBFBD>뿉<EFBFBD>꽌 DOM 荑쇰━瑜<E29481> <20>넻<EFBFBD>빐 <20>룞<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> <20>옄<EFBFBD>떊<EFBFBD>쓽 李<>(Window)<29>뿉 <20>븷<EFBFBD>떦<EFBFBD>맂 <20>룷<EFBFBD>듃瑜<EB9383> 李얠븘<EC96A0>궡<EFBFBD>룄濡<EBA384> <20>닔<EFBFBD>젙. `vscode.env.asExternalUri`瑜<EFBFBD> <20>궗<EFBFBD>슜<EFBFBD>븯<EFBFBD>뿬 <20>룷<EFBFBD>듃 異⑸룎 <20>떆 <20>슦<EFBFBD>쉶<EFBFBD>맂 二쇱냼源뚯<E6BA90><EB9AAF> 濡쒖뺄 <20>룷<EFBFBD>썙<EFBFBD>뵫<EFBFBD>뿉 留ㅽ븨<E385BD>릺<EFBFBD>룄濡<EBA384> 吏<><EFA79E>썝.
|
||||||
|
|
||||||
- **二쇱쓽**: VS Code UI 肄붿뼱(HTML) <20>뙣移<EB99A3> <20>떆, <20>뿬<EFBFBD>윭 李<>(Window)<29>씠<EFBFBD>굹 <20>떎以<EB968E> <20>썝寃<EC8D9D> <20>젒<EFBFBD>냽 <20>떆 <20>솚寃<EC869A>(Scope) 遺꾨━<EABEA8>뿉 媛곷퀎<EAB3B7>븳 二쇱쓽媛<EC93BD> <20>븘<EFBFBD>슂<EFBFBD>븿. <20>쟾<EFBFBD>뿭 <20>옄<EFBFBD>썝<EFBFBD>뿉 <20>쓽議댄븯<EB8C84>뒗 <20>븯<EFBFBD>뱶肄붾뵫 吏<><EFA79E>뼇.
|
- **二쇱쓽**: VS Code UI 肄붿뼱(HTML) <20>뙣移<EB99A3> <20>떆, <20>뿬<EFBFBD>윭 李<>(Window)<29>씠<EFBFBD>굹 <20>떎以<EB968E> <20>썝寃<EC8D9D> <20>젒<EFBFBD>냽 <20>떆 <20>솚寃<EC869A>(Scope) 遺꾨━<EABEA8>뿉 媛곷퀎<EAB3B7>븳 二쇱쓽媛<EC93BD> <20>븘<EFBFBD>슂<EFBFBD>븿. <20>쟾<EFBFBD>뿭 <20>옄<EFBFBD>썝<EFBFBD>뿉 <20>쓽議댄븯<EB8C84>뒗 <20>븯<EFBFBD>뱶肄붾뵫 吏<><EFA79E>뼇.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [<5B>궇吏<EAB687>] [<5B>궎<EFBFBD>썙<EFBFBD>뱶] <20><><EFBFBD> <20>븳以<EBB8B3> <20>슂<EFBFBD>빟
|
### [<5B>궇吏<EAB687>] [<5B>궎<EFBFBD>썙<EFBFBD>뱶] <20><><EFBFBD> <20>븳以<EBB8B3> <20>슂<EFBFBD>빟
|
||||||
|
|
||||||
- **利앹긽**: 臾댁뾿<EB8C81>씠 <20>옒紐삳릺<EC82B3>뿀<EFBFBD>뒗媛<EB9297>
|
- **利앹긽**: 臾댁뾿<EB8C81>씠 <20>옒紐삳릺<EC82B3>뿀<EFBFBD>뒗媛<EB9297>
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: 洹쇰낯 <20>썝<EFBFBD>씤
|
- **<2A>썝<EFBFBD>씤**: 洹쇰낯 <20>썝<EFBFBD>씤
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: <20>삱諛붾Ⅸ <20>빐寃<EBB990> 諛⑸쾿
|
- **<2A>빐寃<EBB990>**: <20>삱諛붾Ⅸ <20>빐寃<EBB990> 諛⑸쾿
|
||||||
|
|
||||||
- **二쇱쓽**: <20>옱諛<EC98B1> 諛⑹<E8AB9B><E291B9>瑜<EFBFBD> <20>쐞<EFBFBD>븳 援먰썕
|
- **二쇱쓽**: <20>옱諛<EC98B1> 諛⑹<E8AB9B><E291B9>瑜<EFBFBD> <20>쐞<EFBFBD>븳 援먰썕
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-08] [Discord Bot] Channel Deletion Cache Desync
|
### [2026-04-08] [Discord Bot] Channel Deletion Cache Desync
|
||||||
|
|
||||||
- **利앹긽**: 遊뉗씠 耳쒖졇 <20>엳<EFBFBD>뒗 <20>긽<EFBFBD>깭<EFBFBD>뿉<EFBFBD>꽌 Discord 梨꾨꼸(g-project-name)<29>쓣 <20>궘<EFBFBD>젣<EFBFBD>븯硫<EBB8AF>, 遊뉗씠 <20>궘<EFBFBD>젣瑜<ECA0A3> <20>씤吏<EC94A4><EFA79E>븯吏<EBB8AF> 紐삵븯怨<EBB8AF> <20>깉 梨꾨꼸<EABEA8>쓣 <20>깮<EFBFBD>꽦<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>쑝硫<EC919D> 硫붿떆吏<EB9686><EFA79E>룄 利앸컻<EC95B8>븿.
|
- **利앹긽**: 遊뉗씠 耳쒖졇 <20>엳<EFBFBD>뒗 <20>긽<EFBFBD>깭<EFBFBD>뿉<EFBFBD>꽌 Discord 梨꾨꼸(g-project-name)<29>쓣 <20>궘<EFBFBD>젣<EFBFBD>븯硫<EBB8AF>, 遊뉗씠 <20>궘<EFBFBD>젣瑜<ECA0A3> <20>씤吏<EC94A4><EFA79E>븯吏<EBB8AF> 紐삵븯怨<EBB8AF> <20>깉 梨꾨꼸<EABEA8>쓣 <20>깮<EFBFBD>꽦<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>쑝硫<EC919D> 硫붿떆吏<EB9686><EFA79E>룄 利앸컻<EC95B8>븿.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: ot.py<70>쓽 self.project_channels <20>뵓<EFBFBD>뀛<EFBFBD>꼫由ъ뿉 梨꾨꼸 媛앹껜媛<EABB9C> 罹먯떆<EBA8AF>릺<EFBFBD>뼱 <20>엳<EFBFBD>뼱, API <20>샇異<EC8387> <20>뾾<EFBFBD>씠 罹먯떆<EBA8AF>맂(<28>궘<EFBFBD>젣<EFBFBD>맂) 梨꾨꼸濡<EABCB8> 硫붿떆吏<EB9686>瑜<EFBFBD> 蹂대궡<EB8C80>젮 <20>떆<EFBFBD>룄<EFBFBD>븯<EFBFBD>떎 404 <20>뿉<EFBFBD>윭 諛쒖깮 <20>썑 <20>떎<EFBFBD>뙣<EFBFBD>븿.
|
- **<2A>썝<EFBFBD>씤**: ot.py<70>쓽 self.project_channels <20>뵓<EFBFBD>뀛<EFBFBD>꼫由ъ뿉 梨꾨꼸 媛앹껜媛<EABB9C> 罹먯떆<EBA8AF>릺<EFBFBD>뼱 <20>엳<EFBFBD>뼱, API <20>샇異<EC8387> <20>뾾<EFBFBD>씠 罹먯떆<EBA8AF>맂(<28>궘<EFBFBD>젣<EFBFBD>맂) 梨꾨꼸濡<EABCB8> 硫붿떆吏<EB9686>瑜<EFBFBD> 蹂대궡<EB8C80>젮 <20>떆<EFBFBD>룄<EFBFBD>븯<EFBFBD>떎 404 <20>뿉<EFBFBD>윭 諛쒖깮 <20>썑 <20>떎<EFBFBD>뙣<EFBFBD>븿.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: 梨꾨꼸 留듯븨<EB93AF>씠 瑗ъ<E79197><D18A><EFBFBD>쓣 <20>븣<EFBFBD>뒗 **Python 遊<>(Docker 而⑦뀒<E291A6>씠<EFBFBD>꼫)<29>쓣 <20>옱<EFBFBD>떆<EFBFBD>옉**<2A>븯<EFBFBD>뿬 罹먯떆瑜<EB9686> 珥덇린<EB8D87>솕<EFBFBD>븯怨<EBB8AF> 梨꾨꼸 紐⑸줉<E291B8>쓣 <20>깉濡<EAB989> 媛깆떊<EAB986>븯寃<EBB8AF> <20>븿.
|
- **<2A>빐寃<EBB990>**: 梨꾨꼸 留듯븨<EB93AF>씠 瑗ъ<E79197><D18A><EFBFBD>쓣 <20>븣<EFBFBD>뒗 **Python 遊<>(Docker 而⑦뀒<E291A6>씠<EFBFBD>꼫)<29>쓣 <20>옱<EFBFBD>떆<EFBFBD>옉**<2A>븯<EFBFBD>뿬 罹먯떆瑜<EB9686> 珥덇린<EB8D87>솕<EFBFBD>븯怨<EBB8AF> 梨꾨꼸 紐⑸줉<E291B8>쓣 <20>깉濡<EAB989> 媛깆떊<EAB986>븯寃<EBB8AF> <20>븿.
|
||||||
|
|
||||||
- **二쇱쓽**: 梨꾨꼸 愿<>由щ뒗 罹먯떆<EBA8AF>뿉 <20>쓽議댄븯湲<EBB8AF> <20>븣臾몄뿉 媛뺤젣濡<ECA0A3> Discord UI<55>뿉<EFBFBD>꽌 梨꾨꼸<EABEA8>쓣 吏<><EFA79E>썱<EFBFBD>쓣 <20>븣<EFBFBD>뒗 諛섎뱶<EC848E>떆 遊뉗쓣 <20>옱援щ룞<D189>빐<EFBFBD>빞 <20>븿.
|
- **二쇱쓽**: 梨꾨꼸 愿<>由щ뒗 罹먯떆<EBA8AF>뿉 <20>쓽議댄븯湲<EBB8AF> <20>븣臾몄뿉 媛뺤젣濡<ECA0A3> Discord UI<55>뿉<EFBFBD>꽌 梨꾨꼸<EABEA8>쓣 吏<><EFA79E>썱<EFBFBD>쓣 <20>븣<EFBFBD>뒗 諛섎뱶<EC848E>떆 遊뉗쓣 <20>옱援щ룞<D189>빐<EFBFBD>빞 <20>븿.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-08] [Extension] Multiple Workspace LS Cross-Connection
|
### [2026-04-08] [Extension] Multiple Workspace LS Cross-Connection
|
||||||
|
|
||||||
- **利앹긽**: ariet-llm 李쎌뿉<EC8E8C>꽌 耳곗쑝<EAB397>굹 gravity_control<6F>쓽 諛깃렇<EAB983>씪<EFBFBD>슫<EFBFBD>뱶 援щ룞 以묒씤 LS<4C>뿉 <20>뿰寃곕릺<EAB395>뼱 <20>옄湲<EC9884> <20>옄<EFBFBD>떊 李쎌쓽 <20>떊<EFBFBD>샇瑜<EC8387> <20>옟吏<EC989F> 紐삵븿.
|
- **利앹긽**: ariet-llm 李쎌뿉<EC8E8C>꽌 耳곗쑝<EAB397>굹 gravity_control<6F>쓽 諛깃렇<EAB983>씪<EFBFBD>슫<EFBFBD>뱶 援щ룞 以묒씤 LS<4C>뿉 <20>뿰寃곕릺<EAB395>뼱 <20>옄湲<EC9884> <20>옄<EFBFBD>떊 李쎌쓽 <20>떊<EFBFBD>샇瑜<EC8387> <20>옟吏<EC989F> 紐삵븿.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: <20>뿬<EFBFBD>윭 VS Code 李쎌쓣 <20>쓣<EFBFBD>썱<EFBFBD>쓣 <20>븣 <20>뼱<EFBFBD>뼡 李쎌뿉<EC8E8C>꽌<EFBFBD>뒗 Antigravity <20>뙣<EFBFBD>꼸<EFBFBD>쓣 <20>늻瑜댁<E7919C><EB8C81> <20>븡<EFBFBD>븘 <20>쟾<EFBFBD>슜 LS媛<53> <20>떆<EFBFBD>옉<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬. ixLSConnection()<29>씠 <20>옄湲<EC9884> 紐レ쓽 LS瑜<53> 李얠<EFA7A1><EC96A0> 紐삵븯怨<EBB8AF> fallback<63>쑝濡<EC919D> 湲곗〈<EAB397>뿉 <20>뼚 <20>엳<EFBFBD>뜕 <20>떎瑜<EB968E> 李쎌쓽 LS<4C>뿉 <20>뿰寃곕맖.
|
- **<2A>썝<EFBFBD>씤**: <20>뿬<EFBFBD>윭 VS Code 李쎌쓣 <20>쓣<EFBFBD>썱<EFBFBD>쓣 <20>븣 <20>뼱<EFBFBD>뼡 李쎌뿉<EC8E8C>꽌<EFBFBD>뒗 Antigravity <20>뙣<EFBFBD>꼸<EFBFBD>쓣 <20>늻瑜댁<E7919C><EB8C81> <20>븡<EFBFBD>븘 <20>쟾<EFBFBD>슜 LS媛<53> <20>떆<EFBFBD>옉<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬. ixLSConnection()<29>씠 <20>옄湲<EC9884> 紐レ쓽 LS瑜<53> 李얠<EFA7A1><EC96A0> 紐삵븯怨<EBB8AF> fallback<63>쑝濡<EC919D> 湲곗〈<EAB397>뿉 <20>뼚 <20>엳<EFBFBD>뜕 <20>떎瑜<EB968E> 李쎌쓽 LS<4C>뿉 <20>뿰寃곕맖.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: <20><><EFBFBD><EFBFBD>긽 李쎌뿉<EC8E8C>꽌 Developer: Reload Window <20>떎<EFBFBD>뻾 <20>썑 **<2A>궗<EFBFBD>씠<EFBFBD>뱶諛붿쓽 濡쒖뺄 Antigravity 梨쀫큸 <20>뙣<EFBFBD>꼸<EFBFBD>쓣 <20>븳 踰<> <20>뿴<EFBFBD>뼱** <20>옄<EFBFBD>떊<EFBFBD>쓽 LS <20>봽濡쒖꽭<EC9296>뒪瑜<EB92AA> <20>쓣<EFBFBD>슫 <20>뮘<EFBFBD>뿉 Gravity Bridge瑜<65> Start<72>븿.
|
- **<2A>빐寃<EBB990>**: <20><><EFBFBD><EFBFBD>긽 李쎌뿉<EC8E8C>꽌 Developer: Reload Window <20>떎<EFBFBD>뻾 <20>썑 **<2A>궗<EFBFBD>씠<EFBFBD>뱶諛붿쓽 濡쒖뺄 Antigravity 梨쀫큸 <20>뙣<EFBFBD>꼸<EFBFBD>쓣 <20>븳 踰<> <20>뿴<EFBFBD>뼱** <20>옄<EFBFBD>떊<EFBFBD>쓽 LS <20>봽濡쒖꽭<EC9296>뒪瑜<EB92AA> <20>쓣<EFBFBD>슫 <20>뮘<EFBFBD>뿉 Gravity Bridge瑜<65> Start<72>븿.
|
||||||
|
|
||||||
- **二쇱쓽**: LS<4C>뒗 <20>옄<EFBFBD>룞<EFBFBD>쑝濡<EC919D> <20>떆<EFBFBD>옉<EFBFBD>릺吏<EBA6BA> <20>븡怨<EBB8A1> <20>궗<EFBFBD>슜<EFBFBD>옄媛<EC9884> 梨꾪똿 <20>뙣<EFBFBD>꼸<EFBFBD>쓣 <20>븳 踰<> <20>겢由<EAB2A2>/<2F>솢<EFBFBD>꽦<EFBFBD>솕<EFBFBD>빐<EFBFBD>빞留<EBB99E> Spawn <20>맖.
|
- **二쇱쓽**: LS<4C>뒗 <20>옄<EFBFBD>룞<EFBFBD>쑝濡<EC919D> <20>떆<EFBFBD>옉<EFBFBD>릺吏<EBA6BA> <20>븡怨<EBB8A1> <20>궗<EFBFBD>슜<EFBFBD>옄媛<EC9884> 梨꾪똿 <20>뙣<EFBFBD>꼸<EFBFBD>쓣 <20>븳 踰<> <20>겢由<EAB2A2>/<2F>솢<EFBFBD>꽦<EFBFBD>솕<EFBFBD>빐<EFBFBD>빞留<EBB99E> Spawn <20>맖.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <20>윍<EFBFBD> Active/Recent Issues
|
## <20>윍<EFBFBD> Active/Recent Issues
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-09] [Extension] Agent UI Native Migration & Icon Text Gluing
|
### [2026-04-09] [Extension] Agent UI Native Migration & Icon Text Gluing
|
||||||
|
|
||||||
- **利앹긽**: UI Tailwind/Native 留덉씠洹몃젅<EBAA83>씠<EFBFBD>뀡 諛<> <20>븘<EFBFBD>씠肄<EC94A0> <20>쟻<EFBFBD>슜 <20>썑, Discord 釉뚮┸吏<E294B8>濡<EFBFBD> <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
- **利앹긽**: UI Tailwind/Native 留덉씠洹몃젅<EBAA83>씠<EFBFBD>뀡 諛<> <20>븘<EFBFBD>씠肄<EC94A0> <20>쟻<EFBFBD>슜 <20>썑, Discord 釉뚮┸吏<E294B8>濡<EFBFBD> <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> UI 踰꾪듉<EABEAA>쓽 `textContent` 異붿텧 <20>떆, Codicons <20>벑 <20>븘<EFBFBD>씠肄<EC94A0> <20>룿<EFBFBD>듃 臾몄옄<EBAA84>뿴(e.g., `渽<EFBFBD> Accept`)<29>씠 <20>븵遺<EBB8B5>遺꾩뿉 蹂묓빀(Gluing)<29>릺硫댁꽌, 湲곗〈<EAB397>쓽 `^` <20>빑而ㅺ<E8808C><E385BA> <20>룷<EFBFBD>븿<EFBFBD>맂 <20>젙洹쒖떇 留ㅼ묶(`/^(?:Always\s*)?Run/i`)<29>씠 <20>떎<EFBFBD>뙣<EFBFBD>븿.
|
- **<2A>썝<EFBFBD>씤**: <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> UI 踰꾪듉<EABEAA>쓽 `textContent` 異붿텧 <20>떆, Codicons <20>벑 <20>븘<EFBFBD>씠肄<EC94A0> <20>룿<EFBFBD>듃 臾몄옄<EBAA84>뿴(e.g., `渽<EFBFBD> Accept`)<29>씠 <20>븵遺<EBB8B5>遺꾩뿉 蹂묓빀(Gluing)<29>릺硫댁꽌, 湲곗〈<EAB397>쓽 `^` <20>빑而ㅺ<E8808C><E385BA> <20>룷<EFBFBD>븿<EFBFBD>맂 <20>젙洹쒖떇 留ㅼ묶(`/^(?:Always\s*)?Run/i`)<29>씠 <20>떎<EFBFBD>뙣<EFBFBD>븿.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: `observer-script.ts`<60>쓽 <20>뒪罹<EB92AA>, Sibling 踰꾪듉 <20>닔吏<EB8B94>, Webview Trigger-click <20>벑 `textContent`瑜<> 異붿텧<EBB6BF>븯<EFBFBD>뒗 紐⑤뱺 DOM <20>씫湲<EC94AB> 援ш컙<D188>뿉 `txt.replace(/^[^a-zA-Z0-9]+/, '')` <20>쟾泥섎━瑜<E29481> <20>쟻<EFBFBD>슜<EFBFBD>븯<EFBFBD>뿬 <20>꽑<EFBFBD>뻾 湲고샇/<2F>븘<EFBFBD>씠肄섏쓣 <20>븞<EFBFBD>쟾<EFBFBD>븯寃<EBB8AF> <20>젣嫄<ECA0A3>.
|
- **<2A>빐寃<EBB990>**: `observer-script.ts`<60>쓽 <20>뒪罹<EB92AA>, Sibling 踰꾪듉 <20>닔吏<EB8B94>, Webview Trigger-click <20>벑 `textContent`瑜<> 異붿텧<EBB6BF>븯<EFBFBD>뒗 紐⑤뱺 DOM <20>씫湲<EC94AB> 援ш컙<D188>뿉 `txt.replace(/^[^a-zA-Z0-9]+/, '')` <20>쟾泥섎━瑜<E29481> <20>쟻<EFBFBD>슜<EFBFBD>븯<EFBFBD>뿬 <20>꽑<EFBFBD>뻾 湲고샇/<2F>븘<EFBFBD>씠肄섏쓣 <20>븞<EFBFBD>쟾<EFBFBD>븯寃<EBB8AF> <20>젣嫄<ECA0A3>.
|
||||||
|
|
||||||
- **二쇱쓽**: Native UI 而댄룷<EB8C84>꼳<EFBFBD>듃 <20>솚寃쎌뿉<EC8E8C>꽌<EFBFBD>뒗 <20>뀓<EFBFBD>뒪<EFBFBD>듃 <20>끂<EFBFBD>뱶肉먮쭔 <20>븘<EFBFBD>땲<EFBFBD>씪 <20>븘<EFBFBD>씠肄<EC94A0>/SVG 而댄룷<EB8C84>꼳<EFBFBD>듃<EFBFBD>쓽 <20>뀓<EFBFBD>뒪<EFBFBD>듃 湲<>猷⑥엵 <20>쁽<EFBFBD>긽<EFBFBD>쑝濡<EC919D> <20>씤<EFBFBD>빐 <20>뾼寃⑺븳 <20>떆<EFBFBD>옉<EFBFBD>젏(`^`) <20>젙洹쒖떇<EC9296>씠 源⑥쭏 <20>닔 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD>, <20>빆<EFBFBD>긽 遺덊븘<EB8D8A>슂<EFBFBD>븳 <20>듅<EFBFBD>닔臾몄옄 <20>쟾泥섎━瑜<E29481> <20>꽑<EFBFBD>뻾<EFBFBD>빐<EFBFBD>빞 <20>븿.
|
- **二쇱쓽**: Native UI 而댄룷<EB8C84>꼳<EFBFBD>듃 <20>솚寃쎌뿉<EC8E8C>꽌<EFBFBD>뒗 <20>뀓<EFBFBD>뒪<EFBFBD>듃 <20>끂<EFBFBD>뱶肉먮쭔 <20>븘<EFBFBD>땲<EFBFBD>씪 <20>븘<EFBFBD>씠肄<EC94A0>/SVG 而댄룷<EB8C84>꼳<EFBFBD>듃<EFBFBD>쓽 <20>뀓<EFBFBD>뒪<EFBFBD>듃 湲<>猷⑥엵 <20>쁽<EFBFBD>긽<EFBFBD>쑝濡<EC919D> <20>씤<EFBFBD>빐 <20>뾼寃⑺븳 <20>떆<EFBFBD>옉<EFBFBD>젏(`^`) <20>젙洹쒖떇<EC9296>씠 源⑥쭏 <20>닔 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD>, <20>빆<EFBFBD>긽 遺덊븘<EB8D8A>슂<EFBFBD>븳 <20>듅<EFBFBD>닔臾몄옄 <20>쟾泥섎━瑜<E29481> <20>꽑<EFBFBD>뻾<EFBFBD>빐<EFBFBD>빞 <20>븿.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-09] [Extension] Agent UI Native Migration & CodeLens False Positive Filter
|
### [2026-04-09] [Extension] Agent UI Native Migration & CodeLens False Positive Filter
|
||||||
|
|
||||||
- **利앹긽**: UI Tailwind/Native 留덉씠洹몃젅<EBAA83>씠<EFBFBD>뀡 <20>쟻<EFBFBD>슜 <20>썑, Discord 釉뚮┸吏<E294B8>濡<EFBFBD> <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD><EC9FBE><EFBFBD> <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬
|
- **利앹긽**: UI Tailwind/Native 留덉씠洹몃젅<EBAA83>씠<EFBFBD>뀡 <20>쟻<EFBFBD>슜 <20>썑, Discord 釉뚮┸吏<E294B8>濡<EFBFBD> <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD><EC9FBE><EFBFBD> <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: Agent <20>뙣<EFBFBD>꼸<EFBFBD>씠 <20>꺆/<2F>뿉<EFBFBD>뵒<EFBFBD>꽣 蹂몃Ц<EBAA83>뿉 吏곸젒 <20>젋<EFBFBD>뜑留곷릺硫댁꽌, 湲곗〈 <20>삤<EFBFBD>옉<EFBFBD>룞 諛⑹<E8AB9B><E291B9> 濡쒖쭅(`if (b.closest('.monaco-editor'))`)<29>뿉 <20>뙣<EFBFBD>꼸 <20>쟾泥<EC9FBE> 踰꾪듉<EABEAA>씠 <20>룷李⑸릺<E291B8>뼱 臾댁떆<EB8C81>맖
|
- **<2A>썝<EFBFBD>씤**: Agent <20>뙣<EFBFBD>꼸<EFBFBD>씠 <20>꺆/<2F>뿉<EFBFBD>뵒<EFBFBD>꽣 蹂몃Ц<EBAA83>뿉 吏곸젒 <20>젋<EFBFBD>뜑留곷릺硫댁꽌, 湲곗〈 <20>삤<EFBFBD>옉<EFBFBD>룞 諛⑹<E8AB9B><E291B9> 濡쒖쭅(`if (b.closest('.monaco-editor'))`)<29>뿉 <20>뙣<EFBFBD>꼸 <20>쟾泥<EC9FBE> 踰꾪듉<EABEAA>씠 <20>룷李⑸릺<E291B8>뼱 臾댁떆<EB8C81>맖
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: <20>꼫臾<EABCAB> 愿묐쾾<EBAC90>쐞<EFBFBD>븳 `.monaco-editor` 諛⑹뼱瑜<EBBCB1> <20>빐<EFBFBD>젣<EFBFBD>븯怨<EBB8AF>, 肄붾뱶 <20>젋利<ECA08B> 怨좎쑀 而⑦뀒<E291A6>씠<EFBFBD>꼫<EFBFBD>씤 `.codelens-decoration` <20>궡遺<EAB6A1><E981BA>씪 寃쎌슦<EC8E8C>뿉留<EBBF89> 臾댁떆<EB8C81>븯<EFBFBD>룄濡<EBA384> <20><><EFBFBD><EFBFBD>룷<EFBFBD>씤<EFBFBD>듃 <20>닔<EFBFBD>젙
|
- **<2A>빐寃<EBB990>**: <20>꼫臾<EABCAB> 愿묐쾾<EBAC90>쐞<EFBFBD>븳 `.monaco-editor` 諛⑹뼱瑜<EBBCB1> <20>빐<EFBFBD>젣<EFBFBD>븯怨<EBB8AF>, 肄붾뱶 <20>젋利<ECA08B> 怨좎쑀 而⑦뀒<E291A6>씠<EFBFBD>꼫<EFBFBD>씤 `.codelens-decoration` <20>궡遺<EAB6A1><E981BA>씪 寃쎌슦<EC8E8C>뿉留<EBBF89> 臾댁떆<EB8C81>븯<EFBFBD>룄濡<EBA384> <20><><EFBFBD><EFBFBD>룷<EFBFBD>씤<EFBFBD>듃 <20>닔<EFBFBD>젙
|
||||||
|
|
||||||
- **二쇱쓽**: DOM <20>샃<EFBFBD><EC8383><EFBFBD>踰<EFBFBD> <20>븘<EFBFBD>꽣 議곌굔 <20>옉<EFBFBD>꽦 <20>떆 <20>옒<EFBFBD>띁 <20>겢<EFBFBD>옒<EFBFBD>뒪<EFBFBD>뒗 UI <20>뵒<EFBFBD>옄<EFBFBD>씤 媛쒗렪(Native, Editor Tab <20>벑 <20>쐞移<EC909E> 蹂<>寃<EFBFBD>)<29>뿉 留ㅼ슦 痍⑥빟<E291A5>븿. 媛<><E5AA9B>옣 援ъ껜<D18A>쟻<EFBFBD>씤 <20>궡遺<EAB6A1> <20>끂<EFBFBD>뱶 <20>겢<EFBFBD>옒<EFBFBD>뒪<EFBFBD>굹 <20><><EFBFBD>寃<EFBFBD> 怨좎쑀 <20>냽<EFBFBD>꽦<EFBFBD>쓣 <20>넻<EFBFBD>빐 <20>븘<EFBFBD>꽣留곹븷 寃<>
|
- **二쇱쓽**: DOM <20>샃<EFBFBD><EC8383><EFBFBD>踰<EFBFBD> <20>븘<EFBFBD>꽣 議곌굔 <20>옉<EFBFBD>꽦 <20>떆 <20>옒<EFBFBD>띁 <20>겢<EFBFBD>옒<EFBFBD>뒪<EFBFBD>뒗 UI <20>뵒<EFBFBD>옄<EFBFBD>씤 媛쒗렪(Native, Editor Tab <20>벑 <20>쐞移<EC909E> 蹂<>寃<EFBFBD>)<29>뿉 留ㅼ슦 痍⑥빟<E291A5>븿. 媛<><E5AA9B>옣 援ъ껜<D18A>쟻<EFBFBD>씤 <20>궡遺<EAB6A1> <20>끂<EFBFBD>뱶 <20>겢<EFBFBD>옒<EFBFBD>뒪<EFBFBD>굹 <20><><EFBFBD>寃<EFBFBD> 怨좎쑀 <20>냽<EFBFBD>꽦<EFBFBD>쓣 <20>넻<EFBFBD>빐 <20>븘<EFBFBD>꽣留곹븷 寃<>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-31] [step-probe] GetAllCascadeTrajectories 10-Item Hard Limit (Signal Drop)
|
### [2026-03-31] [step-probe] GetAllCascadeTrajectories 10-Item Hard Limit (Signal Drop)
|
||||||
|
|
||||||
- **利앹긽**: `guitar_score` <20>벑<EFBFBD>뿉<EFBFBD>꽌 <20>솢<EFBFBD>꽦<EFBFBD>솕<EFBFBD>맂 <20>꽭<EFBFBD>뀡<EFBFBD>쓽 <20>뵒<EFBFBD>뒪肄붾뱶 <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇瑜<EC8387> "怨꾩냽<EABEA9>빐<EFBFBD>꽌" <20>옟吏<EC989F> 紐삵븿. (WS 60珥<30> <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐蹂대떎 <20>뜑 移섎챸<EC848E>쟻<EFBFBD>쑝濡<EC919D> <20>떊<EFBFBD>샇媛<EC8387> <20>븘<EFBFBD>삁 媛<>吏<EFBFBD> <20>븡<EFBFBD>쓬)
|
- **利앹긽**: `guitar_score` <20>벑<EFBFBD>뿉<EFBFBD>꽌 <20>솢<EFBFBD>꽦<EFBFBD>솕<EFBFBD>맂 <20>꽭<EFBFBD>뀡<EFBFBD>쓽 <20>뵒<EFBFBD>뒪肄붾뱶 <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇瑜<EC8387> "怨꾩냽<EABEA9>빐<EFBFBD>꽌" <20>옟吏<EC989F> 紐삵븿. (WS 60珥<30> <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐蹂대떎 <20>뜑 移섎챸<EC848E>쟻<EFBFBD>쑝濡<EC919D> <20>떊<EFBFBD>샇媛<EC8387> <20>븘<EFBFBD>삁 媛<>吏<EFBFBD> <20>븡<EFBFBD>쓬)
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: Extension<6F>씠 <20>솢<EFBFBD>꽦 <20>꽭<EFBFBD>뀡<EFBFBD>쓣 李얘린 <20>쐞<EFBFBD>빐 <20>샇異쒗븯<EC9297>뒗 `GetAllCascadeTrajectories` LS API媛<49> `{}`(鍮<> <20>씤<EFBFBD>옄)濡<> <20>샇異쒕맆 <20>븣, 湲곕낯<EAB395>쟻<EFBFBD>쑝濡<EC919D> **10媛쒖쓽 <20>꽭<EFBFBD>뀡留<EB80A1> 諛섑솚<EC8491>븯<EFBFBD>뒗 <20>븯<EFBFBD>뱶 由щ컠(Pagination Limit)**<2A>씠 嫄몃젮<EBAA83>엳<EFBFBD>쓬. <20>씠濡<EC94A0> <20>씤<EFBFBD>빐 <20>옉<EFBFBD>뾽 <20>궡<EFBFBD>뿭<EFBFBD>씠 <20>늻<EFBFBD>쟻<EFBFBD>릺硫<EBA6BA> <20>닔留롮<EFA78D><EBA1AE> 理쒖떊/吏꾪뻾 以<> <20>꽭<EFBFBD>뀡<EFBFBD>뱾<EFBFBD>씠 10媛<30> 紐⑸줉<E291B8>뿉<EFBFBD>꽌 諛<><E8AB9B>젮<EFBFBD>굹 <20>늻<EFBFBD>씫<EFBFBD>맖. <20>씡<EFBFBD>뒪<EFBFBD>뀗<EFBFBD>뀡<EFBFBD><EB80A1><EFBFBD> <20>꽭<EFBFBD>뀡<EFBFBD>씠 <20>뾾<EFBFBD>떎怨<EB968E> <20>뙋<EFBFBD>떒<EFBFBD>빐 媛뺤젣濡<ECA0A3> `IDLE` 紐⑤뱶<E291A4>뿉 吏꾩엯<EABEA9>븯硫<EBB8AF>, <20>듅<EFBFBD>씤 <20><><EFBFBD>湲곗뿴(WAITING) <20>옄泥대<EFA7A3><EB8C80> 寃<><E5AF83>궗<EFBFBD>븯吏<EBB8AF> <20>븡寃<EBB8A1> <20>맖.
|
- **<2A>썝<EFBFBD>씤**: Extension<6F>씠 <20>솢<EFBFBD>꽦 <20>꽭<EFBFBD>뀡<EFBFBD>쓣 李얘린 <20>쐞<EFBFBD>빐 <20>샇異쒗븯<EC9297>뒗 `GetAllCascadeTrajectories` LS API媛<49> `{}`(鍮<> <20>씤<EFBFBD>옄)濡<> <20>샇異쒕맆 <20>븣, 湲곕낯<EAB395>쟻<EFBFBD>쑝濡<EC919D> **10媛쒖쓽 <20>꽭<EFBFBD>뀡留<EB80A1> 諛섑솚<EC8491>븯<EFBFBD>뒗 <20>븯<EFBFBD>뱶 由щ컠(Pagination Limit)**<2A>씠 嫄몃젮<EBAA83>엳<EFBFBD>쓬. <20>씠濡<EC94A0> <20>씤<EFBFBD>빐 <20>옉<EFBFBD>뾽 <20>궡<EFBFBD>뿭<EFBFBD>씠 <20>늻<EFBFBD>쟻<EFBFBD>릺硫<EBA6BA> <20>닔留롮<EFA78D><EBA1AE> 理쒖떊/吏꾪뻾 以<> <20>꽭<EFBFBD>뀡<EFBFBD>뱾<EFBFBD>씠 10媛<30> 紐⑸줉<E291B8>뿉<EFBFBD>꽌 諛<><E8AB9B>젮<EFBFBD>굹 <20>늻<EFBFBD>씫<EFBFBD>맖. <20>씡<EFBFBD>뒪<EFBFBD>뀗<EFBFBD>뀡<EFBFBD><EB80A1><EFBFBD> <20>꽭<EFBFBD>뀡<EFBFBD>씠 <20>뾾<EFBFBD>떎怨<EB968E> <20>뙋<EFBFBD>떒<EFBFBD>빐 媛뺤젣濡<ECA0A3> `IDLE` 紐⑤뱶<E291A4>뿉 吏꾩엯<EABEA9>븯硫<EBB8AF>, <20>듅<EFBFBD>씤 <20><><EFBFBD>湲곗뿴(WAITING) <20>옄泥대<EFA7A3><EB8C80> 寃<><E5AF83>궗<EFBFBD>븯吏<EBB8AF> <20>븡寃<EBB8A1> <20>맖.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.14): `v0.5.13`<60>뿉<EFBFBD>꽌 <20>룄<EFBFBD>엯<EFBFBD>뻽<EFBFBD>뜕 `{ limit: 100 }`<60>씠 LS <20>떒<EFBFBD>쓽 荑쇰━ 怨쇰<E680A8><EC87B0><EFBFBD>븯濡<EBB8AF> <20>씤<EFBFBD>븳 VS Code UI <20>봽由ъ쭠(DoS)<29>쓣 <20>쑀諛쒗븯<EC9297>뿬 濡ㅻ갚<E385BB>븯<EFBFBD>뒗 以<> <20>븘<EFBFBD>닔 <20>젙<EFBFBD>젹 <20>뙆<EFBFBD>씪誘명꽣(`descending: true`)源뚯<E6BA90><EB9AAF> <20>냼<EFBFBD>떎<EFBFBD>릺<EFBFBD>뿀<EFBFBD>뜕 <20>떎<EFBFBD>닔瑜<EB8B94> 援먯젙<EBA8AF>븿. 理쒖쥌<EC9296>쟻<EFBFBD>쑝濡<EC919D> `{ limit: 30, descending: true }`瑜<> <20>쟻<EFBFBD>슜<EFBFBD>븯<EFBFBD>뿬 <20>뙆<EFBFBD>떛 遺<><E981BA>븯 理쒖냼<EC9296>솕 諛<> 理쒖떊 <20>꽭<EFBFBD>뀡 理쒖긽<EC9296>떒(Index 0) 議고쉶瑜<EC89B6> <20>븞<EFBFBD>쟾<EFBFBD>븯寃<EBB8AF> 援ы쁽<D18B>븿.
|
- **<2A>빐寃<EBB990>** (v0.5.14): `v0.5.13`<60>뿉<EFBFBD>꽌 <20>룄<EFBFBD>엯<EFBFBD>뻽<EFBFBD>뜕 `{ limit: 100 }`<60>씠 LS <20>떒<EFBFBD>쓽 荑쇰━ 怨쇰<E680A8><EC87B0><EFBFBD>븯濡<EBB8AF> <20>씤<EFBFBD>븳 VS Code UI <20>봽由ъ쭠(DoS)<29>쓣 <20>쑀諛쒗븯<EC9297>뿬 濡ㅻ갚<E385BB>븯<EFBFBD>뒗 以<> <20>븘<EFBFBD>닔 <20>젙<EFBFBD>젹 <20>뙆<EFBFBD>씪誘명꽣(`descending: true`)源뚯<E6BA90><EB9AAF> <20>냼<EFBFBD>떎<EFBFBD>릺<EFBFBD>뿀<EFBFBD>뜕 <20>떎<EFBFBD>닔瑜<EB8B94> 援먯젙<EBA8AF>븿. 理쒖쥌<EC9296>쟻<EFBFBD>쑝濡<EC919D> `{ limit: 30, descending: true }`瑜<> <20>쟻<EFBFBD>슜<EFBFBD>븯<EFBFBD>뿬 <20>뙆<EFBFBD>떛 遺<><E981BA>븯 理쒖냼<EC9296>솕 諛<> 理쒖떊 <20>꽭<EFBFBD>뀡 理쒖긽<EC9296>떒(Index 0) 議고쉶瑜<EC89B6> <20>븞<EFBFBD>쟾<EFBFBD>븯寃<EBB8AF> 援ы쁽<D18B>븿.
|
||||||
|
|
||||||
- **二쇱쓽**: LS<4C>쓽 湲곕낯 SQLite/DB <20>쓳<EFBFBD>떟 Limit 洹쒖튃<EC9296>뿉 <20>쓽議댄븯<EB8C84>뿬 <20>쟾泥<EC9FBE> <20>뜲<EFBFBD>씠<EFBFBD>꽣 <20>뒪罹붿쓣 <20>닔<EFBFBD>뻾<EFBFBD>븯<EFBFBD>뒗 濡쒖쭅<EC9296><ECAD85><EFBFBD> <20>뼵<EFBFBD>젣<EFBFBD>뱺 Truncation <20>씠<EFBFBD>뒋(Data Loss)瑜<> <20>쑀諛쒗븷 <20>닔 <20>엳<EFBFBD>쓬.
|
- **二쇱쓽**: LS<4C>쓽 湲곕낯 SQLite/DB <20>쓳<EFBFBD>떟 Limit 洹쒖튃<EC9296>뿉 <20>쓽議댄븯<EB8C84>뿬 <20>쟾泥<EC9FBE> <20>뜲<EFBFBD>씠<EFBFBD>꽣 <20>뒪罹붿쓣 <20>닔<EFBFBD>뻾<EFBFBD>븯<EFBFBD>뒗 濡쒖쭅<EC9296><ECAD85><EFBFBD> <20>뼵<EFBFBD>젣<EFBFBD>뱺 Truncation <20>씠<EFBFBD>뒋(Data Loss)瑜<> <20>쑀諛쒗븷 <20>닔 <20>엳<EFBFBD>쓬.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-31] [WS] Browser API Fallback 60s Timeout (Zombie Connection)
|
### [2026-03-31] [WS] Browser API Fallback 60s Timeout (Zombie Connection)
|
||||||
|
|
||||||
- **利앹긽**: `guitar_score` <20>벑 紐⑤뱺 <20>옉<EFBFBD>뾽 <20>솚寃쎌뿉<EC8E8C>꽌 <20>빟 60珥덈쭏<EB8D88>떎 WebSocket <20>뿰寃곗씠 <20>걡湲곌퀬 <20>옱<EFBFBD>뿰寃곕릺<EAB395>뒗 <20>쁽<EFBFBD>긽<EFBFBD>씠 諛섎났<EC848E>릺硫<EBA6BA>(extension.log<6F>뿉 `Heartbeat timeout` 怨꾩냽 異쒕젰), 洹<> <20>궗<EFBFBD>씠 <20>뵒<EFBFBD>뒪肄붾뱶 <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇瑜<EC8387> <20>넃移<EB8483>.
|
- **利앹긽**: `guitar_score` <20>벑 紐⑤뱺 <20>옉<EFBFBD>뾽 <20>솚寃쎌뿉<EC8E8C>꽌 <20>빟 60珥덈쭏<EB8D88>떎 WebSocket <20>뿰寃곗씠 <20>걡湲곌퀬 <20>옱<EFBFBD>뿰寃곕릺<EAB395>뒗 <20>쁽<EFBFBD>긽<EFBFBD>씠 諛섎났<EC848E>릺硫<EBA6BA>(extension.log<6F>뿉 `Heartbeat timeout` 怨꾩냽 異쒕젰), 洹<> <20>궗<EFBFBD>씠 <20>뵒<EFBFBD>뒪肄붾뱶 <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇瑜<EC8387> <20>넃移<EB8483>.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: Extension<6F>씠 `ws` 紐⑤뱢 濡쒕뱶 <20>떎<EFBFBD>뙣(VS Code <20>솚寃<EC869A> <20>벑)濡<> <20>씤<EFBFBD>빐 釉뚮씪<EB9AAE>슦<EFBFBD><EC8AA6><EFBFBD> <20>궡<EFBFBD>옣 `WebSocket` 媛앹껜濡<EABB9C> Fallback <20>맖. 釉뚮씪<EB9AAE>슦<EFBFBD><EC8AA6><EFBFBD> WS<57>뒗 <20>꽌踰꾩쓽 <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> ping<6E>쓣 諛쏆븘 pong<6E>쓣 <20>옄<EFBFBD>룞 <20>쓳<EFBFBD>떟<EFBFBD>븯吏<EBB8AF>留<EFBFBD> JS<4A>뿉 <20>씠踰ㅽ듃瑜<EB9383> <20>끂異쒗븯吏<EBB8AF> <20>븡<EFBFBD>쓬. <20>씠濡<EC94A0> <20>씤<EFBFBD>빐 `lastPongTime` 媛깆떊<EAB986>씠 遺덇<E981BA><EB8D87><EFBFBD>뒫<EFBFBD>빐<EFBFBD>졇, `Date.now() - lastPongTime > 60000` 議곌굔<EAB38C>씠 臾댁“嫄<E2809C> <20>넻怨쇰릺<EC87B0>뼱 硫<>姨≫븳 <20>뿰寃곗쓣 媛뺤젣 醫낅즺<EB8285>븿 (False Positive).
|
- **<2A>썝<EFBFBD>씤**: Extension<6F>씠 `ws` 紐⑤뱢 濡쒕뱶 <20>떎<EFBFBD>뙣(VS Code <20>솚寃<EC869A> <20>벑)濡<> <20>씤<EFBFBD>빐 釉뚮씪<EB9AAE>슦<EFBFBD><EC8AA6><EFBFBD> <20>궡<EFBFBD>옣 `WebSocket` 媛앹껜濡<EABB9C> Fallback <20>맖. 釉뚮씪<EB9AAE>슦<EFBFBD><EC8AA6><EFBFBD> WS<57>뒗 <20>꽌踰꾩쓽 <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> ping<6E>쓣 諛쏆븘 pong<6E>쓣 <20>옄<EFBFBD>룞 <20>쓳<EFBFBD>떟<EFBFBD>븯吏<EBB8AF>留<EFBFBD> JS<4A>뿉 <20>씠踰ㅽ듃瑜<EB9383> <20>끂異쒗븯吏<EBB8AF> <20>븡<EFBFBD>쓬. <20>씠濡<EC94A0> <20>씤<EFBFBD>빐 `lastPongTime` 媛깆떊<EAB986>씠 遺덇<E981BA><EB8D87><EFBFBD>뒫<EFBFBD>빐<EFBFBD>졇, `Date.now() - lastPongTime > 60000` 議곌굔<EAB38C>씠 臾댁“嫄<E2809C> <20>넻怨쇰릺<EC87B0>뼱 硫<>姨≫븳 <20>뿰寃곗쓣 媛뺤젣 醫낅즺<EB8285>븿 (False Positive).
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.12):
|
- **<2A>빐寃<EBB990>** (v0.5.12):
|
||||||
|
|
||||||
1. `hub.py`: `{"type": "heartbeat"}` JSON 硫붿떆吏<EB9686> <20>닔<EFBFBD>떊 <20>떆 紐낆떆<EB8286>쟻<EFBFBD>쑝濡<EC919D> `{"type": "pong"}` JSON<4F>쓣 <20>쓳<EFBFBD>떟<EFBFBD>븯<EFBFBD>룄濡<EBA384> <20>닔<EFBFBD>젙.
|
1. `hub.py`: `{"type": "heartbeat"}` JSON 硫붿떆吏<EB9686> <20>닔<EFBFBD>떊 <20>떆 紐낆떆<EB8286>쟻<EFBFBD>쑝濡<EC919D> `{"type": "pong"}` JSON<4F>쓣 <20>쓳<EFBFBD>떟<EFBFBD>븯<EFBFBD>룄濡<EBA384> <20>닔<EFBFBD>젙.
|
||||||
|
|
||||||
2. `ws-client.ts`: 紐낆떆<EB8286>쟻 `pong` <20>빖<EFBFBD>뱾<EFBFBD>윭 異붽<E795B0><EBB6BD>. JSON pong 吏<><EFA79E>썝 <20>꽌踰꾧굅<EABEA7>굹 Node.js ws瑜<73> <20>궗<EFBFBD>슜<EFBFBD>븷 <20>븣留<EBB8A3> 60珥<30> <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐 寃<>利앹쓣 嫄곗튂<EAB397>룄濡<EBA384> 議곌굔 蹂닿컯 (`forceHeartbeatTimeoutIfNoPong`).
|
2. `ws-client.ts`: 紐낆떆<EB8286>쟻 `pong` <20>빖<EFBFBD>뱾<EFBFBD>윭 異붽<E795B0><EBB6BD>. JSON pong 吏<><EFA79E>썝 <20>꽌踰꾧굅<EABEA7>굹 Node.js ws瑜<73> <20>궗<EFBFBD>슜<EFBFBD>븷 <20>븣留<EBB8A3> 60珥<30> <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐 寃<>利앹쓣 嫄곗튂<EAB397>룄濡<EBA384> 議곌굔 蹂닿컯 (`forceHeartbeatTimeoutIfNoPong`).
|
||||||
|
|
||||||
- **二쇱쓽**: 釉뚮씪<EB9AAE>슦<EFBFBD><EC8AA6><EFBFBD> <20>몴以<EBAAB4> WebSockets(W3C)<29>뒗 ping/pong <20>젣<EFBFBD>뼱 <20>봽<EFBFBD>젅<EFBFBD>엫<EFBFBD>쓣 JS濡<53> <20>끂異쒗븯吏<EBB8AF> <20>븡<EFBFBD>쓬. <20>뤃由ы븘/<2F>겕濡쒖뒪<EC9296>뵆<EFBFBD>옯<EFBFBD>뤌 WS <20>옒<EFBFBD>띁 <20>궗<EFBFBD>슜 <20>떆 <20>븯<EFBFBD>듃鍮꾪듃<EABEAA>뒗 諛섎뱶<EC848E>떆 JSON 硫붿꽭吏<EABDAD> <20>삎<EFBFBD>깭<EFBFBD>쓽 Application Layer Ping/Pong<6E>쑝濡<EC919D> <20><><EFBFBD><EFBFBD>뼱<EFBFBD>궡嫄곕굹, Native WS API <20>뿬遺<EBBFAC>瑜<EFBFBD> <20>솗<EFBFBD>떎<EFBFBD>엳 泥댄겕<EB8C84>빐<EFBFBD>빞 <20>븿.
|
- **二쇱쓽**: 釉뚮씪<EB9AAE>슦<EFBFBD><EC8AA6><EFBFBD> <20>몴以<EBAAB4> WebSockets(W3C)<29>뒗 ping/pong <20>젣<EFBFBD>뼱 <20>봽<EFBFBD>젅<EFBFBD>엫<EFBFBD>쓣 JS濡<53> <20>끂異쒗븯吏<EBB8AF> <20>븡<EFBFBD>쓬. <20>뤃由ы븘/<2F>겕濡쒖뒪<EC9296>뵆<EFBFBD>옯<EFBFBD>뤌 WS <20>옒<EFBFBD>띁 <20>궗<EFBFBD>슜 <20>떆 <20>븯<EFBFBD>듃鍮꾪듃<EABEAA>뒗 諛섎뱶<EC848E>떆 JSON 硫붿꽭吏<EABDAD> <20>삎<EFBFBD>깭<EFBFBD>쓽 Application Layer Ping/Pong<6E>쑝濡<EC919D> <20><><EFBFBD><EFBFBD>뼱<EFBFBD>궡嫄곕굹, Native WS API <20>뿬遺<EBBFAC>瑜<EFBFBD> <20>솗<EFBFBD>떎<EFBFBD>엳 泥댄겕<EB8C84>빐<EFBFBD>빞 <20>븿.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-28] [step-probe] GetCascadeTrajectorySteps UTF-8 <20>뿉<EFBFBD>윭 臾댄븳 猷⑦봽
|
### [2026-03-28] [step-probe] GetCascadeTrajectorySteps UTF-8 <20>뿉<EFBFBD>윭 臾댄븳 猷⑦봽
|
||||||
|
|
||||||
- **利앹긽**: `guitar_score` <20>봽濡쒖젥<EC9296>듃<EFBFBD>뿉<EFBFBD>꽌 `[STEP-PROBE] error: ...invalid UTF-8` <20>뿉<EFBFBD>윭媛<EC9CAD> 5珥덈쭏<EB8D88>떎 諛섎났<EC848E>릺硫<EBA6BA> Discord <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD>떖<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
- **利앹긽**: `guitar_score` <20>봽濡쒖젥<EC9296>듃<EFBFBD>뿉<EFBFBD>꽌 `[STEP-PROBE] error: ...invalid UTF-8` <20>뿉<EFBFBD>윭媛<EC9CAD> 5珥덈쭏<EB8D88>떎 諛섎났<EC848E>릺硫<EBA6BA> Discord <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD>떖<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: AG LS <20>꽌踰꾩뿉<EABEA9>꽌 <20>듅<EFBFBD>젙 step<65>쓽 `CortexStepEphemeralMessage.content`<60>뿉 諛붿씠<EBB6BF>꼫由<EABCAB> <20>뜲<EFBFBD>씠<EFBFBD>꽣(<28>씠誘몄<E8AA98><EBAA84> <20>벑) <20>룷<EFBFBD>븿 <20>넂 proto UTF-8 吏곷젹<EAB3B7>솕 500 <20>뿉<EFBFBD>윭. `catch(e)` 釉붾줉<EBB6BE>뿉<EFBFBD>꽌 `stallProbed=true`瑜<> <20>꽕<EFBFBD>젙<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>븘 `!ctx.stallProbed` 議곌굔<EAB38C>씠 <20>빆<EFBFBD>긽 true <20>넂 5珥덈쭏<EB8D88>떎 <20>룞<EFBFBD>씪 <20>슂泥<EC8A82> 臾댄븳 <20>옱<EFBFBD>떆<EFBFBD>룄.
|
- **<2A>썝<EFBFBD>씤**: AG LS <20>꽌踰꾩뿉<EABEA9>꽌 <20>듅<EFBFBD>젙 step<65>쓽 `CortexStepEphemeralMessage.content`<60>뿉 諛붿씠<EBB6BF>꼫由<EABCAB> <20>뜲<EFBFBD>씠<EFBFBD>꽣(<28>씠誘몄<E8AA98><EBAA84> <20>벑) <20>룷<EFBFBD>븿 <20>넂 proto UTF-8 吏곷젹<EAB3B7>솕 500 <20>뿉<EFBFBD>윭. `catch(e)` 釉붾줉<EBB6BE>뿉<EFBFBD>꽌 `stallProbed=true`瑜<> <20>꽕<EFBFBD>젙<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>븘 `!ctx.stallProbed` 議곌굔<EAB38C>씠 <20>빆<EFBFBD>긽 true <20>넂 5珥덈쭏<EB8D88>떎 <20>룞<EFBFBD>씪 <20>슂泥<EC8A82> 臾댄븳 <20>옱<EFBFBD>떆<EFBFBD>룄.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.11): `catch` 釉붾줉<EBB6BE>뿉<EFBFBD>꽌 UTF-8 <20>뿉<EFBFBD>윭 媛먯<E5AA9B><EBA8AF> <20>떆 `stepOffset=currentCount-20`<60>쑝濡<EC919D> fallback <20>슂泥<EC8A82>. offset<65>룄 <20>떎<EFBFBD>뙣 <20>떆 `stallProbed=true` <20>꽕<EFBFBD>젙<EFBFBD>븯<EFBFBD>뿬 猷⑦봽 李⑤떒. `delta>0` <20>씠踰ㅽ듃 諛쒖깮 <20>떆 L433<33>뿉<EFBFBD>꽌 <20>옄<EFBFBD>룞 由ъ뀑.
|
- **<2A>빐寃<EBB990>** (v0.5.11): `catch` 釉붾줉<EBB6BE>뿉<EFBFBD>꽌 UTF-8 <20>뿉<EFBFBD>윭 媛먯<E5AA9B><EBA8AF> <20>떆 `stepOffset=currentCount-20`<60>쑝濡<EC919D> fallback <20>슂泥<EC8A82>. offset<65>룄 <20>떎<EFBFBD>뙣 <20>떆 `stallProbed=true` <20>꽕<EFBFBD>젙<EFBFBD>븯<EFBFBD>뿬 猷⑦봽 李⑤떒. `delta>0` <20>씠踰ㅽ듃 諛쒖깮 <20>떆 L433<33>뿉<EFBFBD>꽌 <20>옄<EFBFBD>룞 由ъ뀑.
|
||||||
|
|
||||||
- **二쇱쓽**: `stallProbed=true`<60>뒗 <20>쁺援<EC81BA> Lock<63>씠 <20>븘<EFBFBD>떂 <20><><EFBFBD> `delta>0` <20>떆 <20>옄<EFBFBD>룞 由ъ뀑. UTF-8 <20>뿉<EFBFBD>윭<EFBFBD>뒗 AG <20>꽌踰<EABD8C> 痢<> 臾몄젣(<28>씠誘몄<E8AA98><EBAA84>/諛붿씠<EBB6BF>꼫由<EABCAB> <20>뜲<EFBFBD>씠<EFBFBD>꽣媛<EABDA3> ephemeral message<67>뿉 <20>룷<EFBFBD>븿)<29>씠誘<EC94A0>濡<EFBFBD> Extension<6F>뿉<EFBFBD>꽌 graceful fallback留<6B> 泥섎━.
|
- **二쇱쓽**: `stallProbed=true`<60>뒗 <20>쁺援<EC81BA> Lock<63>씠 <20>븘<EFBFBD>떂 <20><><EFBFBD> `delta>0` <20>떆 <20>옄<EFBFBD>룞 由ъ뀑. UTF-8 <20>뿉<EFBFBD>윭<EFBFBD>뒗 AG <20>꽌踰<EABD8C> 痢<> 臾몄젣(<28>씠誘몄<E8AA98><EBAA84>/諛붿씠<EBB6BF>꼫由<EABCAB> <20>뜲<EFBFBD>씠<EFBFBD>꽣媛<EABDA3> ephemeral message<67>뿉 <20>룷<EFBFBD>븿)<29>씠誘<EC94A0>濡<EFBFBD> Extension<6F>뿉<EFBFBD>꽌 graceful fallback留<6B> 泥섎━.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-28] [approval-handler] stepIndex 誘명솗<EBAA85>젙 <20>떆 wrong-stepIndex RPC <20>궘鍮<EAB698>
|
### [2026-03-28] [approval-handler] stepIndex 誘명솗<EBAA85>젙 <20>떆 wrong-stepIndex RPC <20>궘鍮<EAB698>
|
||||||
|
|
||||||
- **利앹긽**: DOM observer 寃쎈줈濡<ECA488> `terminal_command` pending <20>깮<EFBFBD>꽦 <20>썑 Discord <20>듅<EFBFBD>씤 <20>떆 `HandleCascadeUserInteraction(stepIndex=0)` <20>넂 `"input not registered for step 0"` <20>넂 LS reconnect <20>넂 <20>옱<EFBFBD>떆<EFBFBD>룄 <20>넂 DOM click fallback<63>쑝濡<EC919D> <20><><EFBFBD><EFBFBD>븯. (wrong-LS<4C><53><EFBFBD> <20>룞<EFBFBD>씪<EFBFBD>븳 利앹긽<EC95B9>씠<EFBFBD>굹 <20>떎瑜<EB968E> <20>썝<EFBFBD>씤)
|
- **利앹긽**: DOM observer 寃쎈줈濡<ECA488> `terminal_command` pending <20>깮<EFBFBD>꽦 <20>썑 Discord <20>듅<EFBFBD>씤 <20>떆 `HandleCascadeUserInteraction(stepIndex=0)` <20>넂 `"input not registered for step 0"` <20>넂 LS reconnect <20>넂 <20>옱<EFBFBD>떆<EFBFBD>룄 <20>넂 DOM click fallback<63>쑝濡<EC919D> <20><><EFBFBD><EFBFBD>븯. (wrong-LS<4C><53><EFBFBD> <20>룞<EFBFBD>씪<EFBFBD>븳 利앹긽<EC95B9>씠<EFBFBD>굹 <20>떎瑜<EB968E> <20>썝<EFBFBD>씤)
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: `ctx.lastPendingStepIndex=-1` (step-probe媛<65> UTF-8 <20>뿉<EFBFBD>윭濡<EC9CAD> WAITING 誘멸컧吏<ECBBA7>)<29>엫<EFBFBD>뿉<EFBFBD>룄 `Math.max(0, -1)=0`<60>쑝濡<EC919D> clamp<6D>릺<EFBFBD>뼱 議댁옱<EB8C81>븯吏<EBB8AF> <20>븡<EFBFBD>뒗 step 0<>뿉 RPC <20>쟾<EFBFBD>넚.
|
- **<2A>썝<EFBFBD>씤**: `ctx.lastPendingStepIndex=-1` (step-probe媛<65> UTF-8 <20>뿉<EFBFBD>윭濡<EC9CAD> WAITING 誘멸컧吏<ECBBA7>)<29>엫<EFBFBD>뿉<EFBFBD>룄 `Math.max(0, -1)=0`<60>쑝濡<EC919D> clamp<6D>릺<EFBFBD>뼱 議댁옱<EB8C81>븯吏<EBB8AF> <20>븡<EFBFBD>뒗 step 0<>뿉 RPC <20>쟾<EFBFBD>넚.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.11): `effectiveStepIndex = stepIndex >= 0 ? stepIndex : (lastPendingStepIndex >= 0 ? lastPendingStepIndex : -1)`. `effectiveStepIndex < 0`<60>씠硫<EC94A0> RPC 釉붾줉 <20>쟾泥<EC9FBE> skip <20>넂 DOM click 吏곹뻾 (湲곗〈怨<E38088> <20>룞<EFBFBD>옉 <20>룞<EFBFBD>씪, LS reconnect <20>궘鍮<EAB698> <20>젣嫄<ECA0A3>).
|
- **<2A>빐寃<EBB990>** (v0.5.11): `effectiveStepIndex = stepIndex >= 0 ? stepIndex : (lastPendingStepIndex >= 0 ? lastPendingStepIndex : -1)`. `effectiveStepIndex < 0`<60>씠硫<EC94A0> RPC 釉붾줉 <20>쟾泥<EC9FBE> skip <20>넂 DOM click 吏곹뻾 (湲곗〈怨<E38088> <20>룞<EFBFBD>옉 <20>룞<EFBFBD>씪, LS reconnect <20>궘鍮<EAB698> <20>젣嫄<ECA0A3>).
|
||||||
|
|
||||||
- **二쇱쓽**: 湲곗〈 洹쒖튃 #14(`uint32`<60>뿉 <20>쓬<EFBFBD>닔 湲덉<E6B9B2><EB8D89>)<29><><EFBFBD> 異⑸룎泥섎읆 蹂댁씠<EB8C81>굹, `effectiveStepIndex=-1`<60>씪 <20>븣 RPC <20>옄泥대<EFA7A3><EB8C80> **<2A>쟾<EFBFBD>넚<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>쑝誘<EC919D>濡<EFBFBD>** <20>쐞諛<EC909E> <20>븘<EFBFBD>떂. RPC <20>쟾<EFBFBD>넚 <20>떆<EFBFBD>뿉<EFBFBD>뒗 <20>뿬<EFBFBD>쟾<EFBFBD>엳 <20>쑀<EFBFBD>슚<EFBFBD>븳 stepIndex留<78> <20>궗<EFBFBD>슜.
|
- **二쇱쓽**: 湲곗〈 洹쒖튃 #14(`uint32`<60>뿉 <20>쓬<EFBFBD>닔 湲덉<E6B9B2><EB8D89>)<29><><EFBFBD> 異⑸룎泥섎읆 蹂댁씠<EB8C81>굹, `effectiveStepIndex=-1`<60>씪 <20>븣 RPC <20>옄泥대<EFA7A3><EB8C80> **<2A>쟾<EFBFBD>넚<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>쑝誘<EC919D>濡<EFBFBD>** <20>쐞諛<EC909E> <20>븘<EFBFBD>떂. RPC <20>쟾<EFBFBD>넚 <20>떆<EFBFBD>뿉<EFBFBD>뒗 <20>뿬<EFBFBD>쟾<EFBFBD>엳 <20>쑀<EFBFBD>슚<EFBFBD>븳 stepIndex留<78> <20>궗<EFBFBD>슜.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-25] [Architecture] Discord Signal Drop & Extension Freezes
|
### [2026-03-25] [Architecture] Discord Signal Drop & Extension Freezes
|
||||||
|
|
||||||
- **利앹긽**: <20>옣<EFBFBD>떆媛<EB9686> <20>옄由щ퉬<D189><ED89AC><EFBFBD> <20>썑 蹂듦<E8B982><EB93A6> <20>떆 Discord濡<64> <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇媛<EC8387> <20>삤吏<EC82A4> <20>븡嫄곕굹 VS Code UI媛<49> 媛꾪뿉<EABEAA>쟻/吏<><EFA79E>냽<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> 硫덉땄(Freeze).
|
- **利앹긽**: <20>옣<EFBFBD>떆媛<EB9686> <20>옄由щ퉬<D189><ED89AC><EFBFBD> <20>썑 蹂듦<E8B982><EB93A6> <20>떆 Discord濡<64> <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇媛<EC8387> <20>삤吏<EC82A4> <20>븡嫄곕굹 VS Code UI媛<49> 媛꾪뿉<EABEAA>쟻/吏<><EFA79E>냽<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> 硫덉땄(Freeze).
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**:
|
- **<2A>썝<EFBFBD>씤**:
|
||||||
|
|
||||||
1. `ws.onerror` 諛쒖깮 <20>썑 `onclose` <20>늻<EFBFBD>씫 <20>떆 <20>옱<EFBFBD>뿰寃<EBBFB0> 肄쒕갚 <20>샇異쒖씠 <20>씠猷⑥뼱吏<EBBCB1>吏<EFBFBD> <20>븡<EFBFBD>븘 臾댄븳 <20><><EFBFBD>湲<EFBFBD> (<28>옣<EFBFBD>떆媛<EB9686> 留덈퉬)
|
1. `ws.onerror` 諛쒖깮 <20>썑 `onclose` <20>늻<EFBFBD>씫 <20>떆 <20>옱<EFBFBD>뿰寃<EBBFB0> 肄쒕갚 <20>샇異쒖씠 <20>씠猷⑥뼱吏<EBBCB1>吏<EFBFBD> <20>븡<EFBFBD>븘 臾댄븳 <20><><EFBFBD>湲<EFBFBD> (<28>옣<EFBFBD>떆媛<EB9686> 留덈퉬)
|
||||||
|
|
||||||
2. `ws-client` <20>옱<EFBFBD>뿰寃<EBBFB0> <20>떆 <20>늻<EFBFBD>쟻<EFBFBD>맂 200媛<30> <20>걧瑜<EAB1A7> <20>룞湲곗떇 burst <20>쟾<EFBFBD>넚<EFBFBD>븯<EFBFBD>뿬 Hub<75>쓽 <20>냽<EFBFBD>룄 <20>젣<EFBFBD>븳(60媛<30>/10珥<30>)<29>뿉 嫄몃젮 <20>솗<EFBFBD>젙 <20>쁺援<EC81BA> <20>궘<EFBFBD>젣<EFBFBD>맖
|
2. `ws-client` <20>옱<EFBFBD>뿰寃<EBBFB0> <20>떆 <20>늻<EFBFBD>쟻<EFBFBD>맂 200媛<30> <20>걧瑜<EAB1A7> <20>룞湲곗떇 burst <20>쟾<EFBFBD>넚<EFBFBD>븯<EFBFBD>뿬 Hub<75>쓽 <20>냽<EFBFBD>룄 <20>젣<EFBFBD>븳(60媛<30>/10珥<30>)<29>뿉 嫄몃젮 <20>솗<EFBFBD>젙 <20>쁺援<EC81BA> <20>궘<EFBFBD>젣<EFBFBD>맖
|
||||||
|
|
||||||
3. 濡쒖뺄 釉뚮┸吏<E294B8> `http-bridge.ts`<60>쓽 怨쇨굅 <20>쑀<EFBFBD>궛<EFBFBD>씤 `FALSE_POSITIVE_RE` <20>젙洹쒖떇<EC9296>씠 AI 怨좎쑀 踰꾪듉(Allow, Deny, Accept) 留덉<EFA78D><EB8D89> <20>븘<EFBFBD>꽣留곹븯<EAB3B9>뿬 Discord <20>쟾<EFBFBD>넚 <20>썝泥<EC8D9D> 李⑤떒
|
3. 濡쒖뺄 釉뚮┸吏<E294B8> `http-bridge.ts`<60>쓽 怨쇨굅 <20>쑀<EFBFBD>궛<EFBFBD>씤 `FALSE_POSITIVE_RE` <20>젙洹쒖떇<EC9296>씠 AI 怨좎쑀 踰꾪듉(Allow, Deny, Accept) 留덉<EFA78D><EB8D89> <20>븘<EFBFBD>꽣留곹븯<EAB3B9>뿬 Discord <20>쟾<EFBFBD>넚 <20>썝泥<EC8D9D> 李⑤떒
|
||||||
|
|
||||||
4. `step-probe.ts` <20>뤃留<EBA483> 猷⑦봽 <20>궡 <20>룞湲곗떇 <20>뙆<EFBFBD>씪 I/O <20>궗<EFBFBD>슜<EFBFBD>쑝濡<EC919D> <20>씤<EFBFBD>븳 <20>봽由ъ쫰
|
4. `step-probe.ts` <20>뤃留<EBA483> 猷⑦봽 <20>궡 <20>룞湲곗떇 <20>뙆<EFBFBD>씪 I/O <20>궗<EFBFBD>슜<EFBFBD>쑝濡<EC919D> <20>씤<EFBFBD>븳 <20>봽由ъ쫰
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.10): ws-client<6E>뿉 <20>븯<EFBFBD>뱶 <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐 諛<> 50ms Paced-flush <20>쟻<EFBFBD>슜, http-bridge<67>쓽 <20>젙洹쒖떇 湲곕뒫 <20>셿<EFBFBD>솕, step-probe 鍮꾨룞湲<EBA39E> I/O <20>쟾<EFBFBD>솚 泥댁젣 <20>쟻<EFBFBD>슜, observer-script<70>쓽 <20>븘<EFBFBD>꽣<EFBFBD>맂 <20>떊<EFBFBD>샇 臾댄븳 HTTP <20>뤃留<EBA483> 諛⑹뼱 肄붾뱶 諛섏쁺.
|
- **<2A>빐寃<EBB990>** (v0.5.10): ws-client<6E>뿉 <20>븯<EFBFBD>뱶 <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐 諛<> 50ms Paced-flush <20>쟻<EFBFBD>슜, http-bridge<67>쓽 <20>젙洹쒖떇 湲곕뒫 <20>셿<EFBFBD>솕, step-probe 鍮꾨룞湲<EBA39E> I/O <20>쟾<EFBFBD>솚 泥댁젣 <20>쟻<EFBFBD>슜, observer-script<70>쓽 <20>븘<EFBFBD>꽣<EFBFBD>맂 <20>떊<EFBFBD>샇 臾댄븳 HTTP <20>뤃留<EBA483> 諛⑹뼱 肄붾뱶 諛섏쁺.
|
||||||
|
|
||||||
- **二쇱쓽**: Extension <20>궡遺<EAB6A1> 濡쒖쭅 踰꾧렇<EABEA7><EBA087><EFBFBD><EFBFBD>쑝誘<EC919D>濡<EFBFBD> Hub(Python) 肄붾뱶<EBB6BE>뒗 嫄대뱶由ъ<E794B1><D18A> <20>븡<EFBFBD>쓬. Hub <20>냽<EFBFBD>룄 <20>젣<EFBFBD>븳<EFBFBD><EBB8B3><EFBFBD> <20>젙<EFBFBD>긽 諛⑹뼱 湲곗젣<EAB397>씠誘<EC94A0>濡<EFBFBD> <20>겢<EFBFBD>씪<EFBFBD>씠<EFBFBD>뼵<EFBFBD>듃 <20>떒<EFBFBD>쓽 Pacing<6E>씠 <20>삱諛붾Ⅸ 諛⑺뼢<E291BA>엫.
|
- **二쇱쓽**: Extension <20>궡遺<EAB6A1> 濡쒖쭅 踰꾧렇<EABEA7><EBA087><EFBFBD><EFBFBD>쑝誘<EC919D>濡<EFBFBD> Hub(Python) 肄붾뱶<EBB6BE>뒗 嫄대뱶由ъ<E794B1><D18A> <20>븡<EFBFBD>쓬. Hub <20>냽<EFBFBD>룄 <20>젣<EFBFBD>븳<EFBFBD><EBB8B3><EFBFBD> <20>젙<EFBFBD>긽 諛⑹뼱 湲곗젣<EAB397>씠誘<EC94A0>濡<EFBFBD> <20>겢<EFBFBD>씪<EFBFBD>씠<EFBFBD>뼵<EFBFBD>듃 <20>떒<EFBFBD>쓽 Pacing<6E>씠 <20>삱諛붾Ⅸ 諛⑺뼢<E291BA>엫.
|
||||||
|
|
||||||
### [2026-03-24] DOM Observer /trigger-click <20>젋<EFBFBD>뜑留<EB9C91> <20>닚<EFBFBD>꽌 <20>삤<EFBFBD>옉<EFBFBD>룞 諛<> False Positive <20>봽由ъ쭠
|
### [2026-03-24] DOM Observer /trigger-click <20>젋<EFBFBD>뜑留<EB9C91> <20>닚<EFBFBD>꽌 <20>삤<EFBFBD>옉<EFBFBD>룞 諛<> False Positive <20>봽由ъ쭠
|
||||||
|
|
||||||
- **利앹긽**: v0.5.9 <20>뙣移<EB99A3> <20>씠<EFBFBD>썑 肄붾뵫 <20>떆 Agent <20>솕硫댁씠 <20>걡<EFBFBD>엫<EFBFBD>뾾<EFBFBD>씠 <20>꽌紐<EABD8C> <20><><EFBFBD>湲<EFBFBD>(Pending) <20>긽<EFBFBD>깭濡<EAB9AD> 硫덉땄. <20>삉<EFBFBD>뒗 <20>뵒<EFBFBD>뒪肄붾뱶<EBB6BE>뿉<EFBFBD>꽌 `Approve` <20>떆 <20>뿉<EFBFBD>뵒<EFBFBD>꽣 <20>궡<EFBFBD>쓽 <20>뿁<EFBFBD>슧<EFBFBD>븳 `Run Test`(肄붾뱶 <20>젋利<ECA08B>)瑜<> <20>겢由<EAB2A2><E794B1>븿.
|
- **利앹긽**: v0.5.9 <20>뙣移<EB99A3> <20>씠<EFBFBD>썑 肄붾뵫 <20>떆 Agent <20>솕硫댁씠 <20>걡<EFBFBD>엫<EFBFBD>뾾<EFBFBD>씠 <20>꽌紐<EABD8C> <20><><EFBFBD>湲<EFBFBD>(Pending) <20>긽<EFBFBD>깭濡<EAB9AD> 硫덉땄. <20>삉<EFBFBD>뒗 <20>뵒<EFBFBD>뒪肄붾뱶<EBB6BE>뿉<EFBFBD>꽌 `Approve` <20>떆 <20>뿉<EFBFBD>뵒<EFBFBD>꽣 <20>궡<EFBFBD>쓽 <20>뿁<EFBFBD>슧<EFBFBD>븳 `Run Test`(肄붾뱶 <20>젋利<ECA08B>)瑜<> <20>겢由<EAB2A2><E794B1>븿.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: <20>뀓<EFBFBD>뒪<EFBFBD>듃<EFBFBD><EB9383><EFBFBD> <20>젙洹쒖떇(`/^Run/i` <20>벑)<29>뿉留<EBBF89> <20>쓽議댄븯<EB8C84>뿬 `querySelectorAll`<60>쓣 <20>닔<EFBFBD>뻾<EFBFBD>븷 寃쎌슦, DOM <20>듃由ъ뿉 <20>젋<EFBFBD>뜑留곷맂 <20>닔留롮<EFA78D><EBA1AE> VS Code <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> 肄붾뱶 <20>젋利<ECA08B> 踰꾪듉<EABEAA>쓣 Agent 踰꾪듉蹂대떎 癒쇱<E79992><EC87B1> 李얠븘踰꾨━<EABEA8>뒗 諛쒖깮 <20>쐞移<EC909E>(Context)<29>쓽 <20>븳怨꾩젏.
|
- **<2A>썝<EFBFBD>씤**: <20>뀓<EFBFBD>뒪<EFBFBD>듃<EFBFBD><EB9383><EFBFBD> <20>젙洹쒖떇(`/^Run/i` <20>벑)<29>뿉留<EBBF89> <20>쓽議댄븯<EB8C84>뿬 `querySelectorAll`<60>쓣 <20>닔<EFBFBD>뻾<EFBFBD>븷 寃쎌슦, DOM <20>듃由ъ뿉 <20>젋<EFBFBD>뜑留곷맂 <20>닔留롮<EFA78D><EBA1AE> VS Code <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> 肄붾뱶 <20>젋利<ECA08B> 踰꾪듉<EABEAA>쓣 Agent 踰꾪듉蹂대떎 癒쇱<E79992><EC87B1> 李얠븘踰꾨━<EABEA8>뒗 諛쒖깮 <20>쐞移<EC909E>(Context)<29>쓽 <20>븳怨꾩젏.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.10):
|
- **<2A>빐寃<EBB990>** (v0.5.10):
|
||||||
|
|
||||||
1. 媛먯<E5AA9B><EBA8AF>(Scan): `isVSCodeMainWindow` 諛<> <20>깘<EFBFBD>깋 <20>끂<EFBFBD>뱶 `isBodyRoot` <20>솗<EFBFBD>씤<EFBFBD>쓣 <20>넻<EFBFBD>빐, <20>뿉<EFBFBD>뵒<EFBFBD>꽣 蹂몃Ц <20>쁺<EFBFBD>뿭<EFBFBD>뿉<EFBFBD>꽌<EFBFBD>뒗 "Run", "Approve" 媛먯<E5AA9B><EBA8AF>瑜<EFBFBD> <20>썝泥<EC8D9D> <20>젣嫄<ECA0A3> (<28>삤吏<EC82A4> <20>뙣<EFBFBD>꼸 <20>궡濡<EAB6A1> <20>븳<EFBFBD>젙).
|
1. 媛먯<E5AA9B><EBA8AF>(Scan): `isVSCodeMainWindow` 諛<> <20>깘<EFBFBD>깋 <20>끂<EFBFBD>뱶 `isBodyRoot` <20>솗<EFBFBD>씤<EFBFBD>쓣 <20>넻<EFBFBD>빐, <20>뿉<EFBFBD>뵒<EFBFBD>꽣 蹂몃Ц <20>쁺<EFBFBD>뿭<EFBFBD>뿉<EFBFBD>꽌<EFBFBD>뒗 "Run", "Approve" 媛먯<E5AA9B><EBA8AF>瑜<EFBFBD> <20>썝泥<EC8D9D> <20>젣嫄<ECA0A3> (<28>삤吏<EC82A4> <20>뙣<EFBFBD>꼸 <20>궡濡<EAB6A1> <20>븳<EFBFBD>젙).
|
||||||
|
|
||||||
2. <20>겢由<EAB2A2>(Trigger-click): `deepFindButtons()` <20>궡<EFBFBD>뿉<EFBFBD>꽌 `findPanel()`(<28>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃 <20>뙣<EFBFBD>꼸) -> <20>븣由<EBB8A3> Toasts -> Document 蹂몃Ц <20>닚<EFBFBD>쑝濡<EC919D> <20>깘<EFBFBD>깋 **<2A>슦<EFBFBD>꽑<EFBFBD>닚<EFBFBD>쐞(Priority)**瑜<> 媛뺤젣 <20>쟻<EFBFBD>슜.
|
2. <20>겢由<EAB2A2>(Trigger-click): `deepFindButtons()` <20>궡<EFBFBD>뿉<EFBFBD>꽌 `findPanel()`(<28>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃 <20>뙣<EFBFBD>꼸) -> <20>븣由<EBB8A3> Toasts -> Document 蹂몃Ц <20>닚<EFBFBD>쑝濡<EC919D> <20>깘<EFBFBD>깋 **<2A>슦<EFBFBD>꽑<EFBFBD>닚<EFBFBD>쐞(Priority)**瑜<> 媛뺤젣 <20>쟻<EFBFBD>슜.
|
||||||
|
|
||||||
- **二쇱쓽**: 踰꾪듉 <20>씠踰ㅽ듃 <20>썑<EFBFBD>궧 <20>떆 <20>뀓<EFBFBD>뒪<EFBFBD>듃 留ㅼ묶<E385BC>뿉留<EBBF89> <20>쓽議댄븯吏<EBB8AF> 留먭퀬, 諛섎뱶<EC848E>떆 DOM <20>깘<EFBFBD>깋 <20>슦<EFBFBD>꽑<EFBFBD>닚<EFBFBD>쐞<EFBFBD><EC909E><EFBFBD> 而⑦뀓<E291A6>뒪<EFBFBD>듃 踰붿쐞瑜<EC909E> <20>븿猿<EBB8BF> <20>븘<EFBFBD>꽣留곹븯<EAB3B9>뿬 False Positive瑜<65> 李⑤떒<E291A4>븷 寃<>.
|
- **二쇱쓽**: 踰꾪듉 <20>씠踰ㅽ듃 <20>썑<EFBFBD>궧 <20>떆 <20>뀓<EFBFBD>뒪<EFBFBD>듃 留ㅼ묶<E385BC>뿉留<EBBF89> <20>쓽議댄븯吏<EBB8AF> 留먭퀬, 諛섎뱶<EC848E>떆 DOM <20>깘<EFBFBD>깋 <20>슦<EFBFBD>꽑<EFBFBD>닚<EFBFBD>쐞<EFBFBD><EC909E><EFBFBD> 而⑦뀓<E291A6>뒪<EFBFBD>듃 踰붿쐞瑜<EC909E> <20>븿猿<EBB8BF> <20>븘<EFBFBD>꽣留곹븯<EAB3B9>뿬 False Positive瑜<65> 李⑤떒<E291A4>븷 寃<>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-24] DOM Observer <20><><EFBFBD> VS Code Native UI Blind Spot
|
### [2026-03-24] DOM Observer <20><><EFBFBD> VS Code Native UI Blind Spot
|
||||||
|
|
||||||
- **利앹긽**: "Always Allow" 諛<> <20>씪諛<EC94AA> "Allow Alt+<2B>넻" 沅뚰븳 <20>븣由<EBB8A3> 踰꾪듉<EABEAA>씠 <20>뵒<EFBFBD>뒪肄붾뱶 沅뚰븳 <20>꽱<EFBFBD>떛<EFBFBD>뿉<EFBFBD>꽌 <20>셿<EFBFBD>쟾<EFBFBD>엳 <20>늻<EFBFBD>씫<EFBFBD>맖.
|
- **利앹긽**: "Always Allow" 諛<> <20>씪諛<EC94AA> "Allow Alt+<2B>넻" 沅뚰븳 <20>븣由<EBB8A3> 踰꾪듉<EABEAA>씠 <20>뵒<EFBFBD>뒪肄붾뱶 沅뚰븳 <20>꽱<EFBFBD>떛<EFBFBD>뿉<EFBFBD>꽌 <20>셿<EFBFBD>쟾<EFBFBD>엳 <20>늻<EFBFBD>씫<EFBFBD>맖.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: VS Code <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> <20>븣由<EBB8A3> 諛<> 梨꾪똿 <20>뙣<EFBFBD>꼸 <20>궡<EFBFBD>쓽 踰꾪듉<EABEAA><EB9389><EFBFBD> `<button>` <20>깭洹<EAB9AD> <20><><EFBFBD><EFBFBD>떊 `<a role="button">`, `<vscode-button>` <20>벑<EFBFBD>쓣 <20>궗<EFBFBD>슜<EFBFBD>븯<EFBFBD>뒗<EFBFBD>뜲, 湲곗〈 DOM scan 濡쒖쭅<EC9296>씠 `querySelectorAll('button')`<60>쑝濡<EC919D> <20>븯<EFBFBD>뱶肄붾뵫<EBB6BE>릺<EFBFBD>뼱 <20>끂<EFBFBD>뱶瑜<EBB1B6> <20>븘<EFBFBD>삁 李얠<EFA7A1><EC96A0> 紐삵븿. (異붽<E795B0><EBB6BD>濡<EFBFBD> Always Allow <20>젙洹쒖떇 <20>늻<EFBFBD>씫)
|
- **<2A>썝<EFBFBD>씤**: VS Code <20>꽕<EFBFBD>씠<EFBFBD>떚釉<EB969A> <20>븣由<EBB8A3> 諛<> 梨꾪똿 <20>뙣<EFBFBD>꼸 <20>궡<EFBFBD>쓽 踰꾪듉<EABEAA><EB9389><EFBFBD> `<button>` <20>깭洹<EAB9AD> <20><><EFBFBD><EFBFBD>떊 `<a role="button">`, `<vscode-button>` <20>벑<EFBFBD>쓣 <20>궗<EFBFBD>슜<EFBFBD>븯<EFBFBD>뒗<EFBFBD>뜲, 湲곗〈 DOM scan 濡쒖쭅<EC9296>씠 `querySelectorAll('button')`<60>쑝濡<EC919D> <20>븯<EFBFBD>뱶肄붾뵫<EBB6BE>릺<EFBFBD>뼱 <20>끂<EFBFBD>뱶瑜<EBB1B6> <20>븘<EFBFBD>삁 李얠<EFA7A1><EC96A0> 紐삵븿. (異붽<E795B0><EBB6BD>濡<EFBFBD> Always Allow <20>젙洹쒖떇 <20>늻<EFBFBD>씫)
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.9): DOM scan, 由ъ뒯 <20>썒 <20>벑 紐⑤뱺 <20>깘<EFBFBD>깋 濡쒖쭅 <20><><EFBFBD><EFBFBD>젆<EFBFBD>꽣瑜<EABDA3> `button, [role="button"], vscode-button, .monaco-text-button` <20>쑝濡<EC919D> <20>쟾硫<EC9FBE> 媛쒗렪. <20>젙洹쒖떇<EC9296>쓣 `/^(?:Always )?Allow/i`濡<> <20>닔<EFBFBD>젙.
|
- **<2A>빐寃<EBB990>** (v0.5.9): DOM scan, 由ъ뒯 <20>썒 <20>벑 紐⑤뱺 <20>깘<EFBFBD>깋 濡쒖쭅 <20><><EFBFBD><EFBFBD>젆<EFBFBD>꽣瑜<EABDA3> `button, [role="button"], vscode-button, .monaco-text-button` <20>쑝濡<EC919D> <20>쟾硫<EC9FBE> 媛쒗렪. <20>젙洹쒖떇<EC9296>쓣 `/^(?:Always )?Allow/i`濡<> <20>닔<EFBFBD>젙.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-24] Python Hub <20><><EFBFBD> 醫<>鍮<EFBFBD> 而ㅻ꽖<E385BB>뀡 諛<> UI <20>봽由ъ쭠
|
### [2026-03-24] Python Hub <20><><EFBFBD> 醫<>鍮<EFBFBD> 而ㅻ꽖<E385BB>뀡 諛<> UI <20>봽由ъ쭠
|
||||||
|
|
||||||
- **利앹긽**: `npm run` 紐낅졊<EB8285>씠 `<EFBFBD>떎<EFBFBD>뻾 <20>젙梨<ECA099>` 愿<><E684BF>젴 <20>삤瑜섎줈 <20>떎<EFBFBD>뙣
|
- **利앹긽**: `npm run` 紐낅졊<EB8285>씠 `<EFBFBD>떎<EFBFBD>뻾 <20>젙梨<ECA099>` 愿<><E684BF>젴 <20>삤瑜섎줈 <20>떎<EFBFBD>뙣
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: PowerShell <20>뒪<EFBFBD>겕由쏀듃 <20>떎<EFBFBD>뻾 <20>젙梨낆씠 <20>젣<EFBFBD>븳<EFBFBD>쟻
|
- **<2A>썝<EFBFBD>씤**: PowerShell <20>뒪<EFBFBD>겕由쏀듃 <20>떎<EFBFBD>뻾 <20>젙梨낆씠 <20>젣<EFBFBD>븳<EFBFBD>쟻
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: `cmd /c npm run dev` <20>삎<EFBFBD>떇<EFBFBD>쑝濡<EC919D> cmd瑜<64> <20>넻<EFBFBD>빐 <20>떎<EFBFBD>뻾
|
- **<2A>빐寃<EBB990>**: `cmd /c npm run dev` <20>삎<EFBFBD>떇<EFBFBD>쑝濡<EC919D> cmd瑜<64> <20>넻<EFBFBD>빐 <20>떎<EFBFBD>뻾
|
||||||
|
|
||||||
- **二쇱쓽**: npm 愿<><E684BF>젴 紐낅졊<EB8285><ECA18A><EFBFBD> <20>빆<EFBFBD>긽 `cmd /c` <20>젒<EFBFBD>몢<EFBFBD>뼱 <20>궗<EFBFBD>슜 沅뚯옣
|
- **二쇱쓽**: npm 愿<><E684BF>젴 紐낅졊<EB8285><ECA18A><EFBFBD> <20>빆<EFBFBD>긽 `cmd /c` <20>젒<EFBFBD>몢<EFBFBD>뼱 <20>궗<EFBFBD>슜 沅뚯옣
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-08] PowerShell curl <20><><EFBFBD> Invoke-WebRequest 異⑸룎
|
### [2026-03-08] PowerShell curl <20><><EFBFBD> Invoke-WebRequest 異⑸룎
|
||||||
|
|
||||||
- **利앹긽**: `curl` 紐낅졊<EB8285>씠 <20>삁<EFBFBD>긽怨<EAB8BD> <20>떎瑜<EB968E> <20>쓳<EFBFBD>떟 <20>삎<EFBFBD>떇<EFBFBD>쓣 諛섑솚
|
- **利앹긽**: `curl` 紐낅졊<EB8285>씠 <20>삁<EFBFBD>긽怨<EAB8BD> <20>떎瑜<EB968E> <20>쓳<EFBFBD>떟 <20>삎<EFBFBD>떇<EFBFBD>쓣 諛섑솚
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: PowerShell<6C>뿉<EFBFBD>꽌 `curl`<60><><EFBFBD> `Invoke-WebRequest`<60>쓽 蹂꾩묶
|
- **<2A>썝<EFBFBD>씤**: PowerShell<6C>뿉<EFBFBD>꽌 `curl`<60><><EFBFBD> `Invoke-WebRequest`<60>쓽 蹂꾩묶
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: **`curl.exe`**瑜<> 紐낆떆<EB8286>쟻<EFBFBD>쑝濡<EC919D> <20>궗<EFBFBD>슜
|
- **<2A>빐寃<EBB990>**: **`curl.exe`**瑜<> 紐낆떆<EB8286>쟻<EFBFBD>쑝濡<EC919D> <20>궗<EFBFBD>슜
|
||||||
|
|
||||||
- **二쇱쓽**: HTTP 愿<><E684BF>젴 紐⑤뱺 紐낅졊<EB8285>뿉<EFBFBD>꽌 `curl.exe` <20>궗<EFBFBD>슜 <20>븘<EFBFBD>닔
|
- **二쇱쓽**: HTTP 愿<><E684BF>젴 紐⑤뱺 紐낅졊<EB8285>뿉<EFBFBD>꽌 `curl.exe` <20>궗<EFBFBD>슜 <20>븘<EFBFBD>닔
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 誘명빐寃<EBB990> <20>씠<EFBFBD>뒋
|
## 誘명빐寃<EBB990> <20>씠<EFBFBD>뒋
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-23/24] <20>룊<EFBFBD>깮 吏<><EFA79E>냽<EFBFBD>릺<EFBFBD>뒗 WebSocket 醫<>鍮<EFBFBD> 而ㅻ꽖<E385BB>뀡 諛<> False Positive 媛뺤젣 <20>뿰寃<EBBFB0> <20>걡源<EAB1A1> (v0.5.5 <20>넂 0.5.8)
|
### [2026-03-23/24] <20>룊<EFBFBD>깮 吏<><EFA79E>냽<EFBFBD>릺<EFBFBD>뒗 WebSocket 醫<>鍮<EFBFBD> 而ㅻ꽖<E385BB>뀡 諛<> False Positive 媛뺤젣 <20>뿰寃<EBBFB0> <20>걡源<EAB1A1> (v0.5.5 <20>넂 0.5.8)
|
||||||
|
|
||||||
- **利앹긽**:
|
- **利앹긽**:
|
||||||
|
|
||||||
1. (v0.5.5) <20>젅<EFBFBD>쟾 紐⑤뱶 蹂듦뎄 <20>떆 <20>떎<EFBFBD>뿰寃곗씠 <20>걡<EFBFBD>뼱議뚯쓬<EB9AAF>뿉<EFBFBD>룄 <20>솗<EFBFBD>옣<EFBFBD>씠 <20>씠瑜<EC94A0> <20>씤吏<EC94A4><EFA79E>븯吏<EBB8AF> 紐삵븯<EC82B5>뒗 醫<>鍮<EFBFBD>(Half-open) <20>냼耳<EB83BC> 諛쒖깮.
|
1. (v0.5.5) <20>젅<EFBFBD>쟾 紐⑤뱶 蹂듦뎄 <20>떆 <20>떎<EFBFBD>뿰寃곗씠 <20>걡<EFBFBD>뼱議뚯쓬<EB9AAF>뿉<EFBFBD>룄 <20>솗<EFBFBD>옣<EFBFBD>씠 <20>씠瑜<EC94A0> <20>씤吏<EC94A4><EFA79E>븯吏<EBB8AF> 紐삵븯<EC82B5>뒗 醫<>鍮<EFBFBD>(Half-open) <20>냼耳<EB83BC> 諛쒖깮.
|
||||||
|
|
||||||
2. (v0.5.6) 醫<>鍮<EFBFBD> <20>냼耳볦쓣 <20>옟湲<EC989F> <20>쐞<EFBFBD>빐 10珥<30> <20><><EFBFBD><EFBFBD>씠癒<EC94A0>(`pongTimeoutTimer`)瑜<> <20>꽔<EFBFBD>뿀<EFBFBD>쑝<EFBFBD>굹, VS Code<64>쓽 臾닿굅<EB8BBF>슫 <20>뙆<EFBFBD>씪 寃<><E5AF83>깋 <20>떆 Event Loop媛<70> 釉붾줈<EBB6BE>궧<EFBFBD>릺硫<EBA6BA> 硫<>姨≫븳 <20>뿰寃곗씤<EAB397>뜲<EFBFBD>룄 <20>뿀<EFBFBD>쐞 <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐(False Positive) <20>뙋<EFBFBD>젙<EFBFBD>쑝濡<EC919D> <20>뿰寃곗쓣 媛뺤젣 醫낅즺<EB8285>븿. <20>씠濡<EC94A0> <20>씤<EFBFBD>빐 <20>늻<EFBFBD>쟻<EFBFBD>맂 <20>옱<EFBFBD>뿰寃<EBBFB0> <20>뵜<EFBFBD>젅<EFBFBD>씠(Exponential Backoff)媛<> 60珥덇퉴吏<ED89B4> <20>뒛<EFBFBD>뼱<EFBFBD>굹硫댁꽌 <20>솗<EFBFBD>옣<EFBFBD>씠 <20>떖媛곹븯寃<EBB8AF> 硫덉땄(Freeze).
|
2. (v0.5.6) 醫<>鍮<EFBFBD> <20>냼耳볦쓣 <20>옟湲<EC989F> <20>쐞<EFBFBD>빐 10珥<30> <20><><EFBFBD><EFBFBD>씠癒<EC94A0>(`pongTimeoutTimer`)瑜<> <20>꽔<EFBFBD>뿀<EFBFBD>쑝<EFBFBD>굹, VS Code<64>쓽 臾닿굅<EB8BBF>슫 <20>뙆<EFBFBD>씪 寃<><E5AF83>깋 <20>떆 Event Loop媛<70> 釉붾줈<EBB6BE>궧<EFBFBD>릺硫<EBA6BA> 硫<>姨≫븳 <20>뿰寃곗씤<EAB397>뜲<EFBFBD>룄 <20>뿀<EFBFBD>쐞 <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐(False Positive) <20>뙋<EFBFBD>젙<EFBFBD>쑝濡<EC919D> <20>뿰寃곗쓣 媛뺤젣 醫낅즺<EB8285>븿. <20>씠濡<EC94A0> <20>씤<EFBFBD>빐 <20>늻<EFBFBD>쟻<EFBFBD>맂 <20>옱<EFBFBD>뿰寃<EBBFB0> <20>뵜<EFBFBD>젅<EFBFBD>씠(Exponential Backoff)媛<> 60珥덇퉴吏<ED89B4> <20>뒛<EFBFBD>뼱<EFBFBD>굹硫댁꽌 <20>솗<EFBFBD>옣<EFBFBD>씠 <20>떖媛곹븯寃<EBB8AF> 硫덉땄(Freeze).
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: Node.js `ws` <20>씪<EFBFBD>씠釉뚮윭由ъ쓽 `ws.ping()`<60><><EFBFBD> 鍮꾨룞湲<EBA39E> I/O <20>꽕<EFBFBD>듃<EFBFBD>썙<EFBFBD>겕 <20>걧瑜<EAB1A7> <20><><EFBFBD>吏<EFBFBD>留<EFBFBD>, `setTimeout(..., 10000)` <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐<EFBFBD><EC8D90><EFBFBD> Event Loop 釉붾줈<EBB6BE>궧 <20>빐<EFBFBD>젣 吏곹썑 怨㏓컮濡<ECBBAE> 留뚮즺<EB9AAE>릺<EFBFBD>뼱 踰꾨┝. <20>뵲<EFBFBD>씪<EFBFBD>꽌 <20>꽕<EFBFBD>듃<EFBFBD>썙<EFBFBD>겕 I/O <20>쓳<EFBFBD>떟(pong)蹂대떎 濡쒖뺄 <20><><EFBFBD><EFBFBD>씠癒멸<E79992><EBA9B8> 癒쇱<E79992><EC87B1> <20>꽣<EFBFBD>졇<EFBFBD>꽌 <20>젙<EFBFBD>긽<EFBFBD>쟻<EFBFBD>씤 <20>냼耳볦쓣 二쎌엫.
|
- **<2A>썝<EFBFBD>씤**: Node.js `ws` <20>씪<EFBFBD>씠釉뚮윭由ъ쓽 `ws.ping()`<60><><EFBFBD> 鍮꾨룞湲<EBA39E> I/O <20>꽕<EFBFBD>듃<EFBFBD>썙<EFBFBD>겕 <20>걧瑜<EAB1A7> <20><><EFBFBD>吏<EFBFBD>留<EFBFBD>, `setTimeout(..., 10000)` <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐<EFBFBD><EC8D90><EFBFBD> Event Loop 釉붾줈<EBB6BE>궧 <20>빐<EFBFBD>젣 吏곹썑 怨㏓컮濡<ECBBAE> 留뚮즺<EB9AAE>릺<EFBFBD>뼱 踰꾨┝. <20>뵲<EFBFBD>씪<EFBFBD>꽌 <20>꽕<EFBFBD>듃<EFBFBD>썙<EFBFBD>겕 I/O <20>쓳<EFBFBD>떟(pong)蹂대떎 濡쒖뺄 <20><><EFBFBD><EFBFBD>씠癒멸<E79992><EBA9B8> 癒쇱<E79992><EC87B1> <20>꽣<EFBFBD>졇<EFBFBD>꽌 <20>젙<EFBFBD>긽<EFBFBD>쟻<EFBFBD>씤 <20>냼耳볦쓣 二쎌엫.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.8 <20>셿<EFBFBD>꽦):
|
- **<2A>빐寃<EBB990>** (v0.5.8 <20>셿<EFBFBD>꽦):
|
||||||
|
|
||||||
- <20>쐞<EFBFBD>뿕<EFBFBD>븳 `setTimeout` 諛⑹떇 <20>룓湲<EBA393>.
|
- <20>쐞<EFBFBD>뿕<EFBFBD>븳 `setTimeout` 諛⑹떇 <20>룓湲<EBA393>.
|
||||||
|
|
||||||
- 湲곗〈<EAB397>쓽 25珥<35> 二쇨린 `setInterval` <20>븯<EFBFBD>듃鍮꾪듃 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉 `Date.now() - lastPongTime > 60000` (60珥<30> 珥덇낵 <20>떆 <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐) 寃<>利<EFBFBD> 濡쒖쭅<EC9296>쓣 <20>룄<EFBFBD>엯.
|
- 湲곗〈<EAB397>쓽 25珥<35> 二쇨린 `setInterval` <20>븯<EFBFBD>듃鍮꾪듃 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉 `Date.now() - lastPongTime > 60000` (60珥<30> 珥덇낵 <20>떆 <20><><EFBFBD><EFBFBD>엫<EFBFBD>븘<EFBFBD>썐) 寃<>利<EFBFBD> 濡쒖쭅<EC9296>쓣 <20>룄<EFBFBD>엯.
|
||||||
|
|
||||||
- 留뚯빟 Event Loop媛<70> <20>닔<EFBFBD>떗 珥<> 諛<>由щ뜑<D189>씪<EFBFBD>룄, 釉붾줈<EBB6BE>궧 <20>빐<EFBFBD>젣 <20>썑 <20>걧<EFBFBD>맂 I/O <20>씠踰ㅽ듃(`pong`)媛<> `setInterval` <20><><EFBFBD><EFBFBD>씠癒<EC94A0> 肄쒕갚 <20>씠<EFBFBD>쟾<EFBFBD>뿉 癒쇱<E79992><EC87B1> 泥섎━<EC848E>릺嫄곕굹(Node.js Phase 洹쒖튃), <20>쟻<EFBFBD>뼱<EFBFBD>룄 60珥덈씪<EB8D88>뒗 踰꾪띁 <20>뜒遺꾩뿉 **False Positive 媛<><E5AA9B>뒫<EFBFBD>꽦<EFBFBD>쓣 <20>썝泥<EC8D9D> 李⑤떒**<2A>븿怨<EBB8BF> <20>룞<EFBFBD>떆<EFBFBD>뿉 醫<>鍮<EFBFBD> <20>냼耳볦쓣 <20>븞<EFBFBD>젙<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> <20>젣嫄고븿.
|
- 留뚯빟 Event Loop媛<70> <20>닔<EFBFBD>떗 珥<> 諛<>由щ뜑<D189>씪<EFBFBD>룄, 釉붾줈<EBB6BE>궧 <20>빐<EFBFBD>젣 <20>썑 <20>걧<EFBFBD>맂 I/O <20>씠踰ㅽ듃(`pong`)媛<> `setInterval` <20><><EFBFBD><EFBFBD>씠癒<EC94A0> 肄쒕갚 <20>씠<EFBFBD>쟾<EFBFBD>뿉 癒쇱<E79992><EC87B1> 泥섎━<EC848E>릺嫄곕굹(Node.js Phase 洹쒖튃), <20>쟻<EFBFBD>뼱<EFBFBD>룄 60珥덈씪<EB8D88>뒗 踰꾪띁 <20>뜒遺꾩뿉 **False Positive 媛<><E5AA9B>뒫<EFBFBD>꽦<EFBFBD>쓣 <20>썝泥<EC8D9D> 李⑤떒**<2A>븿怨<EBB8BF> <20>룞<EFBFBD>떆<EFBFBD>뿉 醫<>鍮<EFBFBD> <20>냼耳볦쓣 <20>븞<EFBFBD>젙<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> <20>젣嫄고븿.
|
||||||
|
|
||||||
- **二쇱쓽**: Node.js<6A>쓽 <20>떒<EFBFBD>씪 <20>뒪<EFBFBD>젅<EFBFBD>뱶 Event Loop <20>솚寃<EC869A>(<28>듅<EFBFBD>엳 臾닿굅<EB8BBF>슫 <20>룞湲<EBA39E> <20>옉<EFBFBD>뾽<EFBFBD>씠 <20>옦<EFBFBD><EC98A6><EFBFBD> VS Code Extension)<29>뿉<EFBFBD>꽌 <20>꽕<EFBFBD>듃<EFBFBD>썙<EFBFBD>겕 I/O瑜<4F> 濡쒖뺄 `setTimeout`怨<> 寃쎌<(Race)<29>떆<EFBFBD>궎<EFBFBD>뒗 <20>꽕怨꾨뒗 <20>븘<EFBFBD>뿰<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> False Positive瑜<65> <20>궠<EFBFBD>쓬. Timestamp(`Date.now()`) 湲곕컲 媛꾧꺽 寃<>利<EFBFBD>(Interval check)<29>씠 <20>썾<EFBFBD>뵮 <20>븞<EFBFBD>쟾<EFBFBD>븿.
|
- **二쇱쓽**: Node.js<6A>쓽 <20>떒<EFBFBD>씪 <20>뒪<EFBFBD>젅<EFBFBD>뱶 Event Loop <20>솚寃<EC869A>(<28>듅<EFBFBD>엳 臾닿굅<EB8BBF>슫 <20>룞湲<EBA39E> <20>옉<EFBFBD>뾽<EFBFBD>씠 <20>옦<EFBFBD><EC98A6><EFBFBD> VS Code Extension)<29>뿉<EFBFBD>꽌 <20>꽕<EFBFBD>듃<EFBFBD>썙<EFBFBD>겕 I/O瑜<4F> 濡쒖뺄 `setTimeout`怨<> 寃쎌<(Race)<29>떆<EFBFBD>궎<EFBFBD>뒗 <20>꽕怨꾨뒗 <20>븘<EFBFBD>뿰<EFBFBD>쟻<EFBFBD>쑝濡<EC919D> False Positive瑜<65> <20>궠<EFBFBD>쓬. Timestamp(`Date.now()`) 湲곕컲 媛꾧꺽 寃<>利<EFBFBD>(Interval check)<29>씠 <20>썾<EFBFBD>뵮 <20>븞<EFBFBD>쟾<EFBFBD>븿.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-11] rejectAgentStep / !stop <20><><EFBFBD> AG 誘몃벑濡<EBB291> 而ㅻ㎤<E385BB>뱶 + <20>젋<EFBFBD>뜑<EFBFBD>윭 <20>쟾<EFBFBD>슜 <20>븿<EFBFBD>닔 + <20>뒪<EFBFBD>뀒<EFBFBD>씪 <20>봽由щ<E794B1>명떚釉<EB969A>
|
### [2026-03-11] rejectAgentStep / !stop <20><><EFBFBD> AG 誘몃벑濡<EBB291> 而ㅻ㎤<E385BB>뱶 + <20>젋<EFBFBD>뜑<EFBFBD>윭 <20>쟾<EFBFBD>슜 <20>븿<EFBFBD>닔 + <20>뒪<EFBFBD>뀒<EFBFBD>씪 <20>봽由щ<E794B1>명떚釉<EB969A>
|
||||||
|
|
||||||
- **利앹긽**: `!stop` 紐낅졊<EB8285>씠 AI瑜<49> 硫덉텛吏<ED859B> 紐삵븿. 濡쒓렇: "No active cascade" / "no session tracked yet"
|
- **利앹긽**: `!stop` 紐낅졊<EB8285>씠 AI瑜<49> 硫덉텛吏<ED859B> 紐삵븿. 濡쒓렇: "No active cascade" / "no session tracked yet"
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: (1) `antigravity.agent.rejectAgentStep`<60><><EFBFBD> AG 誘몃벑濡<EBB291> 而ㅻ㎤<E385BB>뱶. (2) <20><><EFBFBD>泥댄븳 `getActiveCascadeId()`<60>뒗 **<2A>젋<EFBFBD>뜑<EFBFBD>윭(DOM) <20>쟾<EFBFBD>슜 <20>븿<EFBFBD>닔** <20><><EFBFBD> Extension host<73>뿉<EFBFBD>꽌 <20>빆<EFBFBD>긽 `undefined` 諛섑솚. (3) **v0.4.5 <20>닔<EFBFBD>젙<EFBFBD>룄 <20>떎<EFBFBD>뙣**: `extension.ts`<60>쓽 `getActiveSessionId: () => activeSessionId`媛<> module-level <20>뒪<EFBFBD>듃留<EB9383> <20>봽由щ<E794B1>명떚釉뚮<E98789><EB9AAE> 李몄“ <20><><EFBFBD> step-probe媛<65> `ctx.activeSessionId`瑜<> <20>뾽<EFBFBD>뜲<EFBFBD>씠<EFBFBD>듃<EFBFBD>빐<EFBFBD>룄 extension.ts<74>쓽 蹂<><E8B982>닔<EFBFBD>뒗 遺덈<E981BA><EB8D88> (<28>봽由щ<E794B1>명떚釉<EB969A> 蹂듭궗)
|
- **<2A>썝<EFBFBD>씤**: (1) `antigravity.agent.rejectAgentStep`<60><><EFBFBD> AG 誘몃벑濡<EBB291> 而ㅻ㎤<E385BB>뱶. (2) <20><><EFBFBD>泥댄븳 `getActiveCascadeId()`<60>뒗 **<2A>젋<EFBFBD>뜑<EFBFBD>윭(DOM) <20>쟾<EFBFBD>슜 <20>븿<EFBFBD>닔** <20><><EFBFBD> Extension host<73>뿉<EFBFBD>꽌 <20>빆<EFBFBD>긽 `undefined` 諛섑솚. (3) **v0.4.5 <20>닔<EFBFBD>젙<EFBFBD>룄 <20>떎<EFBFBD>뙣**: `extension.ts`<60>쓽 `getActiveSessionId: () => activeSessionId`媛<> module-level <20>뒪<EFBFBD>듃留<EB9383> <20>봽由щ<E794B1>명떚釉뚮<E98789><EB9AAE> 李몄“ <20><><EFBFBD> step-probe媛<65> `ctx.activeSessionId`瑜<> <20>뾽<EFBFBD>뜲<EFBFBD>씠<EFBFBD>듃<EFBFBD>빐<EFBFBD>룄 extension.ts<74>쓽 蹂<><E8B982>닔<EFBFBD>뒗 遺덈<E981BA><EB8D88> (<28>봽由щ<E794B1>명떚釉<EB969A> 蹂듭궗)
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (2026-03-18 v0.4.6): `step-probe.ts`<60>뿉<EFBFBD>꽌 `getActiveSessionId()` getter <20>븿<EFBFBD>닔 export <20>넂 extension.ts closures<65>뿉<EFBFBD>꽌 `getStepProbeSessionId()` <20>샇異<EC8387>. <20>씠<EFBFBD>젣 step-probe<62>쓽 live `ctx.activeSessionId`瑜<> 吏곸젒 <20>씫<EFBFBD>쓬 (`ab0c116`)
|
- **<2A>빐寃<EBB990>** (2026-03-18 v0.4.6): `step-probe.ts`<60>뿉<EFBFBD>꽌 `getActiveSessionId()` getter <20>븿<EFBFBD>닔 export <20>넂 extension.ts closures<65>뿉<EFBFBD>꽌 `getStepProbeSessionId()` <20>샇異<EC8387>. <20>씠<EFBFBD>젣 step-probe<62>쓽 live `ctx.activeSessionId`瑜<> 吏곸젒 <20>씫<EFBFBD>쓬 (`ab0c116`)
|
||||||
|
|
||||||
- **二쇱쓽**: JS<4A>뿉<EFBFBD>꽌 **string/number<65>뒗 <20>봽由щ<E794B1>명떚釉뚮씪 李몄“ <20>쟾<EFBFBD>떖 遺덇<E981BA><EB8D87>** <20><><EFBFBD> 媛앹껜 <20>냽<EFBFBD>꽦<EFBFBD>쓣 怨듭쑀<EB93AD>븯<EFBFBD>젮硫<ECA0AE> getter <20>븿<EFBFBD>닔<EFBFBD>굹 媛앹껜 <20>옒<EFBFBD>띁 <20>궗<EFBFBD>슜 <20>븘<EFBFBD>닔
|
- **二쇱쓽**: JS<4A>뿉<EFBFBD>꽌 **string/number<65>뒗 <20>봽由щ<E794B1>명떚釉뚮씪 李몄“ <20>쟾<EFBFBD>떖 遺덇<E981BA><EB8D87>** <20><><EFBFBD> 媛앹껜 <20>냽<EFBFBD>꽦<EFBFBD>쓣 怨듭쑀<EB93AD>븯<EFBFBD>젮硫<ECA0AE> getter <20>븿<EFBFBD>닔<EFBFBD>굹 媛앹껜 <20>옒<EFBFBD>띁 <20>궗<EFBFBD>슜 <20>븘<EFBFBD>닔
|
||||||
|
|
||||||
- **Vikunja**: #411, #410
|
- **Vikunja**: #411, #410
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-19] browser_subagent Allow <20><><EFBFBD> <20>옒紐삳맂 RPC payload
|
### [2026-03-19] browser_subagent Allow <20><><EFBFBD> <20>옒紐삳맂 RPC payload
|
||||||
|
|
||||||
- **利앹긽**: <20>꽌釉<EABD8C> <20>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃 "execute JavaScript on localhost" Allow 踰꾪듉<EABEAA>씠 <20>옄<EFBFBD>룞 <20>듅<EFBFBD>씤<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬
|
- **利앹긽**: <20>꽌釉<EABD8C> <20>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃 "execute JavaScript on localhost" Allow 踰꾪듉<EABEAA>씠 <20>옄<EFBFBD>룞 <20>듅<EFBFBD>씤<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: `step-probe.ts`<60>뿉<EFBFBD>꽌 `browser_subagent` toolName<6D>씠 step_type 遺꾨쪟 <20>뾾<EFBFBD>씠 raw toolName<6D>쑝濡<EC919D> <20>쟾<EFBFBD>떖 <20>넂 `approval-handler.ts`<60>뿉<EFBFBD>꽌 `runExtensionCode` 留ㅽ븨<E385BD>뿉 <20>룷<EFBFBD>븿<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>븘 default `runCommand` RPC payload <20>궗<EFBFBD>슜 <20>넂 AG媛<47> <20>옒紐삳맂 interaction type<70>쑝濡<EC919D> 臾댁떆
|
- **<2A>썝<EFBFBD>씤**: `step-probe.ts`<60>뿉<EFBFBD>꽌 `browser_subagent` toolName<6D>씠 step_type 遺꾨쪟 <20>뾾<EFBFBD>씠 raw toolName<6D>쑝濡<EC919D> <20>쟾<EFBFBD>떖 <20>넂 `approval-handler.ts`<60>뿉<EFBFBD>꽌 `runExtensionCode` 留ㅽ븨<E385BD>뿉 <20>룷<EFBFBD>븿<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>븘 default `runCommand` RPC payload <20>궗<EFBFBD>슜 <20>넂 AG媛<47> <20>옒紐삳맂 interaction type<70>쑝濡<EC919D> 臾댁떆
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.1): `approval-handler.ts` L384<38>뿉 `browser_subagent` 異붽<E795B0><EBB6BD>, `step-probe.ts` L481/L549<34>뿉 `browser_subagent`/`open_browser_url` step_type 遺꾨쪟 異붽<E795B0><EBB6BD> (`549af6d`)
|
- **<2A>빐寃<EBB990>** (v0.5.1): `approval-handler.ts` L384<38>뿉 `browser_subagent` 異붽<E795B0><EBB6BD>, `step-probe.ts` L481/L549<34>뿉 `browser_subagent`/`open_browser_url` step_type 遺꾨쪟 異붽<E795B0><EBB6BD> (`549af6d`)
|
||||||
|
|
||||||
- **二쇱쓽**: <20>깉濡쒖슫 AG <20>룄援<EBA384> 異붽<E795B0><EBB6BD> <20>떆 諛섎뱶<EC848E>떆 (1) step-probe step_type 留ㅽ븨 (2) approval-handler RPC payload 留ㅽ븨 <20>뼇履<EBBC87> 紐⑤몢 <20>뾽<EFBFBD>뜲<EFBFBD>씠<EFBFBD>듃
|
- **二쇱쓽**: <20>깉濡쒖슫 AG <20>룄援<EBA384> 異붽<E795B0><EBB6BD> <20>떆 諛섎뱶<EC848E>떆 (1) step-probe step_type 留ㅽ븨 (2) approval-handler RPC payload 留ㅽ븨 <20>뼇履<EBBC87> 紐⑤몢 <20>뾽<EFBFBD>뜲<EFBFBD>씠<EFBFBD>듃
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-21] Idle<6C>넂Resume <20>떊<EFBFBD>샇 <20>냼<EFBFBD>떎 <20><><EFBFBD> 3以<33> 踰꾧렇
|
### [2026-03-21] Idle<6C>넂Resume <20>떊<EFBFBD>샇 <20>냼<EFBFBD>떎 <20><><EFBFBD> 3以<33> 踰꾧렇
|
||||||
|
|
||||||
- **利앹긽**: AG <20>옣<EFBFBD>떆媛<EB9686> idle <20>썑 <20>옉<EFBFBD>뾽 <20>옱媛<EC98B1> <20>떆 Discord <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD>떖<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬
|
- **利앹긽**: AG <20>옣<EFBFBD>떆媛<EB9686> idle <20>썑 <20>옉<EFBFBD>뾽 <20>옱媛<EC98B1> <20>떆 Discord <20>듅<EFBFBD>씤 <20>떊<EFBFBD>샇媛<EC8387> <20>쟾<EFBFBD>떖<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: (1) `ws-client.ts` `auth_fail` <20>떆 `shouldReconnect=false` <20><><EFBFBD> JWT 24h 留뚮즺 <20>떆 WS <20>쁺援<EC81BA> 醫낅즺. (2) `hub.py` `_disconnect`<60>뿉<EFBFBD>꽌 <20>쑀<EFBFBD>씪 <20>뿰寃<EBBFB0> <20>떆 `pending_owners` <20>궘<EFBFBD>젣 <20><><EFBFBD> <20>옱<EFBFBD>뿰寃<EBBFB0> <20>썑 Discord 踰꾪듉 臾댄슚. (3) `step-probe.ts` `stallProbed=true` + `lastPendingStepIndex=N`<60>씠 WS <20>옱<EFBFBD>뿰寃<EBBFB0> <20>떆 由ъ뀑 <20>븞 <20>맖 <20><><EFBFBD> WAITING step <20>옱<EFBFBD>쟾<EFBFBD>넚 <20>쁺援<EC81BA> 李⑤떒
|
- **<2A>썝<EFBFBD>씤**: (1) `ws-client.ts` `auth_fail` <20>떆 `shouldReconnect=false` <20><><EFBFBD> JWT 24h 留뚮즺 <20>떆 WS <20>쁺援<EC81BA> 醫낅즺. (2) `hub.py` `_disconnect`<60>뿉<EFBFBD>꽌 <20>쑀<EFBFBD>씪 <20>뿰寃<EBBFB0> <20>떆 `pending_owners` <20>궘<EFBFBD>젣 <20><><EFBFBD> <20>옱<EFBFBD>뿰寃<EBBFB0> <20>썑 Discord 踰꾪듉 臾댄슚. (3) `step-probe.ts` `stallProbed=true` + `lastPendingStepIndex=N`<60>씠 WS <20>옱<EFBFBD>뿰寃<EBBFB0> <20>떆 由ъ뀑 <20>븞 <20>맖 <20><><EFBFBD> WAITING step <20>옱<EFBFBD>쟾<EFBFBD>넚 <20>쁺援<EC81BA> 李⑤떒
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>** (v0.5.2): (1) `auth_fail` <20>넂 `registrationCode` <20>옱<EFBFBD>떆<EFBFBD>룄. (2) `pending_owners` orphan 留덉빱濡<EBB9B1> 蹂댁〈+<2B>옱<EFBFBD>븷<EFBFBD>떦. (3) `resetPendingStateForReconnect()` + `onConnected`<60>뿉<EFBFBD>꽌 <20>샇異<EC8387>
|
- **<2A>빐寃<EBB990>** (v0.5.2): (1) `auth_fail` <20>넂 `registrationCode` <20>옱<EFBFBD>떆<EFBFBD>룄. (2) `pending_owners` orphan 留덉빱濡<EBB9B1> 蹂댁〈+<2B>옱<EFBFBD>븷<EFBFBD>떦. (3) `resetPendingStateForReconnect()` + `onConnected`<60>뿉<EFBFBD>꽌 <20>샇異<EC8387>
|
||||||
|
|
||||||
- **二쇱쓽**: WS `onConnected`<60>뿉<EFBFBD>꽌 諛섎뱶<EC848E>떆 step-probe <20>긽<EFBFBD>깭 由ъ뀑 <20>븘<EFBFBD>닔. `stallProbed`/`lastPendingStepIndex`<60>뒗 TTL <20>뾾<EFBFBD>뒗 <20>쁺援<EC81BA> 媛<>
|
- **二쇱쓽**: WS `onConnected`<60>뿉<EFBFBD>꽌 諛섎뱶<EC848E>떆 step-probe <20>긽<EFBFBD>깭 由ъ뀑 <20>븘<EFBFBD>닔. `stallProbed`/`lastPendingStepIndex`<60>뒗 TTL <20>뾾<EFBFBD>뒗 <20>쁺援<EC81BA> 媛<>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
|
||||||
> v0.4.5 <20>닔<EFBFBD>젙 <20>궗<EFBFBD>빆(Hub pending_owners, diff_review WS, auto_approve <20>씠以묒벐湲<EBB290>, WS dual-write, ApprovalView fallback)<29><><EFBFBD>
|
> v0.4.5 <20>닔<EFBFBD>젙 <20>궗<EFBFBD>빆(Hub pending_owners, diff_review WS, auto_approve <20>씠以묒벐湲<EBB290>, WS dual-write, ApprovalView fallback)<29><><EFBFBD>
|
||||||
|
|
||||||
> 肄붾뱶 <20>닔<EFBFBD>젙 <20>셿猷뚮맖. E2E <20>넻<EFBFBD>빀 寃<>利앹<EFA79D><EC95B9> Vikunja #410<31>뿉<EFBFBD>꽌 異붿쟻 以<>.
|
> 肄붾뱶 <20>닔<EFBFBD>젙 <20>셿猷뚮맖. E2E <20>넻<EFBFBD>빀 寃<>利앹<EFA79D><EC95B9> Vikunja #410<31>뿉<EFBFBD>꽌 異붿쟻 以<>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-21] stepIndex=-1 <20><><EFBFBD> AG proto uint32 <20>뿉<EFBFBD>윭
|
### [2026-03-21] stepIndex=-1 <20><><EFBFBD> AG proto uint32 <20>뿉<EFBFBD>윭
|
||||||
|
|
||||||
- **利앹긽**: DOM observer媛<72> Allow 踰꾪듉 媛먯<E5AA9B><EBA8AF> <20>넂 Discord <20>듅<EFBFBD>씤 <20>넂 RPC `HandleCascadeUserInteraction` 400 <20>뿉<EFBFBD>윭
|
- **利앹긽**: DOM observer媛<72> Allow 踰꾪듉 媛먯<E5AA9B><EBA8AF> <20>넂 Discord <20>듅<EFBFBD>씤 <20>넂 RPC `HandleCascadeUserInteraction` 400 <20>뿉<EFBFBD>윭
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: DOM observer 寃쎈줈<EC8E88>뒗 step index瑜<78> 紐⑤쫫 <20>넂 `stepIndex=-1` <20>쟾<EFBFBD>떖 <20>넂 AG proto `uint32` <20>븘<EFBFBD>뱶<EFBFBD>뿉 <20>쓬<EFBFBD>닔 遺덇<E981BA><EB8D87>
|
- **<2A>썝<EFBFBD>씤**: DOM observer 寃쎈줈<EC8E88>뒗 step index瑜<78> 紐⑤쫫 <20>넂 `stepIndex=-1` <20>쟾<EFBFBD>떖 <20>넂 AG proto `uint32` <20>븘<EFBFBD>뱶<EFBFBD>뿉 <20>쓬<EFBFBD>닔 遺덇<E981BA><EB8D87>
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: `Math.max(0, ...)` 濡<> clamp. `permission` type <20>넂 `runExtensionCode.confirm` 留ㅽ븨 異붽<E795B0><EBB6BD> (v0.5.4)
|
- **<2A>빐寃<EBB990>**: `Math.max(0, ...)` 濡<> clamp. `permission` type <20>넂 `runExtensionCode.confirm` 留ㅽ븨 異붽<E795B0><EBB6BD> (v0.5.4)
|
||||||
|
|
||||||
- **二쇱쓽**: DOM observer 寃쎈줈<EC8E88>쓽 step_type<70><65><EFBFBD> <20>빆<EFBFBD>긽 `stepIndex=-1`<60>씪 <20>닔 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD> proto <20>쟾<EFBFBD>떖 <20>쟾 <20>뼇<EFBFBD>닔 蹂댁옣 <20>븘<EFBFBD>닔
|
- **二쇱쓽**: DOM observer 寃쎈줈<EC8E88>쓽 step_type<70><65><EFBFBD> <20>빆<EFBFBD>긽 `stepIndex=-1`<60>씪 <20>닔 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD> proto <20>쟾<EFBFBD>떖 <20>쟾 <20>뼇<EFBFBD>닔 蹂댁옣 <20>븘<EFBFBD>닔
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-21] reviewAbsoluteUris <20><><EFBFBD> latestNotifyUserStep <20>븘<EFBFBD>뱶紐<EBB1B6> 遺덉씪移<EC94AA>
|
### [2026-03-21] reviewAbsoluteUris <20><><EFBFBD> latestNotifyUserStep <20>븘<EFBFBD>뱶紐<EBB1B6> 遺덉씪移<EC94AA>
|
||||||
|
|
||||||
- **利앹긽**: `notify_user`<60>쓽 PathsToReview <20>뙆<EFBFBD>씪 由대젅<EB8C80>씠媛<EC94A0> <20>븳 踰덈룄 <20>옉<EFBFBD>룞<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>쓬
|
- **利앹긽**: `notify_user`<60>쓽 PathsToReview <20>뙆<EFBFBD>씪 由대젅<EB8C80>씠媛<EC94A0> <20>븳 踰덈룄 <20>옉<EFBFBD>룞<EFBFBD>븯吏<EBB8AF> <20>븡<EFBFBD>쓬
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: AG <20>떎<EFBFBD>젣 <20>븘<EFBFBD>뱶紐<EBB1B6> `reviewAbsoluteUris` vs 肄붾뱶 `pathsToReview`/`paths_to_review`/`filePaths`
|
- **<2A>썝<EFBFBD>씤**: AG <20>떎<EFBFBD>젣 <20>븘<EFBFBD>뱶紐<EBB1B6> `reviewAbsoluteUris` vs 肄붾뱶 `pathsToReview`/`paths_to_review`/`filePaths`
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: `reviewAbsoluteUris` 瑜<> 泥<> 踰덉㎏ <20>썑蹂대줈 異붽<E795B0><EBB6BD> (v0.5.3)
|
- **<2A>빐寃<EBB990>**: `reviewAbsoluteUris` 瑜<> 泥<> 踰덉㎏ <20>썑蹂대줈 異붽<E795B0><EBB6BD> (v0.5.3)
|
||||||
|
|
||||||
- **二쇱쓽**: AG RPC <20>븘<EFBFBD>뱶紐낆<EFA78F><EB8286> extension.log `[NOTIFY-STEP] keys=` 濡<> <20>솗<EFBFBD>씤 媛<><E5AA9B>뒫. 異붿륫 湲덉<E6B9B2><EB8D89>
|
- **二쇱쓽**: AG RPC <20>븘<EFBFBD>뱶紐낆<EFA78F><EB8286> extension.log `[NOTIFY-STEP] keys=` 濡<> <20>솗<EFBFBD>씤 媛<><E5AA9B>뒫. 異붿륫 湲덉<E6B9B2><EB8D89>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-03-21] <20>꽭<EFBFBD>뀡 <20>쟾<EFBFBD>솚 <20><><EFBFBD> 泥<> WAITING 媛먯<E5AA9B><EBA8AF> 20-25s 吏<><EFA79E>뿰
|
### [2026-03-21] <20>꽭<EFBFBD>뀡 <20>쟾<EFBFBD>솚 <20><><EFBFBD> 泥<> WAITING 媛먯<E5AA9B><EBA8AF> 20-25s 吏<><EFA79E>뿰
|
||||||
|
|
||||||
- **利앹긽**: <20>깉 <20><><EFBFBD><EFBFBD>솕 <20>떆<EFBFBD>옉 <20>썑 泥<> run_command <20>듅<EFBFBD>씤<EFBFBD>씠 Discord<72>뿉 <20>븞 <20>삤怨<EC82A4> AG<41>뿉<EFBFBD>꽌 吏곸젒 <20>듅<EFBFBD>씤<EFBFBD>빐<EFBFBD>빞 <20>븿
|
- **利앹긽**: <20>깉 <20><><EFBFBD><EFBFBD>솕 <20>떆<EFBFBD>옉 <20>썑 泥<> run_command <20>듅<EFBFBD>씤<EFBFBD>씠 Discord<72>뿉 <20>븞 <20>삤怨<EC82A4> AG<41>뿉<EFBFBD>꽌 吏곸젒 <20>듅<EFBFBD>씤<EFBFBD>빐<EFBFBD>빞 <20>븿
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: `lastModTime=''` 由ъ뀑 <20>넂 `modTimeChanged=true` <20>넂 THINKING 遺꾧린 諛섎났 <20>넂 probe 15-25s 吏<><EFA79E>뿰
|
- **<2A>썝<EFBFBD>씤**: `lastModTime=''` 由ъ뀑 <20>넂 `modTimeChanged=true` <20>넂 THINKING 遺꾧린 諛섎났 <20>넂 probe 15-25s 吏<><EFA79E>뿰
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: `lastModTime=currentModTime` + `return` <20>젣嫄<ECA0A3> + 利됱떆 probe 媛뺤젣 + <20>쉶洹<EC89B6> 媛<><E5AA9B>뱶 異붽<E795B0><EBB6BD> (v0.5.3)
|
- **<2A>빐寃<EBB990>**: `lastModTime=currentModTime` + `return` <20>젣嫄<ECA0A3> + 利됱떆 probe 媛뺤젣 + <20>쉶洹<EC89B6> 媛<><E5AA9B>뱶 異붽<E795B0><EBB6BD> (v0.5.3)
|
||||||
|
|
||||||
- **二쇱쓽**: <20>꽭<EFBFBD>뀡 <20>쟾<EFBFBD>솚 <20>떆 `wasRunning`/`pendingModifiedFiles` 由ъ뀑 <20>븘<EFBFBD>닔 (<28>씠<EFBFBD>쟾 <20>꽭<EFBFBD>뀡 <20>옍<EFBFBD>뿬臾쇰줈 false diff_review 諛⑹<E8AB9B><E291B9>)
|
- **二쇱쓽**: <20>꽭<EFBFBD>뀡 <20>쟾<EFBFBD>솚 <20>떆 `wasRunning`/`pendingModifiedFiles` 由ъ뀑 <20>븘<EFBFBD>닔 (<28>씠<EFBFBD>쟾 <20>꽭<EFBFBD>뀡 <20>옍<EFBFBD>뿬臾쇰줈 false diff_review 諛⑹<E8AB9B><E291B9>)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <20>빑<EFBFBD>떖 <20>옉<EFBFBD>뾽 洹쒖튃 (怨쇨굅 <20>씠<EFBFBD>뒋<EFBFBD>뿉<EFBFBD>꽌 諛섎났<EC848E>맂 <20>뙣<EFBFBD>꽩)
|
## <20>빑<EFBFBD>떖 <20>옉<EFBFBD>뾽 洹쒖튃 (怨쇨굅 <20>씠<EFBFBD>뒋<EFBFBD>뿉<EFBFBD>꽌 諛섎났<EC848E>맂 <20>뙣<EFBFBD>꽩)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> <20>븘<EFBFBD>옒<EFBFBD>뒗 怨쇨굅 <20>씠<EFBFBD>뒋<EFBFBD>뿉<EFBFBD>꽌 諛섎났<EC848E>쟻<EFBFBD>쑝濡<EC919D> <20>굹<EFBFBD><EAB5B9><EFBFBD><EFBFBD>궃 <20>뙣<EFBFBD>꽩<EFBFBD>쓣 洹쒖튃<EC9296>쑝濡<EC919D> <20>젙由ы븳 寃껋엯<EABB8B>땲<EFBFBD>떎.
|
> <20>븘<EFBFBD>옒<EFBFBD>뒗 怨쇨굅 <20>씠<EFBFBD>뒋<EFBFBD>뿉<EFBFBD>꽌 諛섎났<EC848E>쟻<EFBFBD>쑝濡<EC919D> <20>굹<EFBFBD><EAB5B9><EFBFBD><EFBFBD>궃 <20>뙣<EFBFBD>꽩<EFBFBD>쓣 洹쒖튃<EC9296>쑝濡<EC919D> <20>젙由ы븳 寃껋엯<EABB8B>땲<EFBFBD>떎.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| # | 洹쒖튃 | 愿<><E684BF>젴 <20>씠<EFBFBD>뒋 (archive 李몄“) |
|
| # | 洹쒖튃 | 愿<><E684BF>젴 <20>씠<EFBFBD>뒋 (archive 李몄“) |
|
||||||
|
|
||||||
|---|------|--------------------------|
|
|---|------|--------------------------|
|
||||||
|
|
||||||
| 1 | **Hub WS<57><53><EFBFBD> file bridge<67>뒗 <20>긽<EFBFBD>샇 諛고<E8AB9B><EAB3A0><EFBFBD>쟻** <20><><EFBFBD> `if hub: ws + return` / `else: file` | WS dual-write, _auto_approve <20>씠以<EC94A0> <20>벐湲<EBB290> |
|
| 1 | **Hub WS<57><53><EFBFBD> file bridge<67>뒗 <20>긽<EFBFBD>샇 諛고<E8AB9B><EAB3A0><EFBFBD>쟻** <20><><EFBFBD> `if hub: ws + return` / `else: file` | WS dual-write, _auto_approve <20>씠以<EC94A0> <20>벐湲<EBB290> |
|
||||||
|
|
||||||
| 2 | **WS 寃쎈줈 異붽<E795B0><EBB6BD> <20>떆 file-bridge<67>쓽 紐⑤뱺 遺꾧린瑜<EBA6B0> <20>룷<EFBFBD>똿** | diff_review WS regression |
|
| 2 | **WS 寃쎈줈 異붽<E795B0><EBB6BD> <20>떆 file-bridge<67>쓽 紐⑤뱺 遺꾧린瑜<EBA6B0> <20>룷<EFBFBD>똿** | diff_review WS regression |
|
||||||
|
|
||||||
| 3 | **AG RPC `{}` <20>쓳<EFBFBD>떟<EFBFBD><EB969F><EFBFBD> <20>떎<EFBFBD>뙣濡<EB99A3> 媛꾩<** <20><><EFBFBD> 硫붿꽌<EBB6BF>뱶紐<EBB1B6> <20><><EFBFBD><EFBFBD>젮<EFBFBD>룄 <20>뿉<EFBFBD>윭 <20>뾾<EFBFBD>씠 `{}` 諛섑솚 | AcknowledgeCascadeCodeEdit |
|
| 3 | **AG RPC `{}` <20>쓳<EFBFBD>떟<EFBFBD><EB969F><EFBFBD> <20>떎<EFBFBD>뙣濡<EB99A3> 媛꾩<** <20><><EFBFBD> 硫붿꽌<EBB6BF>뱶紐<EBB1B6> <20><><EFBFBD><EFBFBD>젮<EFBFBD>룄 <20>뿉<EFBFBD>윭 <20>뾾<EFBFBD>씠 `{}` 諛섑솚 | AcknowledgeCascadeCodeEdit |
|
||||||
|
|
||||||
| 4 | **ResolveOutstandingSteps<70>뒗 CANCEL <20>룞<EFBFBD>옉** <20><><EFBFBD> <20>듅<EFBFBD>씤<EFBFBD>뿉 <20>젅<EFBFBD><ECA085><EFBFBD> <20>궗<EFBFBD>슜 湲덉<E6B9B2><EB8D89> | Step probe reject |
|
| 4 | **ResolveOutstandingSteps<70>뒗 CANCEL <20>룞<EFBFBD>옉** <20><><EFBFBD> <20>듅<EFBFBD>씤<EFBFBD>뿉 <20>젅<EFBFBD><ECA085><EFBFBD> <20>궗<EFBFBD>슜 湲덉<E6B9B2><EB8D89> | Step probe reject |
|
||||||
|
|
||||||
| 5 | **Extension 肄붾뱶 <20>닔<EFBFBD>젙 <20>썑 諛섎뱶<EC848E>떆 VSIX 鍮뚮뱶 + AG <20><><EFBFBD> <20>옱<EFBFBD>떆<EFBFBD>옉** | Extension 踰꾩쟾 誘몃같<EBAA83>룷 |
|
| 5 | **Extension 肄붾뱶 <20>닔<EFBFBD>젙 <20>썑 諛섎뱶<EC848E>떆 VSIX 鍮뚮뱶 + AG <20><><EFBFBD> <20>옱<EFBFBD>떆<EFBFBD>옉** | Extension 踰꾩쟾 誘몃같<EBAA83>룷 |
|
||||||
|
|
||||||
| 6 | **HTML <20>뙣移<EB99A3> 蹂<>寃<EFBFBD> <20>떆 V8 CachedData <20>궘<EFBFBD>젣 <20>븘<EFBFBD>닔** | V8 CachedData, CSP |
|
| 6 | **HTML <20>뙣移<EB99A3> 蹂<>寃<EFBFBD> <20>떆 V8 CachedData <20>궘<EFBFBD>젣 <20>븘<EFBFBD>닔** | V8 CachedData, CSP |
|
||||||
|
|
||||||
| 7 | **`bridge/pending/` 議곗옉 <20>떆 諛섎뱶<EC848E>떆 `project_name` + `conversation_id` <20>븘<EFBFBD>꽣** | <20>겕濡쒖뒪 <20>봽濡쒖젥<EC9296>듃 DEDUP MERGE |
|
| 7 | **`bridge/pending/` 議곗옉 <20>떆 諛섎뱶<EC848E>떆 `project_name` + `conversation_id` <20>븘<EFBFBD>꽣** | <20>겕濡쒖뒪 <20>봽濡쒖젥<EC9296>듃 DEDUP MERGE |
|
||||||
|
|
||||||
| 8 | **`processResponseFile` <20>긽<EFBFBD>깭 由ъ뀑<D18A><EB8091><EFBFBD> `sawRunningAfterPending=true`留<>** | processResponseFile 臾댄븳 猷⑦봽 |
|
| 8 | **`processResponseFile` <20>긽<EFBFBD>깭 由ъ뀑<D18A><EB8091><EFBFBD> `sawRunningAfterPending=true`留<>** | processResponseFile 臾댄븳 猷⑦봽 |
|
||||||
|
|
||||||
| 9 | **fs.watch Windows 遺덉븞<EB8D89>젙 <20><><EFBFBD> 諛섎뱶<EC848E>떆 polling fallback 蹂묓뻾** | fs.watch silent fail |
|
| 9 | **fs.watch Windows 遺덉븞<EB8D89>젙 <20><><EFBFBD> 諛섎뱶<EC848E>떆 polling fallback 蹂묓뻾** | fs.watch silent fail |
|
||||||
|
|
||||||
| 10 | **diff_review<65>뒗 VS Code 而ㅻ㎤<E385BB>뱶留<EBB1B6> <20>쑀<EFBFBD>슚** <20><><EFBFBD> RPC 3媛<33> <20>쟾<EFBFBD>왂 紐⑤몢 <20>떎<EFBFBD>뙣 <20>솗<EFBFBD>젙 | diff_review RPC dead-end |
|
| 10 | **diff_review<65>뒗 VS Code 而ㅻ㎤<E385BB>뱶留<EBB1B6> <20>쑀<EFBFBD>슚** <20><><EFBFBD> RPC 3媛<33> <20>쟾<EFBFBD>왂 紐⑤몢 <20>떎<EFBFBD>뙣 <20>솗<EFBFBD>젙 | diff_review RPC dead-end |
|
||||||
|
|
||||||
| 11 | **HttpBridgeContext<78>뿉 <20>봽由щ<E794B1>명떚釉<EB969A> by-value 蹂듭궗 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> 蹂꾨룄 媛앹껜 <20>깮<EFBFBD>꽦 <20>떆 getter <20>궗<EFBFBD>슜 | HttpBridgeContext stale primitive |
|
| 11 | **HttpBridgeContext<78>뿉 <20>봽由щ<E794B1>명떚釉<EB969A> by-value 蹂듭궗 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> 蹂꾨룄 媛앹껜 <20>깮<EFBFBD>꽦 <20>떆 getter <20>궗<EFBFBD>슜 | HttpBridgeContext stale primitive |
|
||||||
|
|
||||||
| 12 | **<2A>깉 AG <20>룄援<EBA384> 異붽<E795B0><EBB6BD> <20>떆 step-probe step_type 留ㅽ븨 + approval-handler RPC payload 留ㅽ븨 <20>뼇履<EBBC87> <20>븘<EFBFBD>닔** | browser_subagent Allow |
|
| 12 | **<2A>깉 AG <20>룄援<EBA384> 異붽<E795B0><EBB6BD> <20>떆 step-probe step_type 留ㅽ븨 + approval-handler RPC payload 留ㅽ븨 <20>뼇履<EBBC87> <20>븘<EFBFBD>닔** | browser_subagent Allow |
|
||||||
|
|
||||||
| 13 | **WS `onConnected`<60>뿉<EFBFBD>꽌 step-probe <20>긽<EFBFBD>깭 由ъ뀑 <20>븘<EFBFBD>닔** <20><><EFBFBD> `stallProbed`/`lastPendingStepIndex`<60>뒗 TTL <20>뾾<EFBFBD>뒗 <20>쁺援<EC81BA> 媛<> | Idle<6C>넂Resume <20>떊<EFBFBD>샇 <20>냼<EFBFBD>떎 |
|
| 13 | **WS `onConnected`<60>뿉<EFBFBD>꽌 step-probe <20>긽<EFBFBD>깭 由ъ뀑 <20>븘<EFBFBD>닔** <20><><EFBFBD> `stallProbed`/`lastPendingStepIndex`<60>뒗 TTL <20>뾾<EFBFBD>뒗 <20>쁺援<EC81BA> 媛<> | Idle<6C>넂Resume <20>떊<EFBFBD>샇 <20>냼<EFBFBD>떎 |
|
||||||
|
|
||||||
| 14 | **AG proto `uint32` <20>븘<EFBFBD>뱶<EFBFBD>뿉 <20>쓬<EFBFBD>닔 <20>쟾<EFBFBD>떖 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> `stepIndex` <20>벑<EFBFBD><EBB291><EFBFBD> `Math.max(0, ...)` <20>븘<EFBFBD>닔 | stepIndex=-1 RPC 400 |
|
| 14 | **AG proto `uint32` <20>븘<EFBFBD>뱶<EFBFBD>뿉 <20>쓬<EFBFBD>닔 <20>쟾<EFBFBD>떖 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> `stepIndex` <20>벑<EFBFBD><EBB291><EFBFBD> `Math.max(0, ...)` <20>븘<EFBFBD>닔 | stepIndex=-1 RPC 400 |
|
||||||
|
|
||||||
| 15 | **RPC "input not registered" = wrong-LS <20>뿰寃<EBBFB0>** <20><><EFBFBD> `fixLSConnection()` <20>옄<EFBFBD>룞 <20>옱<EFBFBD>떆<EFBFBD>룄 <20>븘<EFBFBD>닔, `lines.length<=1` 議곌린醫낅즺 湲덉<E6B9B2><EB8D89> | Deriva wrong-LS (v0.5.5) |
|
| 15 | **RPC "input not registered" = wrong-LS <20>뿰寃<EBBFB0>** <20><><EFBFBD> `fixLSConnection()` <20>옄<EFBFBD>룞 <20>옱<EFBFBD>떆<EFBFBD>룄 <20>븘<EFBFBD>닔, `lines.length<=1` 議곌린醫낅즺 湲덉<E6B9B2><EB8D89> | Deriva wrong-LS (v0.5.5) |
|
||||||
|
|
||||||
| 16 | **<2A>씡<EFBFBD>뒪<EFBFBD>뀗<EFBFBD>뀡(Bridge)<29><><EFBFBD> <20>옄<EFBFBD>쓽<EFBFBD>쟻 鍮꾩쫰<EABEA9>땲<EFBFBD>뒪 <20>뙋<EFBFBD>떒 <20>젅<EFBFBD><ECA085><EFBFBD> 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> `SafeToAutoRun` <20>벑<EFBFBD>쓽 議곌굔 釉뚮옖移<EC9896> 遺꾧린<EABEA7>뒗 紐⑤몢 遊뉗쑝濡<EC919D> <20>쐞<EFBFBD>엫 (Agnostic Bridge) | SafeToAutoRun Deadlock (v0.5.15) |
|
| 16 | **<2A>씡<EFBFBD>뒪<EFBFBD>뀗<EFBFBD>뀡(Bridge)<29><><EFBFBD> <20>옄<EFBFBD>쓽<EFBFBD>쟻 鍮꾩쫰<EABEA9>땲<EFBFBD>뒪 <20>뙋<EFBFBD>떒 <20>젅<EFBFBD><ECA085><EFBFBD> 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> `SafeToAutoRun` <20>벑<EFBFBD>쓽 議곌굔 釉뚮옖移<EC9896> 遺꾧린<EABEA7>뒗 紐⑤몢 遊뉗쑝濡<EC919D> <20>쐞<EFBFBD>엫 (Agnostic Bridge) | SafeToAutoRun Deadlock (v0.5.15) |
|
||||||
|
|
||||||
| 17 | **package.json 鍮뚮뱶 <20>뒪<EFBFBD>겕由쏀듃 媛뺤젣** <20><><EFBFBD> `vscode:prepublish` 異붽<E795B0><EBB6BD>濡<EFBFBD> <20>궊<EFBFBD><EAB68A><EFBFBD> <20>냼<EFBFBD>뒪 諛고룷 <20>썝泥<EC8D9D> 李⑤떒 | VSIX v0.5.15 鍮뚮뱶 <20>늻<EFBFBD>씫 |
|
| 17 | **package.json 鍮뚮뱶 <20>뒪<EFBFBD>겕由쏀듃 媛뺤젣** <20><><EFBFBD> `vscode:prepublish` 異붽<E795B0><EBB6BD>濡<EFBFBD> <20>궊<EFBFBD><EAB68A><EFBFBD> <20>냼<EFBFBD>뒪 諛고룷 <20>썝泥<EC8D9D> 李⑤떒 | VSIX v0.5.15 鍮뚮뱶 <20>늻<EFBFBD>씫 |
|
||||||
|
|
||||||
| 18 | **<2A>룞湲곗떇 `cp.execSync` <20>궗<EFBFBD>슜 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> Windows <20>솚寃쎌뿉<EC8E8C>꽌 硫붿씤 <20>씠踰ㅽ듃猷⑦봽 <20>봽由ъ쭠 諛<> WS heartbeat <20>떒<EFBFBD>젅 <20>쑀諛<EC9180> | detectProjectName <20>봽由ъ쭠 |
|
| 18 | **<2A>룞湲곗떇 `cp.execSync` <20>궗<EFBFBD>슜 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> Windows <20>솚寃쎌뿉<EC8E8C>꽌 硫붿씤 <20>씠踰ㅽ듃猷⑦봽 <20>봽由ъ쭠 諛<> WS heartbeat <20>떒<EFBFBD>젅 <20>쑀諛<EC9180> | detectProjectName <20>봽由ъ쭠 |
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-09] [Bot/Extension] Discord Signal Relay Failure & Empty Body
|
### [2026-04-09] [Bot/Extension] Discord Signal Relay Failure & Empty Body
|
||||||
- **利앹긽**: <20>뵒<EFBFBD>뒪肄붾뱶 遊뉗<E9818A><EB8997> '<27>옄<EFBFBD>룞 <20>듅<EFBFBD>씤<EFBFBD>맖'<27>쓣 <20>쓣<EFBFBD>슦吏<EC8AA6>留<EFBFBD> <20>떎<EFBFBD>젣 肄붾뱶 蹂몃Ц<EBAA83>씠 <20>몴<EFBFBD>떆<EFBFBD>릺吏<EBA6BA> <20>븡怨<EBB8A1>, 梨꾨꼸<EABEA8>뿉 吏꾩쭨 梨꾪똿 硫붿떆吏<EB9686><EFA79E>굹 <20>븣由쇱씠 <20>뒪<EFBFBD>뙵 <20>걧 <20>뮘<EFBFBD>뿉 諛<><E8AB9B>젮 <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
- **利앹긽**: <20>뵒<EFBFBD>뒪肄붾뱶 遊뉗<E9818A><EB8997> '<27>옄<EFBFBD>룞 <20>듅<EFBFBD>씤<EFBFBD>맖'<27>쓣 <20>쓣<EFBFBD>슦吏<EC8AA6>留<EFBFBD> <20>떎<EFBFBD>젣 肄붾뱶 蹂몃Ц<EBAA83>씠 <20>몴<EFBFBD>떆<EFBFBD>릺吏<EBA6BA> <20>븡怨<EBB8A1>, 梨꾨꼸<EABEA8>뿉 吏꾩쭨 梨꾪똿 硫붿떆吏<EB9686><EFA79E>굹 <20>븣由쇱씠 <20>뒪<EFBFBD>뙵 <20>걧 <20>뮘<EFBFBD>뿉 諛<><E8AB9B>젮 <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
||||||
- **<2A>썝<EFBFBD>씤**: 1) observer-script.ts<74>뿉<EFBFBD>꽌 踰꾪듉 <20>뀓<EFBFBD>뒪<EFBFBD>듃 留ㅼ묶 <20>떆 Run <20>떒<EFBFBD>뼱<EFBFBD>쓽 寃쎄퀎(\b) 泥섎━瑜<E29481> <20>븯吏<EBB8AF> <20>븡<EFBFBD>븘 VS Code <20>븯<EFBFBD>떒<EFBFBD>쓽 'Running 1 command'瑜<> 媛<>濡쒖콈<EC9296>뼱 PENDING <20>뒪<EFBFBD>뙵 臾댄븳 <20>깮<EFBFBD>꽦. 2) bot.py<70>뿉<EFBFBD>꽌 <20>옄<EFBFBD>룞 <20>듅<EFBFBD>씤 Embed <20>깮<EFBFBD>꽦 <20>떆 req.description<6F>쓣 洹몃━吏<E29481> <20>븡怨<EBB8A1> 踰꾪듉 <20>뀓<EFBFBD>뒪<EFBFBD>듃(req.command)留<> <20>몴<EFBFBD>떆. 3) step-probe.ts<74>뿉<EFBFBD>꽌 <20>꽭<EFBFBD>뀡 援먯껜 <20>떆 理쒓렐 <20>븣由<EBB8A3> <20>씤<EFBFBD>뜳<EFBFBD>뒪 珥덇린<EB8D87>솕瑜<EC8695> <20>옒紐삵븯<EC82B5>뿬 <20>꽭<EFBFBD>뀡<EFBFBD>쓽 泥<> 硫붿떆吏<EB9686>瑜<EFBFBD> 臾댁“嫄<E2809C> <20>뱶濡<EBB1B6>.
|
- **<2A>썝<EFBFBD>씤**: 1) observer-script.ts<74>뿉<EFBFBD>꽌 踰꾪듉 <20>뀓<EFBFBD>뒪<EFBFBD>듃 留ㅼ묶 <20>떆 Run <20>떒<EFBFBD>뼱<EFBFBD>쓽 寃쎄퀎(\b) 泥섎━瑜<E29481> <20>븯吏<EBB8AF> <20>븡<EFBFBD>븘 VS Code <20>븯<EFBFBD>떒<EFBFBD>쓽 'Running 1 command'瑜<> 媛<>濡쒖콈<EC9296>뼱 PENDING <20>뒪<EFBFBD>뙵 臾댄븳 <20>깮<EFBFBD>꽦. 2) bot.py<70>뿉<EFBFBD>꽌 <20>옄<EFBFBD>룞 <20>듅<EFBFBD>씤 Embed <20>깮<EFBFBD>꽦 <20>떆 req.description<6F>쓣 洹몃━吏<E29481> <20>븡怨<EBB8A1> 踰꾪듉 <20>뀓<EFBFBD>뒪<EFBFBD>듃(req.command)留<> <20>몴<EFBFBD>떆. 3) step-probe.ts<74>뿉<EFBFBD>꽌 <20>꽭<EFBFBD>뀡 援먯껜 <20>떆 理쒓렐 <20>븣由<EBB8A3> <20>씤<EFBFBD>뜳<EFBFBD>뒪 珥덇린<EB8D87>솕瑜<EC8695> <20>옒紐삵븯<EC82B5>뿬 <20>꽭<EFBFBD>뀡<EFBFBD>쓽 泥<> 硫붿떆吏<EB9686>瑜<EFBFBD> 臾댁“嫄<E2809C> <20>뱶濡<EBB1B6>.
|
||||||
- **<2A>빐寃<EBB990>**: DOM 媛먯<E5AA9B><EBA8AF> <20>젙洹쒖떇<EC9296>뿉 \b 媛뺤젣 遺<><E981BA>뿬 (/Run\b/), bot.py<70>쓽 Auto-Approve 履<> Embed 蹂몃Ц<EBAA83>뿉 req.description <20>젋<EFBFBD>뜑留<EB9C91> 異붽<E795B0><EBB6BD>, step-probe.ts<74>뿉<EFBFBD>꽌 session init <20>떆 index瑜<78> -1濡<31> 由ъ뀑.
|
- **<2A>빐寃<EBB990>**: DOM 媛먯<E5AA9B><EBA8AF> <20>젙洹쒖떇<EC9296>뿉 \b 媛뺤젣 遺<><E981BA>뿬 (/Run\b/), bot.py<70>쓽 Auto-Approve 履<> Embed 蹂몃Ц<EBAA83>뿉 req.description <20>젋<EFBFBD>뜑留<EB9C91> 異붽<E795B0><EBB6BD>, step-probe.ts<74>뿉<EFBFBD>꽌 session init <20>떆 index瑜<78> -1濡<31> 由ъ뀑.
|
||||||
- **二쇱쓽**: Native UI <20>뀓<EFBFBD>뒪<EFBFBD>듃 媛먯<E5AA9B><EBA8AF> <20>떆 <20>떒<EFBFBD>뼱 寃쎄퀎(\b)源뚯<E6BA90><EB9AAF> 寃<>利앺빐<EC95BA>빞 False Positive瑜<65> 留됱쓣 <20>닔 <20>엳<EFBFBD>쑝硫<EC919D>, Auto-Approve<76>뒗 諛섎뱶<EC848E>떆 蹂몃Ц<EBAA83>쓣 <20>끂異쒗빐<EC9297>빞 <20>븿.
|
- **二쇱쓽**: Native UI <20>뀓<EFBFBD>뒪<EFBFBD>듃 媛먯<E5AA9B><EBA8AF> <20>떆 <20>떒<EFBFBD>뼱 寃쎄퀎(\b)源뚯<E6BA90><EB9AAF> 寃<>利앺빐<EC95BA>빞 False Positive瑜<65> 留됱쓣 <20>닔 <20>엳<EFBFBD>쑝硫<EC919D>, Auto-Approve<76>뒗 諛섎뱶<EC848E>떆 蹂몃Ц<EBAA83>쓣 <20>끂異쒗빐<EC9297>빞 <20>븿.
|
||||||
|
|
||||||
### [2026-04-10] [Extension] AI Response Content Missing (Nested PlannerResponse)
|
### [2026-04-10] [Extension] AI Response Content Missing (Nested PlannerResponse)
|
||||||
- **利앹긽**: <20>뵒<EFBFBD>뒪肄붾뱶 梨꾪똿諛⑹뿉 Agent<6E>쓽 <20>뀓<EFBFBD>뒪<EFBFBD>듃 <20>쓳<EFBFBD>떟(AI <20>쓳<EFBFBD>떟)<29>씠 <20>븘<EFBFBD>삁 <20>늻<EFBFBD>씫<EFBFBD>릺<EFBFBD>뼱 <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
- **利앹긽**: <20>뵒<EFBFBD>뒪肄붾뱶 梨꾪똿諛⑹뿉 Agent<6E>쓽 <20>뀓<EFBFBD>뒪<EFBFBD>듃 <20>쓳<EFBFBD>떟(AI <20>쓳<EFBFBD>떟)<29>씠 <20>븘<EFBFBD>삁 <20>늻<EFBFBD>씫<EFBFBD>릺<EFBFBD>뼱 <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>쓬.
|
||||||
- **<2A>썝<EFBFBD>씤**: GetCascadeTrajectorySteps媛<73> 諛섑솚<EC8491>븯<EFBFBD>뒗 plannerResponse媛<65> <20>봽濡쒗넗肄<EB8497> 諛⑹떇<E291B9>뿉 <20>뵲<EFBFBD>씪 理쒖긽<EC9296>떒(s.plannerResponse)<29>씠 <20>븘<EFBFBD>땶 s.step.plannerResponse<73>뿉 以묒꺽<EBAC92>릺<EFBFBD>뼱 <20>뱾<EFBFBD>뼱<EFBFBD>삱 <20>닔 <20>엳<EFBFBD>쓬. 湲곗〈 <20>뙆<EFBFBD>꽌<EFBFBD>뒗 <20>븯<EFBFBD>뱶肄붾뵫<EBB6BE>맂 <20>븘<EFBFBD>뱶 諛<> <20>뵆<EFBFBD>옯 援ъ“留<E2809C> 議고쉶<EAB3A0>븯<EFBFBD>뿬 <20>쓳<EFBFBD>떟<EFBFBD>쓣 踰꾨┝.
|
- **<2A>썝<EFBFBD>씤**: GetCascadeTrajectorySteps媛<73> 諛섑솚<EC8491>븯<EFBFBD>뒗 plannerResponse媛<65> <20>봽濡쒗넗肄<EB8497> 諛⑹떇<E291B9>뿉 <20>뵲<EFBFBD>씪 理쒖긽<EC9296>떒(s.plannerResponse)<29>씠 <20>븘<EFBFBD>땶 s.step.plannerResponse<73>뿉 以묒꺽<EBAC92>릺<EFBFBD>뼱 <20>뱾<EFBFBD>뼱<EFBFBD>삱 <20>닔 <20>엳<EFBFBD>쓬. 湲곗〈 <20>뙆<EFBFBD>꽌<EFBFBD>뒗 <20>븯<EFBFBD>뱶肄붾뵫<EBB6BE>맂 <20>븘<EFBFBD>뱶 諛<> <20>뵆<EFBFBD>옯 援ъ“留<E2809C> 議고쉶<EAB3A0>븯<EFBFBD>뿬 <20>쓳<EFBFBD>떟<EFBFBD>쓣 踰꾨┝.
|
||||||
@@ -281,23 +582,32 @@
|
|||||||
- **<2A>썝<EFBFBD>씤**: AI <20>쓳<EFBFBD>떟<EFBFBD>씠<EFBFBD>굹 肄붾뵫 <20>옉<EFBFBD>뾽<EFBFBD>씠 5珥<35>(<28>뤃留<EBA483> 二쇨린) 誘몃쭔<EBAA83>쑝濡<EC919D> 留ㅼ슦 鍮좊Ⅴ寃<E285A4> <20>걹<EFBFBD>굹硫<EAB5B9>, <20>솗<EFBFBD>옣<EFBFBD>씠 `IDLE -> IDLE` <20>긽<EFBFBD>깭留<EAB9AD> 愿<>李고븯硫<EBB8AF> `wasRunning` <20>뵆<EFBFBD>옒洹멸<E6B4B9><EBA9B8> `false`濡<> <20>쑀吏<EC9180><EFA79E>맖. 湲곗〈 `[RESPONSE-CAPTURE]` 議곌굔<EAB38C>떇(`wasRunning && !isRunning && currentCount > ...`)<29>씠 `wasRunning=false`濡<> <20>씤<EFBFBD>빐 釉붾줉<EBB6BE>릺<EFBFBD>뼱 罹≪쿂 <20>옄泥대<EFA7A3><EB8C80> <20>셿<EFBFBD>쟾<EFBFBD>엳 嫄대꼫<EB8C80>쎇寃<EC8E87> <20>맖.
|
- **<2A>썝<EFBFBD>씤**: AI <20>쓳<EFBFBD>떟<EFBFBD>씠<EFBFBD>굹 肄붾뵫 <20>옉<EFBFBD>뾽<EFBFBD>씠 5珥<35>(<28>뤃留<EBA483> 二쇨린) 誘몃쭔<EBAA83>쑝濡<EC919D> 留ㅼ슦 鍮좊Ⅴ寃<E285A4> <20>걹<EFBFBD>굹硫<EAB5B9>, <20>솗<EFBFBD>옣<EFBFBD>씠 `IDLE -> IDLE` <20>긽<EFBFBD>깭留<EAB9AD> 愿<>李고븯硫<EBB8AF> `wasRunning` <20>뵆<EFBFBD>옒洹멸<E6B4B9><EBA9B8> `false`濡<> <20>쑀吏<EC9180><EFA79E>맖. 湲곗〈 `[RESPONSE-CAPTURE]` 議곌굔<EAB38C>떇(`wasRunning && !isRunning && currentCount > ...`)<29>씠 `wasRunning=false`濡<> <20>씤<EFBFBD>빐 釉붾줉<EBB6BE>릺<EFBFBD>뼱 罹≪쿂 <20>옄泥대<EFA7A3><EB8C80> <20>셿<EFBFBD>쟾<EFBFBD>엳 嫄대꼫<EB8C80>쎇寃<EC8E87> <20>맖.
|
||||||
- **<2A>빐寃<EBB990>**: `wasRunning` 寃<>利앹쓣 <20>궘<EFBFBD>젣<EFBFBD>븯怨<EBB8AF> `!isRunning && currentCount > lastResponseCaptureStep` 議곌굔<EAB38C>쑝濡<EC919D> <20>셿<EFBFBD>솕<EFBFBD>븯<EFBFBD>뿬 <20>늻<EFBFBD>씫<EFBFBD>맂 step<65>씠 <20>엳<EFBFBD>쓣 <20>븣 臾댁“嫄<E2809C> 罹≪쿂<E289AA>븯<EFBFBD>룄濡<EBA384> 蹂<>寃<EFBFBD>. 異붽<E795B0><EBB6BD>濡<EFBFBD> <20>삤<EFBFBD>옒<EFBFBD>맂 `[RESPONSE-CAPTURE]` <20>궡 <20>븯<EFBFBD>뱶肄붾뵫 <20>뙆<EFBFBD>꽌瑜<EABD8C> `extractPlannerText`濡<> <20>씪<EFBFBD>썝<EFBFBD>솕 <20>쟻<EFBFBD>슜.
|
- **<2A>빐寃<EBB990>**: `wasRunning` 寃<>利앹쓣 <20>궘<EFBFBD>젣<EFBFBD>븯怨<EBB8AF> `!isRunning && currentCount > lastResponseCaptureStep` 議곌굔<EAB38C>쑝濡<EC919D> <20>셿<EFBFBD>솕<EFBFBD>븯<EFBFBD>뿬 <20>늻<EFBFBD>씫<EFBFBD>맂 step<65>씠 <20>엳<EFBFBD>쓣 <20>븣 臾댁“嫄<E2809C> 罹≪쿂<E289AA>븯<EFBFBD>룄濡<EBA384> 蹂<>寃<EFBFBD>. 異붽<E795B0><EBB6BD>濡<EFBFBD> <20>삤<EFBFBD>옒<EFBFBD>맂 `[RESPONSE-CAPTURE]` <20>궡 <20>븯<EFBFBD>뱶肄붾뵫 <20>뙆<EFBFBD>꽌瑜<EABD8C> `extractPlannerText`濡<> <20>씪<EFBFBD>썝<EFBFBD>솕 <20>쟻<EFBFBD>슜.
|
||||||
- **二쇱쓽**: <20>뤃留<EBA483> 諛⑹떇<E291B9>뿉<EFBFBD>꽌<EFBFBD>뒗 <20>긽<EFBFBD>깭(RUNNING->IDLE) <20>쟾<EFBFBD>씠瑜<EC94A0> <20>솗<EFBFBD>떊<EFBFBD>븷 <20>닔 <20>뾾<EFBFBD>쑝誘<EC919D>濡<EFBFBD>, Step Count(<28>씤<EFBFBD>뜳<EFBFBD>뒪 <20>쟾吏<EC9FBE>)<29>씪<EFBFBD>뒗 100% <20>떊猶<EB968A> 媛<><E5AA9B>뒫<EFBFBD>븳 留덉빱瑜<EBB9B1> <20>넻<EFBFBD>빐 <20>깉 <20>쓳<EFBFBD>떟 <20>뿬遺<EBBFAC>瑜<EFBFBD> 媛먯<E5AA9B><EBA8AF><EFBFBD>빐<EFBFBD>빞 <20>븿.
|
- **二쇱쓽**: <20>뤃留<EBA483> 諛⑹떇<E291B9>뿉<EFBFBD>꽌<EFBFBD>뒗 <20>긽<EFBFBD>깭(RUNNING->IDLE) <20>쟾<EFBFBD>씠瑜<EC94A0> <20>솗<EFBFBD>떊<EFBFBD>븷 <20>닔 <20>뾾<EFBFBD>쑝誘<EC919D>濡<EFBFBD>, Step Count(<28>씤<EFBFBD>뜳<EFBFBD>뒪 <20>쟾吏<EC9FBE>)<29>씪<EFBFBD>뒗 100% <20>떊猶<EB968A> 媛<><E5AA9B>뒫<EFBFBD>븳 留덉빱瑜<EBB9B1> <20>넻<EFBFBD>빐 <20>깉 <20>쓳<EFBFBD>떟 <20>뿬遺<EBBFAC>瑜<EFBFBD> 媛먯<E5AA9B><EBA8AF><EFBFBD>빐<EFBFBD>빞 <20>븿.
|
||||||
|
|
||||||
### [2026-04-10] [Bot] chat_snapshot_scanner 臾댄븳 Abort 諛<> <20>뙆<EFBFBD>씪 <20>쟻泥<EC9FBB> (Exception <20>늻<EFBFBD>씫)
|
### [2026-04-10] [Bot] chat_snapshot_scanner 臾댄븳 Abort 諛<> <20>뙆<EFBFBD>씪 <20>쟻泥<EC9FBB> (Exception <20>늻<EFBFBD>씫)
|
||||||
- **利앹긽**: 遊뉗씠 <20>뵒<EFBFBD>뒪肄붾뱶濡<EBB1B6> AI <20>떟蹂<EB969F>(梨꾪똿 <20>뒪<EFBFBD>깄<EFBFBD>꺑)<29>쓣 <20>쟾<EFBFBD><EC9FBE><EFBFBD> <20>쟾<EFBFBD>넚<EFBFBD>븯吏<EBB8AF> 紐삵븯怨<EBB8AF> <20>젆<EFBFBD>씠 嫄몃┝. ridge/chat_snapshots/<2F>뿉 泥섎━<EC848E>릺吏<EBA6BA> <20>븡<EFBFBD><EBB8A1><EFBFBD> JSON <20>뙆<EFBFBD>씪<EFBFBD>씠 <20>닔<EFBFBD>떗 媛<> <20>쟻泥대맖.
|
- **利앹긽**: 遊뉗씠 <20>뵒<EFBFBD>뒪肄붾뱶濡<EBB1B6> AI <20>떟蹂<EB969F>(梨꾪똿 <20>뒪<EFBFBD>깄<EFBFBD>꺑)<29>쓣 <20>쟾<EFBFBD><EC9FBE><EFBFBD> <20>쟾<EFBFBD>넚<EFBFBD>븯吏<EBB8AF> 紐삵븯怨<EBB8AF> <20>젆<EFBFBD>씠 嫄몃┝. ridge/chat_snapshots/<2F>뿉 泥섎━<EC848E>릺吏<EBA6BA> <20>븡<EFBFBD><EBB8A1><EFBFBD> JSON <20>뙆<EFBFBD>씪<EFBFBD>씠 <20>닔<EFBFBD>떗 媛<> <20>쟻泥대맖.
|
||||||
- **<2A>썝<EFBFBD>씤**: ot.py<70>쓽 chat_snapshot_scanner<65>뿉<EFBFBD>꽌 <20>뙆<EFBFBD>씪<EFBFBD>쓣 <20>닚<EFBFBD>쉶 <20>뙆<EFBFBD>떛<EFBFBD>븷 <20>븣 <20>궡遺<EAB6A1><E981BA>쓽 .unlink() 怨쇱젙<EC87B1>뿉<EFBFBD>꽌 諛쒖깮<EC9296>븯<EFBFBD>뒗 <20>삁<EFBFBD>쇅<EFBFBD>굹 discord.Embed <20>깮<EFBFBD>꽦 <20>삁<EFBFBD>쇅 <20>벑<EFBFBD>쓣 猷⑦봽 <20>븞<EFBFBD>뿉<EFBFBD>꽌 <20>옟<EFBFBD>븘二쇱<E4BA8C><EC87B1> 紐삵븿. 泥<> <20>뿉<EFBFBD>윭 <20>뙆<EFBFBD>씪(poison pill)<29>쓣 留뚮굹<EB9AAE>뒗 <20>닚媛<EB8B9A> 猷⑦봽 <20>쟾泥닿<EFA7A3><EB8BBF> <20>룺<EFBFBD>뙆<EFBFBD>릺<EFBFBD>뼱 <20>뮘履쎌쓽 <20>젙<EFBFBD>긽 <20>뙆<EFBFBD>씪<EFBFBD>뱾<EFBFBD>룄 <20>쁺<EFBFBD>썝<EFBFBD>엳 泥섎━<EC848E>릺吏<EBA6BA> <20>븡怨<EBB8A1> <20>떎<EFBFBD>쓬 <20>뤃 <20>뒪耳<EB92AA>以꾩뿉<EABEA9>꽌 <20>떎<EFBFBD>떆 泥<> <20>뙆<EFBFBD>씪<EFBFBD>뿉 留됲옒.
|
- **<2A>썝<EFBFBD>씤**: ot.py<70>쓽 chat_snapshot_scanner<65>뿉<EFBFBD>꽌 <20>뙆<EFBFBD>씪<EFBFBD>쓣 <20>닚<EFBFBD>쉶 <20>뙆<EFBFBD>떛<EFBFBD>븷 <20>븣 <20>궡遺<EAB6A1><E981BA>쓽 .unlink() 怨쇱젙<EC87B1>뿉<EFBFBD>꽌 諛쒖깮<EC9296>븯<EFBFBD>뒗 <20>삁<EFBFBD>쇅<EFBFBD>굹 discord.Embed <20>깮<EFBFBD>꽦 <20>삁<EFBFBD>쇅 <20>벑<EFBFBD>쓣 猷⑦봽 <20>븞<EFBFBD>뿉<EFBFBD>꽌 <20>옟<EFBFBD>븘二쇱<E4BA8C><EC87B1> 紐삵븿. 泥<> <20>뿉<EFBFBD>윭 <20>뙆<EFBFBD>씪(poison pill)<29>쓣 留뚮굹<EB9AAE>뒗 <20>닚媛<EB8B9A> 猷⑦봽 <20>쟾泥닿<EFA7A3><EB8BBF> <20>룺<EFBFBD>뙆<EFBFBD>릺<EFBFBD>뼱 <20>뮘履쎌쓽 <20>젙<EFBFBD>긽 <20>뙆<EFBFBD>씪<EFBFBD>뱾<EFBFBD>룄 <20>쁺<EFBFBD>썝<EFBFBD>엳 泥섎━<EC848E>릺吏<EBA6BA> <20>븡怨<EBB8A1> <20>떎<EFBFBD>쓬 <20>뤃 <20>뒪耳<EB92AA>以꾩뿉<EABEA9>꽌 <20>떎<EFBFBD>떆 泥<> <20>뙆<EFBFBD>씪<EFBFBD>뿉 留됲옒.
|
||||||
- **<2A>빐寃<EBB990>**: 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉 except Exception<6F>쓣 異붽<E795B0><EBB6BD><EFBFBD>븯<EFBFBD>뿬 <20>쟾<EFBFBD>뿭 <20>삁<EFBFBD>쇅瑜<EC8785> <20>옟<EFBFBD>븘 諛⑹뼱. <20>떎<EFBFBD>뙣<EFBFBD>븳 <20>뙆<EFBFBD>씪<EFBFBD><EC94AA><EFBFBD> glob<6F>뿉<EFBFBD>꽌 諛섎났 <20>떆<EFBFBD>룄<EFBFBD>릺吏<EBA6BA> <20>븡寃<EBB8A1> .json.failed濡<64> <20>슦<EFBFBD>쉶(rename)<29>떆耳<EB9686> <20>걧瑜<EAB1A7> 鍮꾩썙以<EC8D99>.
|
- **<2A>빐寃<EBB990>**: 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉 except Exception<6F>쓣 異붽<E795B0><EBB6BD><EFBFBD>븯<EFBFBD>뿬 <20>쟾<EFBFBD>뿭 <20>삁<EFBFBD>쇅瑜<EC8785> <20>옟<EFBFBD>븘 諛⑹뼱. <20>떎<EFBFBD>뙣<EFBFBD>븳 <20>뙆<EFBFBD>씪<EFBFBD><EC94AA><EFBFBD> glob<6F>뿉<EFBFBD>꽌 諛섎났 <20>떆<EFBFBD>룄<EFBFBD>릺吏<EBA6BA> <20>븡寃<EBB8A1> .json.failed濡<64> <20>슦<EFBFBD>쉶(rename)<29>떆耳<EB9686> <20>걧瑜<EAB1A7> 鍮꾩썙以<EC8D99>.
|
||||||
- **二쇱쓽**: <20>뤃留<EBA483>/<2F>뒪罹먮꼫 or 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉<EFBFBD>꽌<EFBFBD>뒗 媛쒕퀎 <20>븘<EFBFBD>씠<EFBFBD>뀥 <20>뙆<EFBFBD>떛 <20>떒怨꾩뿉<EABEA9>꽌 諛쒖깮 媛<><E5AA9B>뒫<EFBFBD>븳 紐⑤뱺 <20>삁<EFBFBD>쇅 <20>긽<EFBFBD>깭<EFBFBD>뿉 <20><><EFBFBD><EFBFBD>븳 Defensive Catch 諛<> Continue(<28>슦<EFBFBD>쉶) 濡쒖쭅<EC9296>씠 <20>븘<EFBFBD>닔<EFBFBD>엫.
|
- **二쇱쓽**: <20>뤃留<EBA483>/<2F>뒪罹먮꼫 or 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉<EFBFBD>꽌<EFBFBD>뒗 媛쒕퀎 <20>븘<EFBFBD>씠<EFBFBD>뀥 <20>뙆<EFBFBD>떛 <20>떒怨꾩뿉<EABEA9>꽌 諛쒖깮 媛<><E5AA9B>뒫<EFBFBD>븳 紐⑤뱺 <20>삁<EFBFBD>쇅 <20>긽<EFBFBD>깭<EFBFBD>뿉 <20><><EFBFBD><EFBFBD>븳 Defensive Catch 諛<> Continue(<28>슦<EFBFBD>쉶) 濡쒖쭅<EC9296>씠 <20>븘<EFBFBD>닔<EFBFBD>엫.
|
||||||
|
|
||||||
### [2026-04-10] [Extension] GetAllCascadeTrajectories 10-Item Hard Limit Bypass (Signal Drop)
|
### [2026-04-10] [Extension] GetAllCascadeTrajectories 10-Item Hard Limit Bypass (Signal Drop)
|
||||||
- **증상**: 기존에 작성했던 { limit: 30 } 파라미터가 LS 백엔드에서 무시되어 최신 세션이 10개 제한에 걸려 잘려나감. (Discord로 메시지 단 한 글자도 안 넘어옴).
|
- **증상**: 기존에 작성했던 { limit: 30 } 파라미터가 LS 백엔드에서 무시되어 최신 세션이 10개 제한에 걸려 잘려나감. (Discord로 메시지 단 한 글자도 안 넘어옴).
|
||||||
- **원인**: GetAllCascadeTrajectories는 구조적으로 pagination 옵션을 무시하거나 강제 10 제한이 걸림.
|
- **원인**: GetAllCascadeTrajectories는 구조적으로 pagination 옵션을 무시하거나 강제 10 제한이 걸림.
|
||||||
- **해결**: step-probe.ts에서 기본 GetAllCascadeTrajectories와 더불어 모든 트래젝토리를 덤프하는 GetDiagnostics API를 병행 호출하고 머지하여 최신 Session ID를 놓치지 않고 추출하게 함.
|
- **해결**: step-probe.ts에서 기본 GetAllCascadeTrajectories와 더불어 모든 트래젝토리를 덤프하는 GetDiagnostics API를 병행 호출하고 머지하여 최신 Session ID를 놓치지 않고 추출하게 함.
|
||||||
- **주의**: LS Backend에서 정의한 RPC의 한계상 Argument 조작으로 제한을 회피할 수 없으므로, 향후 GetDiagnostics 등 백도어 데이터를 활용할 것.
|
- **주의**: LS Backend에서 정의한 RPC의 한계상 Argument 조작으로 제한을 회피할 수 없으므로, 향후 GetDiagnostics 등 백도어 데이터를 활용할 것.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-10] [Probe Logging] <20><><EFBFBD> AI<41>쓳<EFBFBD>떟 <20>뀓<EFBFBD>뒪<EFBFBD>듃 & WAITING <20>뒪<EFBFBD>뀦 <20>룞<EFBFBD>떆 <20>늻<EFBFBD>씫 踰꾧렇
|
### [2026-04-10] [Probe Logging] <20><><EFBFBD> AI<41>쓳<EFBFBD>떟 <20>뀓<EFBFBD>뒪<EFBFBD>듃 & WAITING <20>뒪<EFBFBD>뀦 <20>룞<EFBFBD>떆 <20>늻<EFBFBD>씫 踰꾧렇
|
||||||
|
|
||||||
- **利앹긽**: 援됱옣<EB90B1>엳 鍮좊Ⅸ AI <20>쓳<EFBFBD>떟(<28>삉<EFBFBD>뒗 利됯컖<EB90AF>쟻<EFBFBD>씤 <20>댋 <20>샇異<EC8387>) <20>떆 `step-probe.ts`媛<> 硫붿떆吏<EB9686><EFA79E><EFBFBD><EFBFBD> <20>듅<EFBFBD>씤 <20>떎<EFBFBD>씠<EFBFBD>뼹濡쒓렇瑜<EBA087> 紐⑤몢 Discord濡<64> 由대젅<EB8C80>씠<EFBFBD>븯吏<EBB8AF> 紐삵븿.
|
- **利앹긽**: 援됱옣<EB90B1>엳 鍮좊Ⅸ AI <20>쓳<EFBFBD>떟(<28>삉<EFBFBD>뒗 利됯컖<EB90AF>쟻<EFBFBD>씤 <20>댋 <20>샇異<EC8387>) <20>떆 `step-probe.ts`媛<> 硫붿떆吏<EB9686><EFA79E><EFBFBD><EFBFBD> <20>듅<EFBFBD>씤 <20>떎<EFBFBD>씠<EFBFBD>뼹濡쒓렇瑜<EBA087> 紐⑤몢 Discord濡<64> 由대젅<EB8C80>씠<EFBFBD>븯吏<EBB8AF> 紐삵븿.
|
||||||
|
|
||||||
- **<2A>썝<EFBFBD>씤**: <20>떎<EFBFBD>떆媛<EB9686> <20>뀓<EFBFBD>뒪<EFBFBD>듃 罹≪쿂(`delta > 0`) 議곌굔<EAB38C>뿉 `isRunning &&`<60>씠 嫄몃젮<EBAA83>엳<EFBFBD>뼱, <20>긽<EFBFBD>깭媛<EAB9AD> `WAITING`<60>씠<EFBFBD>굹 `IDLE`濡<> 利됱떆 <20>꽆<EFBFBD>뼱媛<EBBCB1>硫<EFBFBD> <20>뀓<EFBFBD>뒪<EFBFBD>듃瑜<EB9383> 罹≪쿂<E289AA>븯<EFBFBD>뒗 猷⑦떞<E291A6>씠 <20>쟾遺<EC9FBE> <20>뒪<EFBFBD>궢<EFBFBD>맖. <20>삉<EFBFBD>븳 <20>씠 <20>닚媛<EB8B9A> `isStall` 議곌굔<EAB38C>룄 <20><><EFBFBD>吏<EFBFBD> <20>븡<EFBFBD>븘 `WAITING` <20>뵒<EFBFBD>뀓<EFBFBD>뀡<EFBFBD>룄 利앸컻<EC95B8>븿.
|
- **<2A>썝<EFBFBD>씤**: <20>떎<EFBFBD>떆媛<EB9686> <20>뀓<EFBFBD>뒪<EFBFBD>듃 罹≪쿂(`delta > 0`) 議곌굔<EAB38C>뿉 `isRunning &&`<60>씠 嫄몃젮<EBAA83>엳<EFBFBD>뼱, <20>긽<EFBFBD>깭媛<EAB9AD> `WAITING`<60>씠<EFBFBD>굹 `IDLE`濡<> 利됱떆 <20>꽆<EFBFBD>뼱媛<EBBCB1>硫<EFBFBD> <20>뀓<EFBFBD>뒪<EFBFBD>듃瑜<EB9383> 罹≪쿂<E289AA>븯<EFBFBD>뒗 猷⑦떞<E291A6>씠 <20>쟾遺<EC9FBE> <20>뒪<EFBFBD>궢<EFBFBD>맖. <20>삉<EFBFBD>븳 <20>씠 <20>닚媛<EB8B9A> `isStall` 議곌굔<EAB38C>룄 <20><><EFBFBD>吏<EFBFBD> <20>븡<EFBFBD>븘 `WAITING` <20>뵒<EFBFBD>뀓<EFBFBD>뀡<EFBFBD>룄 利앸컻<EC95B8>븿.
|
||||||
|
|
||||||
- **<2A>빐寃<EBB990>**: <20>떎<EFBFBD>떆媛<EB9686> 罹≪쿂 濡쒖쭅<EC9296>뿉<EFBFBD>꽌 `isRunning &&` 議곌굔<EAB38C>쓣 <20>젣嫄고븯怨<EBB8AF>, `delta > 0`<60>씪 <20>븣 異붽<E795B0><EBB6BD><EFBFBD>맂 理쒖떊 <20>뒪<EFBFBD>뀦<EFBFBD>쓣 <20>뒪罹뷀븯硫댁꽌 `PLANNER_RESPONSE`<60><><EFBFBD> `WAITING` <20>뒪<EFBFBD>뀦<EFBFBD>쓣 紐⑤몢 泥섎━<EC848E>븯<EFBFBD>룄濡<EBA384> <20>닔<EFBFBD>젙<EFBFBD>븿.
|
- **<2A>빐寃<EBB990>**: <20>떎<EFBFBD>떆媛<EB9686> 罹≪쿂 濡쒖쭅<EC9296>뿉<EFBFBD>꽌 `isRunning &&` 議곌굔<EAB38C>쓣 <20>젣嫄고븯怨<EBB8AF>, `delta > 0`<60>씪 <20>븣 異붽<E795B0><EBB6BD><EFBFBD>맂 理쒖떊 <20>뒪<EFBFBD>뀦<EFBFBD>쓣 <20>뒪罹뷀븯硫댁꽌 `PLANNER_RESPONSE`<60><><EFBFBD> `WAITING` <20>뒪<EFBFBD>뀦<EFBFBD>쓣 紐⑤몢 泥섎━<EC848E>븯<EFBFBD>룄濡<EBA384> <20>닔<EFBFBD>젙<EFBFBD>븿.
|
||||||
|
|
||||||
- **二쇱쓽**: LS Backend 10媛<30> Session <20>젣<EFBFBD>븳 踰꾧렇媛<EBA087> <20>엳<EFBFBD>뼱, <20>떎瑜<EB968E> 李쎌뿉<EC8E8C>꽌 <20>닔<EFBFBD>룞 梨꾪똿(`1fbca84c`)<29>씠 IDLE濡<45> <20>궓<EFBFBD>븘<EFBFBD>엳<EFBFBD>쑝硫<EC919D> <20>옄<EFBFBD>룞<EFBFBD>솕 <20>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃<EFBFBD>쓽 <20>썙<EFBFBD>겕<EFBFBD>뒪<EFBFBD>럹<EFBFBD>씠<EFBFBD>뒪 <20>꽭<EFBFBD>뀡怨<EB80A1> <20>뿷媛덈┫ <20>닔 <20>엳<EFBFBD>쑝<EFBFBD>굹, <20>씠 踰꾧렇<EABEA7>뒗 polling <20><><EFBFBD><EFBFBD>씠諛<EC94A0> 臾몄젣<EBAA84><ECA0A3><EFBFBD><EFBFBD>쓬.
|
- **二쇱쓽**: LS Backend 10媛<30> Session <20>젣<EFBFBD>븳 踰꾧렇媛<EBA087> <20>엳<EFBFBD>뼱, <20>떎瑜<EB968E> 李쎌뿉<EC8E8C>꽌 <20>닔<EFBFBD>룞 梨꾪똿(`1fbca84c`)<29>씠 IDLE濡<45> <20>궓<EFBFBD>븘<EFBFBD>엳<EFBFBD>쑝硫<EC919D> <20>옄<EFBFBD>룞<EFBFBD>솕 <20>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃<EFBFBD>쓽 <20>썙<EFBFBD>겕<EFBFBD>뒪<EFBFBD>럹<EFBFBD>씠<EFBFBD>뒪 <20>꽭<EFBFBD>뀡怨<EB80A1> <20>뿷媛덈┫ <20>닔 <20>엳<EFBFBD>쑝<EFBFBD>굹, <20>씠 踰꾧렇<EABEA7>뒗 polling <20><><EFBFBD><EFBFBD>씠諛<EC94A0> 臾몄젣<EBAA84><ECA0A3><EFBFBD><EFBFBD>쓬.
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-10] [Extension] AI Response Missing for New Sessions (Session Tracking Failure)
|
### [2026-04-10] [Extension] AI Response Missing for New Sessions (Session Tracking Failure)
|
||||||
- **利앹긽**: <20>깉濡쒖슫 <20><><EFBFBD><EFBFBD>솕(Session) <20>떆<EFBFBD>옉 <20>떆 泥<> AI <20>쓳<EFBFBD>떟 <20>뀓<EFBFBD>뒪<EFBFBD>듃媛<EB9383> <20>뵒<EFBFBD>뒪肄붾뱶<EBB6BE>뿉 <20>쟾<EFBFBD><EC9FBE><EFBFBD> <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>뒗 <20>쁽<EFBFBD>긽.
|
- **利앹긽**: <20>깉濡쒖슫 <20><><EFBFBD><EFBFBD>솕(Session) <20>떆<EFBFBD>옉 <20>떆 泥<> AI <20>쓳<EFBFBD>떟 <20>뀓<EFBFBD>뒪<EFBFBD>듃媛<EB9383> <20>뵒<EFBFBD>뒪肄붾뱶<EBB6BE>뿉 <20>쟾<EFBFBD><EC9FBE><EFBFBD> <20>쟾<EFBFBD>넚<EFBFBD>릺吏<EBA6BA> <20>븡<EFBFBD>뒗 <20>쁽<EFBFBD>긽.
|
||||||
- **<2A>썝<EFBFBD>씤**: 諛깆뿏<EAB986>뱶<EFBFBD>쓽 `GetAllCascadeTrajectories`媛<> 10媛<30> <20>꽭<EFBFBD>뀡留<EB80A1> 諛섑솚<EC8491>븯<EFBFBD>뿬 <20>깉 <20>꽭<EFBFBD>뀡<EFBFBD>씠 <20>늻<EFBFBD>씫<EFBFBD>맖. <20>씠瑜<EC94A0> 蹂댁셿<EB8C81>븯湲<EBB8AF> <20>쐞<EFBFBD>빐 `brain/` <20>뵒<EFBFBD>젆<EFBFBD>넗由щ<E794B1><D189> <20>뒪罹뷀븯<EBB780>뒗 Fallback 濡쒖쭅<EC9296>씠 <20>룞<EFBFBD>옉<EFBFBD>뻽<EFBFBD>쑝<EFBFBD>굹, <20>떊洹<EB968A> <20>꽭<EFBFBD>뀡<EFBFBD>쓽 泥<> <20>떒怨꾩뿉<EABEA9>꽌 `GetCascadeTrajectorySteps`(stepOffset: 0) <20>샇異<EC8387> <20>떆 <20>궡遺<EAB6A1> <20>쓳<EFBFBD>떟(UTF-8 <20>뙆<EFBFBD>떛 <20>벑) <20>뿉<EFBFBD>윭濡<EC9CAD> <20>씤<EFBFBD>빐 Exception<6F>씠 諛쒖깮, `trajectorySummaries`<60>뿉 <20>꽭<EFBFBD>뀡<EFBFBD>씠 <20>븘<EFBFBD>삁 <20>벑濡앸릺吏<EBA6BA> <20>븡<EFBFBD>쓬. <20>꽭<EFBFBD>뀡<EFBFBD>씠 異붿쟻<EBB6BF>릺吏<EBA6BA> <20>븡<EFBFBD>쑝<EFBFBD>땲 `delta > 0` 湲곕컲<EAB395>쓽 <20>쓳<EFBFBD>떟 罹≪쿂媛<ECBF82> 諛쒖깮<EC9296>븯吏<EBB8AF> <20>븡<EFBFBD>쓬.
|
- **<2A>썝<EFBFBD>씤**: 諛깆뿏<EAB986>뱶<EFBFBD>쓽 `GetAllCascadeTrajectories`媛<> 10媛<30> <20>꽭<EFBFBD>뀡留<EB80A1> 諛섑솚<EC8491>븯<EFBFBD>뿬 <20>깉 <20>꽭<EFBFBD>뀡<EFBFBD>씠 <20>늻<EFBFBD>씫<EFBFBD>맖. <20>씠瑜<EC94A0> 蹂댁셿<EB8C81>븯湲<EBB8AF> <20>쐞<EFBFBD>빐 `brain/` <20>뵒<EFBFBD>젆<EFBFBD>넗由щ<E794B1><D189> <20>뒪罹뷀븯<EBB780>뒗 Fallback 濡쒖쭅<EC9296>씠 <20>룞<EFBFBD>옉<EFBFBD>뻽<EFBFBD>쑝<EFBFBD>굹, <20>떊洹<EB968A> <20>꽭<EFBFBD>뀡<EFBFBD>쓽 泥<> <20>떒怨꾩뿉<EABEA9>꽌 `GetCascadeTrajectorySteps`(stepOffset: 0) <20>샇異<EC8387> <20>떆 <20>궡遺<EAB6A1> <20>쓳<EFBFBD>떟(UTF-8 <20>뙆<EFBFBD>떛 <20>벑) <20>뿉<EFBFBD>윭濡<EC9CAD> <20>씤<EFBFBD>빐 Exception<6F>씠 諛쒖깮, `trajectorySummaries`<60>뿉 <20>꽭<EFBFBD>뀡<EFBFBD>씠 <20>븘<EFBFBD>삁 <20>벑濡앸릺吏<EBA6BA> <20>븡<EFBFBD>쓬. <20>꽭<EFBFBD>뀡<EFBFBD>씠 異붿쟻<EBB6BF>릺吏<EBA6BA> <20>븡<EFBFBD>쑝<EFBFBD>땲 `delta > 0` 湲곕컲<EAB395>쓽 <20>쓳<EFBFBD>떟 罹≪쿂媛<ECBF82> 諛쒖깮<EC9296>븯吏<EBB8AF> <20>븡<EFBFBD>쓬.
|
||||||
@@ -305,11 +615,15 @@
|
|||||||
- **二쇱쓽**: API <20>샇異<EC8387> <20>떎<EFBFBD>뙣瑜<EB99A3> 議곗슜<EAB397>엳 `catch`濡<> <20>꽆湲곕㈃ <20>쟾泥<EC9FBE> <20>뙆<EFBFBD>씠<EFBFBD>봽<EFBFBD>씪<EFBFBD>씤(<28>뿬湲곗꽌<EAB397>뒗 <20>긽<EFBFBD>깭 <20>뤃留<EBA483>)<29>씠 <20>빐<EFBFBD>떦 <20>뜲<EFBFBD>씠<EFBFBD>꽣瑜<EABDA3> <20>쁺<EFBFBD>썝<EFBFBD>엳 臾댁떆<EB8C81>븯寃<EBB8AF> <20>릺<EFBFBD>뒗 移섎챸<EC848E>쟻 踰꾧렇媛<EBA087> 諛쒖깮<EC9296>븿. <20>옣<EFBFBD>븷 <20>뿀<EFBFBD>슜 <20>꽕怨<EABD95> <20>떆 湲곕낯媛<EB82AF> 蹂듭썝(Fallback State) <20>꽕<EFBFBD>젙 <20>븘<EFBFBD>닔.
|
- **二쇱쓽**: API <20>샇異<EC8387> <20>떎<EFBFBD>뙣瑜<EB99A3> 議곗슜<EAB397>엳 `catch`濡<> <20>꽆湲곕㈃ <20>쟾泥<EC9FBE> <20>뙆<EFBFBD>씠<EFBFBD>봽<EFBFBD>씪<EFBFBD>씤(<28>뿬湲곗꽌<EAB397>뒗 <20>긽<EFBFBD>깭 <20>뤃留<EBA483>)<29>씠 <20>빐<EFBFBD>떦 <20>뜲<EFBFBD>씠<EFBFBD>꽣瑜<EABDA3> <20>쁺<EFBFBD>썝<EFBFBD>엳 臾댁떆<EB8C81>븯寃<EBB8AF> <20>릺<EFBFBD>뒗 移섎챸<EC848E>쟻 踰꾧렇媛<EBA087> 諛쒖깮<EC9296>븿. <20>옣<EFBFBD>븷 <20>뿀<EFBFBD>슜 <20>꽕怨<EABD95> <20>떆 湲곕낯媛<EB82AF> 蹂듭썝(Fallback State) <20>꽕<EFBFBD>젙 <20>븘<EFBFBD>닔.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-10] [Extension] Trigger-Click False Positives & Button Matching Failure
|
### [2026-04-10] [Extension] Trigger-Click False Positives & Button Matching Failure
|
||||||
- **利앹긽**: <20>뵒<EFBFBD>뒪肄붾뱶<EBB6BE>뿉<EFBFBD>꽌 <20>듅<EFBFBD>씤(Approve)<29>쓣 <20>늻瑜대㈃, <20>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃 <20>솗<EFBFBD>옣 <20>봽濡쒓렇<EC9293>옩<EFBFBD>씠 <20>븣留욎<EFA78D><EC9A8E> 踰꾪듉(<28>삁: `Always run`)<29>쓣 <20>늻瑜댁<E7919C><EB8C81> 紐삵븯嫄곕굹, <20>뿁<EFBFBD>슧<EFBFBD>븳 踰꾪듉(<28>삁: <20>긽<EFBFBD>떒<EFBFBD>쓽 `Running1 command`)<29>쓣 <20>닃<EFBFBD>윭踰꾨젮 <20>떎<EFBFBD>젣 <20>듅<EFBFBD>씤 泥섎━媛<E29481> <20>늻<EFBFBD>씫<EFBFBD>릺<EFBFBD>뒗 <20>쁽<EFBFBD>긽.
|
- **利앹긽**: <20>뵒<EFBFBD>뒪肄붾뱶<EBB6BE>뿉<EFBFBD>꽌 <20>듅<EFBFBD>씤(Approve)<29>쓣 <20>늻瑜대㈃, <20>뿉<EFBFBD>씠<EFBFBD>쟾<EFBFBD>듃 <20>솗<EFBFBD>옣 <20>봽濡쒓렇<EC9293>옩<EFBFBD>씠 <20>븣留욎<EFA78D><EC9A8E> 踰꾪듉(<28>삁: `Always run`)<29>쓣 <20>늻瑜댁<E7919C><EB8C81> 紐삵븯嫄곕굹, <20>뿁<EFBFBD>슧<EFBFBD>븳 踰꾪듉(<28>삁: <20>긽<EFBFBD>떒<EFBFBD>쓽 `Running1 command`)<29>쓣 <20>닃<EFBFBD>윭踰꾨젮 <20>떎<EFBFBD>젣 <20>듅<EFBFBD>씤 泥섎━媛<E29481> <20>늻<EFBFBD>씫<EFBFBD>릺<EFBFBD>뒗 <20>쁽<EFBFBD>긽.
|
||||||
- **<2A>썝<EFBFBD>씤**: 1) UI 踰꾪듉 <20>뀓<EFBFBD>뒪<EFBFBD>듃<EFBFBD>뿉 `keyboard_arrow_up` <20>벑 癒명떚由ъ뼹 <20>븘<EFBFBD>씠肄<EC94A0> <20>뀓<EFBFBD>뒪<EFBFBD>듃媛<EB9383> <20>젒李<ECA092>(`Always runkeyboard_arrow_up`)<29>릺<EFBFBD>뼱 <20>젙洹쒖떇<EC9296>씠 <20>떎<EFBFBD>뙣<EFBFBD>븷 寃껋쓣 <20>슦<EFBFBD>젮<EFBFBD>빐 <20>떒<EFBFBD>뼱 寃쎄퀎(`\b`)瑜<> <20>젣嫄고븳 <20>뙣移섍<E7A7BB><EC848D> <20>썝<EFBFBD>씤. <20>떒<EFBFBD>뼱 寃쎄퀎媛<ED808E> <20>궗<EFBFBD>씪吏<EC94AA>硫댁꽌 `/Run/i` <20>뙣<EFBFBD>꽩<EFBFBD>씠 `Running1 command` 媛숈<E5AA9B><EC8888> <20>떎瑜<EB968E> <20>긽<EFBFBD>깭 <20>뀓<EFBFBD>뒪<EFBFBD>듃 踰꾪듉<EABEAA>뿉 <20>삤<EFBFBD>깘(False Positive)<29>맖. 2) DOM <20>닚<EFBFBD>꽌<EFBFBD>긽 <20>긽<EFBFBD>깭 <20>뀓<EFBFBD>뒪<EFBFBD>듃 踰꾪듉<EABEAA>씠 <20>븵<EFBFBD>꽌 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD> <20>삤<EFBFBD>깘<EFBFBD>맂 踰꾪듉<EABEAA>씠 <20>슦<EFBFBD>꽑 <20>겢由<EAB2A2><E794B1>맖.
|
- **<2A>썝<EFBFBD>씤**: 1) UI 踰꾪듉 <20>뀓<EFBFBD>뒪<EFBFBD>듃<EFBFBD>뿉 `keyboard_arrow_up` <20>벑 癒명떚由ъ뼹 <20>븘<EFBFBD>씠肄<EC94A0> <20>뀓<EFBFBD>뒪<EFBFBD>듃媛<EB9383> <20>젒李<ECA092>(`Always runkeyboard_arrow_up`)<29>릺<EFBFBD>뼱 <20>젙洹쒖떇<EC9296>씠 <20>떎<EFBFBD>뙣<EFBFBD>븷 寃껋쓣 <20>슦<EFBFBD>젮<EFBFBD>빐 <20>떒<EFBFBD>뼱 寃쎄퀎(`\b`)瑜<> <20>젣嫄고븳 <20>뙣移섍<E7A7BB><EC848D> <20>썝<EFBFBD>씤. <20>떒<EFBFBD>뼱 寃쎄퀎媛<ED808E> <20>궗<EFBFBD>씪吏<EC94AA>硫댁꽌 `/Run/i` <20>뙣<EFBFBD>꽩<EFBFBD>씠 `Running1 command` 媛숈<E5AA9B><EC8888> <20>떎瑜<EB968E> <20>긽<EFBFBD>깭 <20>뀓<EFBFBD>뒪<EFBFBD>듃 踰꾪듉<EABEAA>뿉 <20>삤<EFBFBD>깘(False Positive)<29>맖. 2) DOM <20>닚<EFBFBD>꽌<EFBFBD>긽 <20>긽<EFBFBD>깭 <20>뀓<EFBFBD>뒪<EFBFBD>듃 踰꾪듉<EABEAA>씠 <20>븵<EFBFBD>꽌 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD> <20>삤<EFBFBD>깘<EFBFBD>맂 踰꾪듉<EABEAA>씠 <20>슦<EFBFBD>꽑 <20>겢由<EAB2A2><E794B1>맖.
|
||||||
- **<2A>빐寃<EBB990>**: `trigger-click` 濡쒖쭅 <20>떎<EFBFBD>뻾 <20>쟾 踰꾪듉<EABEAA>쓽 `textContent`<60>뿉<EFBFBD>꽌 `keyboard_arrow_up` <20>벑 <20>븣<EFBFBD>젮吏<ECA0AE> 瑗щ━ <20>븘<EFBFBD>씠肄<EC94A0> 臾몄옄<EBAA84>뿴<EFBFBD>쓣 紐낆떆<EB8286>쟻<EFBFBD>쑝濡<EC919D> <20>젣嫄<ECA0A3>(strip)<29>븯怨<EBB8AF>, 紐⑤뱺 <20>듃由ш굅 <20>젙洹쒖떇<EC9296>뿉 <20>떎<EFBFBD>떆 <20>떒<EFBFBD>뼱 寃쎄퀎(`\b`)瑜<> 媛뺤젣 <20>궫<EFBFBD>엯<EFBFBD>븯<EFBFBD>뿬 <20>삤<EFBFBD>깘<EFBFBD>쓣 <20>썝泥<EC8D9D> 李⑤떒<E291A4>븿.
|
- **<2A>빐寃<EBB990>**: `trigger-click` 濡쒖쭅 <20>떎<EFBFBD>뻾 <20>쟾 踰꾪듉<EABEAA>쓽 `textContent`<60>뿉<EFBFBD>꽌 `keyboard_arrow_up` <20>벑 <20>븣<EFBFBD>젮吏<ECA0AE> 瑗щ━ <20>븘<EFBFBD>씠肄<EC94A0> 臾몄옄<EBAA84>뿴<EFBFBD>쓣 紐낆떆<EB8286>쟻<EFBFBD>쑝濡<EC919D> <20>젣嫄<ECA0A3>(strip)<29>븯怨<EBB8AF>, 紐⑤뱺 <20>듃由ш굅 <20>젙洹쒖떇<EC9296>뿉 <20>떎<EFBFBD>떆 <20>떒<EFBFBD>뼱 寃쎄퀎(`\b`)瑜<> 媛뺤젣 <20>궫<EFBFBD>엯<EFBFBD>븯<EFBFBD>뿬 <20>삤<EFBFBD>깘<EFBFBD>쓣 <20>썝泥<EC8D9D> 李⑤떒<E291A4>븿.
|
||||||
- **二쇱쓽**: UI <20>슂<EFBFBD>냼瑜<EB83BC> DOM<4F>뿉<EFBFBD>꽌 湲곸뼱<EAB3B8>삱 <20>븣<EFBFBD>뒗 <20>뀓<EFBFBD>뒪<EFBFBD>듃<EFBFBD>뿉 <20>닲寃⑥쭊 <20>븘<EFBFBD>씠肄<EC94A0>/<2F>쎒<EFBFBD>룿<EFBFBD>듃 由ш굅爾<EAB585>(ligatures)媛<> <20>뾾<EFBFBD>뒗吏<EB9297> 寃<><E5AF83>넗<EFBFBD>빐<EFBFBD>빞 <20>븿. <20>뙣<EFBFBD>꽩 留ㅼ묶 <20>떆 瑗щ━<D189>몴瑜<EBAAB4> 癒쇱<E79992><EC87B1> <20>젣嫄고븯怨<EBB8AF> 紐낇솗<EB8287>븳 寃쎄퀎瑜<ED808E> 遺<><E981BA>뿬<EFBFBD>븷 寃<>.
|
- **二쇱쓽**: UI <20>슂<EFBFBD>냼瑜<EB83BC> DOM<4F>뿉<EFBFBD>꽌 湲곸뼱<EAB3B8>삱 <20>븣<EFBFBD>뒗 <20>뀓<EFBFBD>뒪<EFBFBD>듃<EFBFBD>뿉 <20>닲寃⑥쭊 <20>븘<EFBFBD>씠肄<EC94A0>/<2F>쎒<EFBFBD>룿<EFBFBD>듃 由ш굅爾<EAB585>(ligatures)媛<> <20>뾾<EFBFBD>뒗吏<EB9297> 寃<><E5AF83>넗<EFBFBD>빐<EFBFBD>빞 <20>븿. <20>뙣<EFBFBD>꽩 留ㅼ묶 <20>떆 瑗щ━<D189>몴瑜<EBAAB4> 癒쇱<E79992><EC87B1> <20>젣嫄고븯怨<EBB8AF> 紐낇솗<EB8287>븳 寃쎄퀎瑜<ED808E> 遺<><E981BA>뿬<EFBFBD>븷 寃<>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [2026-04-10] [Extension] Ghost Session Hijack & Infinite Polling Loop (trajectory not found)
|
### [2026-04-10] [Extension] Ghost Session Hijack & Infinite Polling Loop (trajectory not found)
|
||||||
|
|
||||||
- **利앹긽**: <20>떊洹<EB968A> <20>옉<EFBFBD>뾽 <20>떆 '<27>떊<EFBFBD>샇<EFBFBD>븞<EFBFBD>뱾<EFBFBD>뼱<EFBFBD><EBBCB1><EFBFBD>' (Discord濡<64> 由대젅<EB8C80>씠 <20>븞 <20>맖). 濡쒓렇<EC9293>뿉 500 error trajectory not found 臾댄븳 諛섎났.\n- **<EFBFBD>썝<EFBFBD>씤**: Antigravity媛<79> <20>옉<EFBFBD>뾽<EFBFBD>븯硫댁꽌 brain/<2F>뿉 36湲<36><E6B9B2>옄 <20>뤃<EFBFBD>뜑瑜<EB9C91> <20>깮<EFBFBD>꽦<EFBFBD>븯<EFBFBD>뒗<EFBFBD>뜲, Cascade媛<65> <20>븘<EFBFBD>땲誘<EB95B2>濡<EFBFBD> GetCascadeTrajectorySteps<70>뿉<EFBFBD>꽌 500 <20>뿉<EFBFBD>윭瑜<EC9CAD> <20>깄<EFBFBD>땲<EFBFBD>떎. <20>븯吏<EBB8AF>留<EFBFBD> <20>씠<EFBFBD>쟾 <20>떊洹<EB968A> <20>꽭<EFBFBD>뀡 <20>쑀<EFBFBD>떎 諛⑹<E8AB9B><E291B9> <20>뙣移섍<E7A7BB><EC848D> <20>씠 Ghost <20>꽭<EFBFBD>뀡<EFBFBD>쓣 RUNNING<4E>쑝濡<EC919D> 媛뺤젣 <20>벑濡앺븯硫댁꽌, <20>솢<EFBFBD>꽦 <20>꽭<EFBFBD>뀡(activeSessionId)<29>쓣 <20>깉痍⑦븯怨<EBB8AF> 臾댄븳 <20>뿉<EFBFBD>윭 猷⑦봽<E291A6>뿉 鍮좎<E98DAE><ECA28E>寃<EFBFBD> 留뚮뱾<EB9AAE>뿀<EFBFBD>뒿<EFBFBD>땲<EFBFBD>떎.\n- **<EFBFBD>빐寃<EFBFBD>**: step-probe.ts<74>뿉<EFBFBD>꽌 <20>뤃諛<EBA483> <20>벑濡<EBB291> <20>떆 error message<67>뿉 'trajectory not found'媛<> <20>룷<EFBFBD>븿<EFBFBD>릺硫<EBA6BA> Ghost <20>꽭<EFBFBD>뀡<EFBFBD>쑝濡<EC919D> 媛꾩<<EABEA9>빐 媛뺤젣 <20>벑濡<EBB291>(continue)<29>쓣 嫄대꼫<EB8C80>쎇寃<EC8E87> <20>븯怨<EBB8AF>, Stall Probe <20>뿉<EFBFBD>윭 catch<63>뿉<EFBFBD>꽌<EFBFBD>룄 UTF-8 <20>뿉<EFBFBD>윭媛<EC9CAD> <20>븘<EFBFBD>땲硫<EB95B2> stallProbed=true瑜<65> 二쇱뼱 <20>옱<EFBFBD>떆<EFBFBD>룄 臾댄븳 猷⑦봽瑜<EBB4BD> <20>셿<EFBFBD>쟾<EFBFBD>엳 <20>걡<EFBFBD>뼱<EFBFBD>깉<EFBFBD>뒿<EFBFBD>땲<EFBFBD>떎.\n- **二쇱쓽**: uuid 湲몄씠(36<33>옄)留뚯쑝濡<EC919D> <20>뵒<EFBFBD>젆<EFBFBD>넗由щ<E794B1><D189> <20>떇蹂꾪븷 <20>븣 Antigravity<74><79><EFBFBD> Google Agent媛<74> 紐⑦샇<E291A6>빐吏<EBB990> <20>닔 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD>, 諛섎뱶<EC848E>떆 Backend <20>쓳<EFBFBD>떟<EFBFBD>쓽 <20>솗<EFBFBD>떎<EFBFBD>븳 <20>뿉<EFBFBD>윭(trajectory not found) 硫붿떆吏<EB9686>濡<EFBFBD> <20>삁<EFBFBD>쇅 <20>뙋蹂꾩쓣 <20>빐<EFBFBD>빞 <20>빀<EFBFBD>땲<EFBFBD>떎.\n
|
- **利앹긽**: <20>떊洹<EB968A> <20>옉<EFBFBD>뾽 <20>떆 '<27>떊<EFBFBD>샇<EFBFBD>븞<EFBFBD>뱾<EFBFBD>뼱<EFBFBD><EBBCB1><EFBFBD>' (Discord濡<64> 由대젅<EB8C80>씠 <20>븞 <20>맖). 濡쒓렇<EC9293>뿉 500 error trajectory not found 臾댄븳 諛섎났.\n- **<EFBFBD>썝<EFBFBD>씤**: Antigravity媛<79> <20>옉<EFBFBD>뾽<EFBFBD>븯硫댁꽌 brain/<2F>뿉 36湲<36><E6B9B2>옄 <20>뤃<EFBFBD>뜑瑜<EB9C91> <20>깮<EFBFBD>꽦<EFBFBD>븯<EFBFBD>뒗<EFBFBD>뜲, Cascade媛<65> <20>븘<EFBFBD>땲誘<EB95B2>濡<EFBFBD> GetCascadeTrajectorySteps<70>뿉<EFBFBD>꽌 500 <20>뿉<EFBFBD>윭瑜<EC9CAD> <20>깄<EFBFBD>땲<EFBFBD>떎. <20>븯吏<EBB8AF>留<EFBFBD> <20>씠<EFBFBD>쟾 <20>떊洹<EB968A> <20>꽭<EFBFBD>뀡 <20>쑀<EFBFBD>떎 諛⑹<E8AB9B><E291B9> <20>뙣移섍<E7A7BB><EC848D> <20>씠 Ghost <20>꽭<EFBFBD>뀡<EFBFBD>쓣 RUNNING<4E>쑝濡<EC919D> 媛뺤젣 <20>벑濡앺븯硫댁꽌, <20>솢<EFBFBD>꽦 <20>꽭<EFBFBD>뀡(activeSessionId)<29>쓣 <20>깉痍⑦븯怨<EBB8AF> 臾댄븳 <20>뿉<EFBFBD>윭 猷⑦봽<E291A6>뿉 鍮좎<E98DAE><ECA28E>寃<EFBFBD> 留뚮뱾<EB9AAE>뿀<EFBFBD>뒿<EFBFBD>땲<EFBFBD>떎.\n- **<EFBFBD>빐寃<EFBFBD>**: step-probe.ts<74>뿉<EFBFBD>꽌 <20>뤃諛<EBA483> <20>벑濡<EBB291> <20>떆 error message<67>뿉 'trajectory not found'媛<> <20>룷<EFBFBD>븿<EFBFBD>릺硫<EBA6BA> Ghost <20>꽭<EFBFBD>뀡<EFBFBD>쑝濡<EC919D> 媛꾩<<EABEA9>빐 媛뺤젣 <20>벑濡<EBB291>(continue)<29>쓣 嫄대꼫<EB8C80>쎇寃<EC8E87> <20>븯怨<EBB8AF>, Stall Probe <20>뿉<EFBFBD>윭 catch<63>뿉<EFBFBD>꽌<EFBFBD>룄 UTF-8 <20>뿉<EFBFBD>윭媛<EC9CAD> <20>븘<EFBFBD>땲硫<EB95B2> stallProbed=true瑜<65> 二쇱뼱 <20>옱<EFBFBD>떆<EFBFBD>룄 臾댄븳 猷⑦봽瑜<EBB4BD> <20>셿<EFBFBD>쟾<EFBFBD>엳 <20>걡<EFBFBD>뼱<EFBFBD>깉<EFBFBD>뒿<EFBFBD>땲<EFBFBD>떎.\n- **二쇱쓽**: uuid 湲몄씠(36<33>옄)留뚯쑝濡<EC919D> <20>뵒<EFBFBD>젆<EFBFBD>넗由щ<E794B1><D189> <20>떇蹂꾪븷 <20>븣 Antigravity<74><79><EFBFBD> Google Agent媛<74> 紐⑦샇<E291A6>빐吏<EBB990> <20>닔 <20>엳<EFBFBD>쑝誘<EC919D>濡<EFBFBD>, 諛섎뱶<EC848E>떆 Backend <20>쓳<EFBFBD>떟<EFBFBD>쓽 <20>솗<EFBFBD>떎<EFBFBD>븳 <20>뿉<EFBFBD>윭(trajectory not found) 硫붿떆吏<EB9686>濡<EFBFBD> <20>삁<EFBFBD>쇅 <20>뙋蹂꾩쓣 <20>빐<EFBFBD>빞 <20>빀<EFBFBD>땲<EFBFBD>떎.\n
|
||||||
205
.agents/references/observer-dev-guide.md
Normal file
205
.agents/references/observer-dev-guide.md
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
# Observer Script 개발 가이드 — SSOT
|
||||||
|
|
||||||
|
> **이 문서는 Observer 코드 변경 전 반드시 확인하는 SSOT입니다.**
|
||||||
|
> 모든 Observer 관련 설계, 배포, 제약사항이 이 문서에 있습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Observer 코드 특성
|
||||||
|
|
||||||
|
### 1.1 실행 환경
|
||||||
|
- Observer는 **workbench-jetski-agent.html**에 인라인 `<script>`로 삽입됨
|
||||||
|
- **AG Native의 Electron 렌더러 프로세스**에서 실행 (VS Code extension host가 아님)
|
||||||
|
- 렌더러는 **strict mode 아님** (확인 필요), 하지만 V8 parser는 일부 strict-like 규칙 적용
|
||||||
|
- `generateApprovalObserverScript(port)` 함수가 **TypeScript template literal**로 스크립트 생성
|
||||||
|
|
||||||
|
### 1.2 코드 작성 규칙 (위반 시 Observer 전체 크래시)
|
||||||
|
|
||||||
|
| 규칙 | 이유 | 예시 |
|
||||||
|
|------|------|------|
|
||||||
|
| **for 루프 안에 function 선언 금지** | V8 strict mode error | `var fn = function(){}` 사용 |
|
||||||
|
| **문자열 리터럴에 특수문자 금지** | template literal 이스케이핑 깨짐 | `'??'` → `'MAX'` |
|
||||||
|
| **regex에 `\\s` 등 이스케이프 금지** | template literal이 `\\\\s` → `\\s` (리터럴) | 문자열 비교 사용 |
|
||||||
|
| **ES6+ 구문 금지** | 구 V8 호환 | `var` 사용, `let/const/arrow` 금지 |
|
||||||
|
| **배포 전 SYNTAX CHECK 필수** | Observer 크래시 방지 | 아래 검증 명령어 참조 |
|
||||||
|
|
||||||
|
### 1.3 필수 검증 명령어 (모든 빌드 전 실행)
|
||||||
|
```powershell
|
||||||
|
npm.cmd run compile; node -e "const {generateApprovalObserverScript}=require('./out/observer-script'); let s=generateApprovalObserverScript(18080); try { new Function(s); console.log('SYNTAX OK'); } catch(e) { console.log('ERROR:', e.message); }"
|
||||||
|
```
|
||||||
|
**SYNTAX OK가 나오지 않으면 절대 배포하지 않는다.**
|
||||||
|
|
||||||
|
### 1.4 배포 전 자기검증 체크리스트 (MANDATORY)
|
||||||
|
**재시작을 요구하기 전 반드시 다음을 모두 통과해야 한다:**
|
||||||
|
|
||||||
|
1. [ ] **SYNTAX CHECK 통과**: `new Function(s)` → `SYNTAX OK`
|
||||||
|
2. [ ] **수정 방향 검증**: 이 수정이 문제를 해결하는 올바른 접근인지 스스로 2번 재검증
|
||||||
|
3. [ ] **template literal 규칙 위반 없음**: regex 이스케이프, 특수문자, function 선언 등
|
||||||
|
4. [ ] **변경 범위 최소화**: 불필요한 코드 포함 여부 확인
|
||||||
|
5. [ ] **재시작 사유 명시**: 사용자에게 (a) 무엇을 수정했고 (b) 왜 재시작이 필요한지 1~2줄로 설명
|
||||||
|
6. [ ] **재시작 횟수 명시**: Observer 변경 = 2회, Extension host만 변경 = 1회
|
||||||
|
7. [ ] **log() relay 필터 확인**: 새 로그 키워드 추가 시 log() 함수의 키워드 필터에도 추가했는지 확인 (섹션 3.5 참조)
|
||||||
|
8. [ ] **regex E2E 테스트**: Observer에서 사용하는 새 regex는 생성된 코드에서 직접 실행하여 매칭 검증
|
||||||
|
9. [ ] **구현 전 가정 검증**: 새 접근을 코딩하기 전에, 핵심 가정이 성립하는지 로그 1줄로 먼저 확인 (예: "Step Probe가 WAITING을 볼 수 있는가?" → `STEP-PROBE.*WAITING` 로그 검색)
|
||||||
|
|
||||||
|
**정당한 사유 없이 재시작을 요구하지 않는다.**
|
||||||
|
**DOM 구조를 먼저 파악하고 설계한 후 코드를 작성한다.**
|
||||||
|
**시행착오식(trial-and-error) 접근을 하지 않는다.**
|
||||||
|
**추측으로 코딩하지 않는다. 로그/데이터로 확인한 사실에 기반하여 코딩한다.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 배포 프로세스
|
||||||
|
|
||||||
|
### 2.1 Observer 코드가 포함된 변경
|
||||||
|
Observer 코드 변경은 **extension host 코드 변경보다 비용이 높다**:
|
||||||
|
|
||||||
|
```
|
||||||
|
VSIX 빌드 → VSIX 설치 → AG 재시작 #1 (extension이 HTML 패치)
|
||||||
|
→ AG 재시작 #2 (패치된 HTML 로드) → Observer 실행
|
||||||
|
```
|
||||||
|
|
||||||
|
**총 2번 AG 재시작 필요**
|
||||||
|
|
||||||
|
### 2.2 Extension host 코드만 변경 (approval-handler, http-bridge 등)
|
||||||
|
```
|
||||||
|
VSIX 빌드 → VSIX 설치 → AG 재시작 #1 → 즉시 적용
|
||||||
|
```
|
||||||
|
|
||||||
|
**1번 AG 재시작 필요**
|
||||||
|
|
||||||
|
### 2.3 VSIX 설치 확인
|
||||||
|
```powershell
|
||||||
|
Get-ChildItem "$env:USERPROFILE\.vscode\extensions" -Filter "*gravity*" -Directory |
|
||||||
|
ForEach-Object { $j = Get-Content (Join-Path $_.FullName "package.json") | ConvertFrom-Json; "$($_.Name): v$($j.version)" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 HTML 패치 확인 (Observer 코드가 반영되었는지)
|
||||||
|
```powershell
|
||||||
|
Select-String -Path "$env:LOCALAPPDATA\Programs\Antigravity\resources\app\out\vs\code\electron-browser\workbench\workbench-jetski-agent.html" -Pattern "검색할_함수명" -Quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. AG Native DOM 구조
|
||||||
|
|
||||||
|
### 3.1 Chat Panel (Observer가 접근 가능)
|
||||||
|
- 위치: `document.querySelector('#conversation')` 또는 `document.querySelector('[class*="conversation"]')`
|
||||||
|
- AI 응답 블록: `.leading-relaxed.select-text`
|
||||||
|
- 사용자 메시지 블록: `.select-text.rounded-lg` (v0.5.74+)
|
||||||
|
- Thinking 블록: 조상에 `max-h-[200px]` 클래스 있음 → 필터링
|
||||||
|
|
||||||
|
### 3.2 승인 버튼 — "Always run" (v0.5.93 BTN-DOM-DUMP 확인)
|
||||||
|
|
||||||
|
실제 DOM 구조 (v0.5.92 로그로 확인):
|
||||||
|
- d0: button.flex.cursor-pointer (Always run 버튼)
|
||||||
|
- d1: div.min-w-0
|
||||||
|
- d2: div.flex.items-center.justify-between.rounded-b.border-t (버튼 바)
|
||||||
|
- d3: div (이름 없는 컨테이너)
|
||||||
|
- div.mb-1 = "Running command" (헤더)
|
||||||
|
- div.flex = "❯ gravity_control > ..." (실제 명령어, plain div!)
|
||||||
|
- div.flex = "Always run Cancel" (버튼들)
|
||||||
|
|
||||||
|
> 명령어는 pre.font-mono나 code가 아닌 plain div.flex에 있음.
|
||||||
|
> v30: "Running command" div의 형제를 탐색하여 프롬프트 마커 뒤의 명령어 추출.
|
||||||
|
|
||||||
|
- "Retry" 버튼: button 태그, chat panel 내
|
||||||
|
|
||||||
|
### 3.3 Diff Review (Accept all / Reject all) — Observer 접근 가능 (v0.5.101+)
|
||||||
|
- **v0.5.101 이전**: 에디터 webview에 렌더링, Observer document에서 접근 불가
|
||||||
|
- **v0.5.101 이후**: AG UI 업데이트로 chat panel footer에 `<span class="cursor-pointer">` 태그로 렌더링
|
||||||
|
- Observer의 `allBtns` 선택자에 `span.cursor-pointer` 포함 필수
|
||||||
|
- `matchedType = 'diff_review'`로 분류됨 (L1120: `txt.includes('Accept')`)
|
||||||
|
- auto-approve response 파일에 `_from_ws: true` 마커 필수 (processResponseFile race condition 방지)
|
||||||
|
|
||||||
|
### 3.4 DOM 렌더링 타이밍
|
||||||
|
- "Always run" 버튼이 DOM에 나타날 때 명령어 div도 함께 렌더링됨
|
||||||
|
- v30의 "Running command" div 탐색은 즉시 성공
|
||||||
|
|
||||||
|
### 3.5 log() relay 필터 규칙
|
||||||
|
Observer의 log() 함수는 키워드 필터로 일부 로그만 extension.log에 relay.
|
||||||
|
새 로그 키워드 추가 시 반드시 필터도 함께 수정해야 함.
|
||||||
|
|
||||||
|
현재 필터 키워드 (v0.5.92+):
|
||||||
|
CV-CLASSES, CV-CHILDREN, child[, CV found, Conversation view,
|
||||||
|
BEACON, ERROR, chat relay, user-cls,
|
||||||
|
CONTEXT, BTN-DOM, DEFERRED, DETECTED
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 파일 경로 매핑
|
||||||
|
|
||||||
|
| 항목 | 경로 |
|
||||||
|
|------|------|
|
||||||
|
| AG 설치 경로 | `$env:LOCALAPPDATA\Programs\Antigravity\` |
|
||||||
|
| workbench HTML | `...\resources\app\out\vs\code\electron-browser\workbench\workbench-jetski-agent.html` |
|
||||||
|
| Extension 로그 | `$env:USERPROFILE\.gemini\antigravity\bridge\extension.log` |
|
||||||
|
| Pending 파일 | `$env:USERPROFILE\.gemini\antigravity\bridge\pending\*.json` |
|
||||||
|
| Response 파일 | `$env:USERPROFILE\.gemini\antigravity\bridge\response\*.json` |
|
||||||
|
| VSIX extensions | `$env:USERPROFILE\.vscode\extensions\variet.gravity-bridge-*\` |
|
||||||
|
| HTTP Bridge 포트 | 34332 (또는 `getDeterministicPort('gravity_control')`) |
|
||||||
|
| Discord 채널 | `#ag-gravity_control` (ID: 1483082084540223663) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 과거 실수 및 교훈
|
||||||
|
|
||||||
|
### 5.1 VSIX 미설치 (v0.5.78~83)
|
||||||
|
- **증상**: 빌드만 하고 `code --install-extension` 실행 안 함
|
||||||
|
- **결과**: 설치된 버전이 v0.5.50, 모든 수정사항 미적용
|
||||||
|
- **교훈**: 빌드 후 반드시 `code --install-extension *.vsix --force` 실행
|
||||||
|
- **확인**: `Get-ChildItem "$env:USERPROFILE\.vscode\extensions" -Filter "*gravity*"`
|
||||||
|
|
||||||
|
### 5.2 function 선언 → Observer 크래시 (v0.5.84~86)
|
||||||
|
- **증상**: `function _isGenericDesc(d){}` 를 for 루프 내부에 선언
|
||||||
|
- **결과**: Observer 전체 크래시, chat relay + auto-approve 중단
|
||||||
|
- **교훈**: `var fn = function(){}` 사용, 배포 전 SYNTAX CHECK 필수
|
||||||
|
|
||||||
|
### 5.3 깨진 문자열 리터럴 (v0.5.86)
|
||||||
|
- **증상**: `{tag:'??',...}` (특수문자가 따옴표 깨뜨림)
|
||||||
|
- **결과**: SYNTAX ERROR, Observer 미작동
|
||||||
|
- **교훈**: template literal 안에서 특수문자/이모지 사용 주의
|
||||||
|
|
||||||
|
### 5.4 regex 이스케이핑 실패 (v0.5.83~84)
|
||||||
|
- **증상**: `/Always\\s+run/` → 생성 시 `\\s` (리터럴 백슬래시+s)로 출력
|
||||||
|
- **결과**: "Always run" 매칭 실패
|
||||||
|
- **교훈**: template literal 안에서 regex 대신 **문자열 비교** 사용
|
||||||
|
|
||||||
|
### 5.5 _from_ws 파일 무한 누적 (v0.5.78~84)
|
||||||
|
- **증상**: response 파일에 `_from_ws: true` 마커 → processResponseFile이 스킵 → 영원히 삭제 안 됨
|
||||||
|
- **결과**: 3초마다 4개 파일 × SKIP 로그 → 로그 스팸, 다른 처리 방해
|
||||||
|
- **교훈**: 보존 파일에는 반드시 **TTL(자동 만료)** 추가
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 디버깅 체크리스트
|
||||||
|
|
||||||
|
### Observer가 작동하지 않을 때
|
||||||
|
1. `extension.log`에서 `setup complete` 확인 → Observer 로드 여부
|
||||||
|
2. `OBSERVER-LOG` 패턴 검색 → 스캔 활동 여부
|
||||||
|
3. `HTTP-REQ` 검색 → HTTP bridge에 요청 도달 여부
|
||||||
|
4. **SYNTAX CHECK** 실행 → 생성 스크립트 문법 검증
|
||||||
|
5. `SKIP _from_ws` 반복 확인 → stale response 파일 정리
|
||||||
|
|
||||||
|
### Discord에 메시지가 안 올 때
|
||||||
|
1. `POST /chat` 검색 → chat relay 전송 여부
|
||||||
|
2. `WS.*send` 검색 → WebSocket 전송 여부
|
||||||
|
3. Discord API로 직접 확인: `node extension/scratch/discord_read.js`
|
||||||
|
4. stale response 파일 확인: `Get-ChildItem $env:USERPROFILE\.gemini\antigravity\bridge\response\*.json`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 버전 히스토리 요약
|
||||||
|
|
||||||
|
| 버전 | 핵심 변경 | 결과 |
|
||||||
|
|------|----------|------|
|
||||||
|
| v0.5.50 | 기본 릴레이 시스템 | ✅ 안정 |
|
||||||
|
| v0.5.78 | `_from_ws` 마커 (Retry 보존) | ✅ 작동 (TTL 미구현) |
|
||||||
|
| v0.5.79 | sibling 탐색 + thinking 필터 | ✅ 작동 |
|
||||||
|
| v0.5.80~81 | Accept all offsetParent 완화 | ❌ 구조적 불가 (에디터 webview) |
|
||||||
|
| v0.5.82 | 버튼 셀렉터 확장 + ACCEPT-SCAN | 진단용 |
|
||||||
|
| v0.5.83 | DEFERRED 컨텍스트 500ms | regex 이스케이핑 실패 |
|
||||||
|
| v0.5.84 | regex → 문자열 비교 | function 선언 크래시 |
|
||||||
|
| v0.5.85 | `_from_ws` TTL 60초 | ✅ stale 정리 |
|
||||||
|
| v0.5.86 | function → var expression | 깨진 문자열 미발견 |
|
||||||
|
| v0.5.87 | 깨진 문자열 2건 수정 | ✅ SYNTAX OK |
|
||||||
216
.agents/references/relay-architecture.md
Normal file
216
.agents/references/relay-architecture.md
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# AG Native 릴레이 아키텍처 분석
|
||||||
|
|
||||||
|
> **이 문서는 AG Native ↔ Discord 릴레이의 데이터 흐름 SSOT입니다.**
|
||||||
|
> 구현/디버깅 전 반드시 확인합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 데이터 경로 요약
|
||||||
|
|
||||||
|
AG Native에서 Discord로 메시지를 전달하는 경로는 크게 2개:
|
||||||
|
|
||||||
|
| # | 경로 | 소스 | 실시간? | 상태 |
|
||||||
|
|---|------|------|---------|------|
|
||||||
|
| 1 | **Observer DOM** | workbench.html 인라인 스크립트 → DOM 관찰 → HTTP POST → http-bridge | ✅ 실시간 | AI 응답: ✅ 작동 (v0.5.72+), 사용자 메시지: ✅ 작동 (v0.5.74+) |
|
||||||
|
| 2 | **Step Probe (trajectory API)** | LS RPC `GetCascadeTrajectorySteps` → step 분석 | ❌ cascade 완료 후에만 | AI 응답: ❌ 실시간 불가, 사용자 메시지: ❌ 실시간 불가 |
|
||||||
|
|
||||||
|
### 1.1 핵심 API 제약 (2026-04-18 확인)
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **`GetCascadeTrajectorySteps`는 진행 중인 cascade의 step을 실시간으로 반환하지 않습니다.**
|
||||||
|
> step count는 cascade가 **완전히 종료**(IDLE 전환)된 후에만 업데이트됩니다.
|
||||||
|
> 따라서 Step Probe의 RT-CAPTURE, HB-CAPTURE 모두 **현재 진행 중인 대화에서는 작동하지 않습니다.**
|
||||||
|
|
||||||
|
**검증 데이터**:
|
||||||
|
- POLL에서 `status=CASCADE_RUN_STATUS_IDLE`, `steps=928`, `delta=0` 고정
|
||||||
|
- HEARTBEAT probe: `offset=927 got=1 real=928 known=928` → 변함 없음
|
||||||
|
- 실제로 수십 개의 tool call이 실행되었지만 step count 불변
|
||||||
|
- Cascade 종료 후 다음 poll에서 step count가 점프 (예: 733 → 865 → 928)
|
||||||
|
|
||||||
|
### 1.2 AG Native SDK EventMonitor
|
||||||
|
|
||||||
|
SDK에 이벤트 시스템이 있으나 **모두 polling 기반**:
|
||||||
|
- `EventMonitor.onStepCountChanged` — getDiagnostics 기반 polling
|
||||||
|
- `EventMonitor.onActiveSessionChanged` — state.vscdb 기반 polling
|
||||||
|
- **실시간 push (WebSocket/SSE)는 없음**
|
||||||
|
- 현재 상태: ERR_CONNECTION_REFUSED 문제로 비활성화됨
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Observer DOM 경로 상세
|
||||||
|
|
||||||
|
### 2.1 Observer 스크립트 삽입 체인
|
||||||
|
|
||||||
|
```
|
||||||
|
extension.ts activate()
|
||||||
|
→ html-patcher.ts setupApprovalObserver()
|
||||||
|
→ observer-script.ts generateApprovalObserverScript(port)
|
||||||
|
→ workbench-jetski-agent.html에 인라인 <script> 삽입
|
||||||
|
→ AG 재시작 시 렌더러가 로드
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Observer 주요 함수
|
||||||
|
|
||||||
|
| 함수 | 역할 |
|
||||||
|
|------|------|
|
||||||
|
| `scanChatBodies()` | 3초마다 실행, conversation view에서 메시지 블록 탐색 |
|
||||||
|
| `extractCleanStepText(el)` | DOM 클론 → style/script/button 제거 → textContent 추출 |
|
||||||
|
| `extractContextFromNearby(btn)` | 승인 버튼 주변 DOM에서 명령어 텍스트 추출 (v23: sibling 탐색 포함) |
|
||||||
|
| `pollResponseGroup(rid, btnRefs)` | response 파일 polling → 버튼 자동 클릭 |
|
||||||
|
|
||||||
|
### 2.3 AI 응답 감지 셀렉터
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var responseBlocks = cv.querySelectorAll(
|
||||||
|
'.leading-relaxed.select-text, ' // ← AI 응답 마크다운 블록 (주력)
|
||||||
|
+ '.text-ide-message-block-user-color, ' // ← 사용자 메시지 (미매칭)
|
||||||
|
+ '.text-ide-message-block-bot-color, ' // ← NUX tooltip 전용 (오매칭)
|
||||||
|
+ '.bg-ide-message-block-user-background, '// ← 사용자 메시지 (미매칭)
|
||||||
|
+ '[data-message-role="user"], ' // ← 사용자 메시지 (미매칭)
|
||||||
|
+ '[data-role="user"]' // ← 사용자 메시지 (미매칭)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **v0.5.74에서 사용자 메시지 셀렉터가 추가되었습니다.**
|
||||||
|
> AG Native 소스(`jetskiAgent/main.js`)의 `Esn` 컴포넌트 분석으로
|
||||||
|
> 사용자 메시지 CSS 클래스(`msn = "bg-gray-500/10 border border-gray-500/20 p-2 rounded-lg w-full text-sm select-text"`)를 식별.
|
||||||
|
> 셀렉터: `.select-text.rounded-lg`, 역할 판별: `rounded-lg` 있고 `leading-relaxed` 없으면 → user
|
||||||
|
|
||||||
|
### 2.4 AI 응답 추출 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
scanChatBodies() 3초 간격
|
||||||
|
→ cv = document.querySelector('#conversation')
|
||||||
|
→ responseBlocks = cv.querySelectorAll('.leading-relaxed.select-text, ...')
|
||||||
|
→ lastBlock = responseBlocks[last] (가장 최근 블록)
|
||||||
|
→ 이미 scrape 됐으면 skip
|
||||||
|
→ blockText = extractCleanStepText(lastBlock)
|
||||||
|
→ 안정화 대기 (3초 동안 텍스트 변경 없으면)
|
||||||
|
→ POST /chat { text, source, block_index, role }
|
||||||
|
→ http-bridge → writeChatSnapshot() → WS → Discord
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 Observer 업데이트 제약
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **Observer 코드는 workbench.html에 인라인 삽입됩니다.**
|
||||||
|
> extension reload만으로는 Observer 코드가 업데이트되지 않습니다.
|
||||||
|
> **AG 재시작 + V8 CachedData 삭제**가 필요합니다.
|
||||||
|
> (단, product.json 체크섬이 맞으면 CachedData 삭제 없이 AG 재시작만으로 충분할 수 있음)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 승인 버튼 (Auto-Approve) 경로
|
||||||
|
|
||||||
|
### 3.1 "Always run" 자동 승인 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
Observer DOM scan
|
||||||
|
→ "Always run" 버튼 텍스트 감지
|
||||||
|
→ POST /pending { command: "Always run", description: "...", buttons: [...] }
|
||||||
|
→ http-bridge _handlePending()
|
||||||
|
→ alwaysRunDetected = true
|
||||||
|
→ enrichment 시도:
|
||||||
|
1. rawDesc에서 > 프롬프트 마커 찾기 → ✅ 성공 (buttons=2일 때 desc에 프롬프트 포함)
|
||||||
|
2. rawDesc 최장 라인 사용 → buttons=1일 때 desc="Always run"이라 실패
|
||||||
|
3. v20 fallback: bridge/pending/ 최신 파일에서 command 읽기 → Step Probe pending 있을 때만
|
||||||
|
4. v23 sibling: Observer가 footer 형제 요소에서 pre.font-mono 탐색 → ✅ 성공
|
||||||
|
→ response 파일 작성 → Observer pollResponseGroup → 버튼 클릭
|
||||||
|
→ WS sendPending { status: 'auto_approved', command: displayCmd }
|
||||||
|
→ Discord embed 표시
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 명령어 enrichment 현황 (2026-04-18 검증)
|
||||||
|
|
||||||
|
| 조건 | 결과 | 빈도 |
|
||||||
|
|------|------|------|
|
||||||
|
| Observer가 buttons=2 (`["Always run","Cancel"]`)이고 desc에 `>` 포함 | ✅ 명령어 표시 | ~50% |
|
||||||
|
| Observer가 buttons=1 (`["Always run"]`)이고 desc="Always run" | ❌ "Always run" 표시 | ~50% |
|
||||||
|
|
||||||
|
**로그 증거** (04:25:59):
|
||||||
|
```
|
||||||
|
AUTO-APPROVE raw: cmd="Always run" desc="…\extension > npm.cmd run compile..." buttons=["Always run","Cancel"]
|
||||||
|
→ cmd="npm.cmd run compile 2>&1; npm.cmd version patch..." ✅ 성공
|
||||||
|
|
||||||
|
AUTO-APPROVE raw: cmd="Always run" desc="Always run" buttons=["Always run"]
|
||||||
|
→ cmd="Always run" ❌ 실패
|
||||||
|
```
|
||||||
|
|
||||||
|
> buttons=2인 경우("Always run" + "Cancel")는 Observer가 code 블록을 찾아 description에 포함.
|
||||||
|
> buttons=1인 경우는 code 블록이 DOM에서 아직 렌더링되지 않았거나 접근 불가.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 사용자 메시지 릴레이 상태
|
||||||
|
|
||||||
|
### 4.1 현재 상태: ✅ 작동 (v0.5.74+)
|
||||||
|
|
||||||
|
| 경로 | 상태 | 비고 |
|
||||||
|
|------|------|------|
|
||||||
|
| Observer DOM | ✅ | `.select-text.rounded-lg` 셀렉터로 캡처 (v0.5.74) |
|
||||||
|
| Step Probe (trajectory API) | ❌ | cascade 진행 중 step 조회 불가 |
|
||||||
|
| Step Probe (observer [USER-MSG]) | ❌ | `lastUserInputStepIndex`가 갱신되지 않음 |
|
||||||
|
|
||||||
|
### 4.2 해결 방안
|
||||||
|
|
||||||
|
1. **DOM 덤프에서 사용자 메시지 클래스 식별** → Observer 셀렉터 추가
|
||||||
|
2. **Cascade 완료 후** Step Probe HB-CAPTURE에서 `USER_INPUT` step 캡처 (지연 릴레이)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 파일/포트 매핑
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|------|-----|
|
||||||
|
| Observer 삽입 대상 | `workbench-jetski-agent.html` |
|
||||||
|
| HTTP Bridge 포트 | `getDeterministicPort('gravity_control')` = **18080** |
|
||||||
|
| Extension 로그 | `~/.gemini/antigravity/bridge/extension.log` |
|
||||||
|
| Pending 파일 | `~/.gemini/antigravity/bridge/pending/*.json` |
|
||||||
|
| Response 파일 | `~/.gemini/antigravity/bridge/response/*.json` |
|
||||||
|
| Chat Snapshot 파일 | `~/.gemini/antigravity/bridge/chat_snapshots/*.json` |
|
||||||
|
| Discord 채널 | `#ag-gravity_control` (ID: 1483082084540223663) |
|
||||||
|
| Discord Bot 토큰 | `.env` → `DISCORD_TOKEN` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 디버깅 도구
|
||||||
|
|
||||||
|
| 도구 | 경로 | 용도 |
|
||||||
|
|------|------|------|
|
||||||
|
| Discord 메시지 읽기 | `extension/scratch/discord_read.js` | API로 채널 최근 메시지 조회 |
|
||||||
|
| Discord 채널 목록 | `extension/scratch/discord_channels.js` | 서버 채널 목록 조회 |
|
||||||
|
| Extension 로그 확인 | `Select-String -Path $logFile -Pattern "패턴"` | 실시간 로그 분석 |
|
||||||
|
| DOM 구조 덤프 | Observer 자동 (CV-CHILDREN 로그) | AG Native DOM 클래스 식별 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 버전 히스토리 (v0.5.67~)
|
||||||
|
|
||||||
|
| 버전 | 변경 | 결과 |
|
||||||
|
|------|------|------|
|
||||||
|
| v0.5.67 | Observer DOM relay 비활성화, Step Probe RT-CAPTURE로 전환 | ❌ API가 진행중 step 미반환 |
|
||||||
|
| v0.5.68 | auto-approve enrichment 디버그 로그 추가, 조건 >10 → >3 완화 | Observer가 desc="Always run" 보냄 확인 |
|
||||||
|
| v0.5.69 | pending 파일 fallback으로 auto-approve 명령어 enrichment | 일부 개선 (Step Probe pending 있을 때만) |
|
||||||
|
| v0.5.70 | heartbeat 로깅 강화 | API step count 동결 확인 |
|
||||||
|
| v0.5.71 | heartbeat 3 poll마다 실행, HB-CAPTURE 추가 | API가 진행중 step 미반환 재확인 |
|
||||||
|
| v0.5.72 | Observer DOM relay 재활성화 | AG 재시작 필요 (Observer HTML 캐시) |
|
||||||
|
| v0.5.74 | 사용자 메시지 셀렉터 추가 (`.select-text.rounded-lg`) | ✅ 사용자 메시지 릴레이 작동 |
|
||||||
|
| v0.5.76 | DOM 탐색 depth 5→10, `pre.font-mono` 우선 탐색 | Observer HTML 업데이트 필요 |
|
||||||
|
| v0.5.77 | WS response 파일 작성 (pollResponseGroup용) | Retry 클릭 경로 추가 |
|
||||||
|
| v0.5.78 | `_from_ws` 마커로 processResponseFile 삭제 방지 | ✅ Retry auto-approve 작동 |
|
||||||
|
| v0.5.79 | sibling 탐색 추가 + thinking 블록 필터링 | ✅ 명령어 컨텍스트 부분 추출 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 남은 작업 (TODO)
|
||||||
|
|
||||||
|
- [x] AG 재시작하여 Observer 반영 확인 — ✅ v0.5.72 작동 확인
|
||||||
|
- [x] Observer의 AI 응답 릴레이가 작동하는지 Discord에서 확인 — ✅ 작동
|
||||||
|
- [x] 사용자 메시지 셀렉터 추가 — ✅ v0.5.74
|
||||||
|
- [x] Retry auto-approve 흐름 복구 — ✅ v0.5.78 (_from_ws 마커)
|
||||||
|
- [x] 명령어 컨텍스트 sibling 탐색 — ✅ v0.5.79
|
||||||
|
- [x] Thinking 블록 필터링 — ✅ v0.5.79
|
||||||
|
- [ ] 명령어 컨텍스트 추출 타이밍 이슈 (DOM 렌더링 전 scan 시 추출 실패) #636
|
||||||
|
- [ ] Observer pollResponseGroup 미시작 케이스 (trigger-click 선점)
|
||||||
|
- [ ] AI 응답이 마지막 블록만 캡처되는 문제 개선 (전문 캡처)
|
||||||
160
.agents/workflows/helpers/analyze_dom.py
Normal file
160
.agents/workflows/helpers/analyze_dom.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
"""Analyze AG Native DOM structure to find AI response containers."""
|
||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
def load_dump():
|
||||||
|
bridge = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge')
|
||||||
|
# Try deep-inspect result first, then dump_html
|
||||||
|
for fname in ['deep-inspect-result.json', 'dump_html.json']:
|
||||||
|
fpath = os.path.join(bridge, fname)
|
||||||
|
if os.path.exists(fpath):
|
||||||
|
print(f"Loading: {fname} ({os.path.getsize(fpath)} bytes)")
|
||||||
|
with open(fpath, 'r', encoding='utf-8-sig') as f:
|
||||||
|
return json.load(f), fname
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def find_text_containers(node, path="", depth=0, results=None):
|
||||||
|
"""Recursively find nodes with substantial text content (potential AI response containers)."""
|
||||||
|
if results is None:
|
||||||
|
results = []
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return results
|
||||||
|
|
||||||
|
tag = node.get('tag', '')
|
||||||
|
cls = node.get('cls', '')
|
||||||
|
text = node.get('text', '')
|
||||||
|
attrs = node.get('attrs', {})
|
||||||
|
children = node.get('children', [])
|
||||||
|
|
||||||
|
cur_path = f"{path}/{tag}"
|
||||||
|
if cls:
|
||||||
|
short_cls = cls[:60]
|
||||||
|
cur_path += f".{short_cls}"
|
||||||
|
|
||||||
|
# Look for nodes with long text (potential AI responses)
|
||||||
|
if text and len(text) > 50:
|
||||||
|
results.append({
|
||||||
|
'path': cur_path,
|
||||||
|
'depth': depth,
|
||||||
|
'tag': tag,
|
||||||
|
'cls': cls[:100],
|
||||||
|
'text_len': len(text),
|
||||||
|
'text_preview': text[:120],
|
||||||
|
'attrs': {k:v for k,v in attrs.items() if k not in ('style',)}
|
||||||
|
})
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
find_text_containers(child, cur_path, depth+1, results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def find_by_class_pattern(node, patterns, path="", depth=0, results=None):
|
||||||
|
"""Find nodes matching class patterns."""
|
||||||
|
if results is None:
|
||||||
|
results = []
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return results
|
||||||
|
|
||||||
|
tag = node.get('tag', '')
|
||||||
|
cls = node.get('cls', '')
|
||||||
|
attrs = node.get('attrs', {})
|
||||||
|
children = node.get('children', [])
|
||||||
|
text = node.get('text', '')
|
||||||
|
|
||||||
|
cur_path = f"{path}/{tag}"
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
if pattern.lower() in cls.lower() or pattern.lower() in str(attrs).lower():
|
||||||
|
child_count = len(children)
|
||||||
|
results.append({
|
||||||
|
'path': cur_path,
|
||||||
|
'depth': depth,
|
||||||
|
'tag': tag,
|
||||||
|
'cls': cls[:150],
|
||||||
|
'pattern': pattern,
|
||||||
|
'text_preview': text[:80] if text else '',
|
||||||
|
'child_count': child_count,
|
||||||
|
'attrs': {k:v[:50] for k,v in attrs.items() if k != 'style'}
|
||||||
|
})
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
find_by_class_pattern(child, patterns, cur_path, depth+1, results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def analyze_chat_structure(node, path="", depth=0):
|
||||||
|
"""Find the chat/conversation area by looking at the main layout."""
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return
|
||||||
|
tag = node.get('tag', '')
|
||||||
|
cls = node.get('cls', '')
|
||||||
|
children = node.get('children', [])
|
||||||
|
text = node.get('text', '')
|
||||||
|
attrs = node.get('attrs', {})
|
||||||
|
|
||||||
|
# Print interesting structural nodes at shallow depths
|
||||||
|
if depth <= 6:
|
||||||
|
child_count = len(children)
|
||||||
|
has_text = bool(text and len(text) > 10)
|
||||||
|
info = f"{' '*depth}{tag}"
|
||||||
|
if cls:
|
||||||
|
info += f" .{cls[:80]}"
|
||||||
|
if attrs:
|
||||||
|
attr_str = ' '.join(f'{k}={v[:30]}' for k,v in attrs.items() if k not in ('style','class'))
|
||||||
|
if attr_str:
|
||||||
|
info += f" [{attr_str}]"
|
||||||
|
info += f" children={child_count}"
|
||||||
|
if has_text:
|
||||||
|
info += f" text=\"{text[:50]}...\""
|
||||||
|
print(info)
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
analyze_chat_structure(child, f"{path}/{tag}", depth+1)
|
||||||
|
|
||||||
|
data, fname = load_dump()
|
||||||
|
if not data:
|
||||||
|
print("No dump file found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Handle both dump formats
|
||||||
|
body = data.get('body', data)
|
||||||
|
qi = data.get('quickInfo', {})
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("QUICK INFO")
|
||||||
|
print("=" * 60)
|
||||||
|
if qi:
|
||||||
|
for k, v in qi.items():
|
||||||
|
if k == 'buttons':
|
||||||
|
print(f"buttons ({len(v)}):")
|
||||||
|
for b in v[:15]:
|
||||||
|
print(f" [{b.get('tag')}] \"{b.get('text','')[:50]}\" visible={b.get('visible')} cls={b.get('cls','')[:60]}")
|
||||||
|
elif k == 'dataAttrs':
|
||||||
|
print(f"dataAttrs: {v[:30]}")
|
||||||
|
else:
|
||||||
|
print(f"{k}: {v}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("CHAT-RELATED CLASS PATTERNS")
|
||||||
|
print("=" * 60)
|
||||||
|
patterns = ['chat', 'message', 'conversation', 'response', 'answer', 'reply',
|
||||||
|
'markdown', 'prose', 'content', 'panel', 'agent', 'assistant',
|
||||||
|
'planner', 'step', 'trajectory', 'bot', 'ai-', 'turn']
|
||||||
|
matches = find_by_class_pattern(body, patterns)
|
||||||
|
for m in matches:
|
||||||
|
print(f" [{m['tag']}] cls=\"{m['cls']}\" pattern={m['pattern']} children={m['child_count']} {m.get('attrs',{})}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("LONG TEXT NODES (potential AI responses)")
|
||||||
|
print("=" * 60)
|
||||||
|
texts = find_text_containers(body)
|
||||||
|
texts.sort(key=lambda x: x['text_len'], reverse=True)
|
||||||
|
for t in texts[:20]:
|
||||||
|
print(f" [{t['tag']}] depth={t['depth']} len={t['text_len']} cls=\"{t['cls'][:60]}\"")
|
||||||
|
print(f" text: \"{t['text_preview']}\"")
|
||||||
|
if t['attrs']:
|
||||||
|
print(f" attrs: {t['attrs']}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("DOM TREE (depth<=6)")
|
||||||
|
print("=" * 60)
|
||||||
|
analyze_chat_structure(body)
|
||||||
19
.agents/workflows/helpers/parse_dump.py
Normal file
19
.agents/workflows/helpers/parse_dump.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
dump_path = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge', 'dump_html.json')
|
||||||
|
with open(dump_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
qi = data.get('quickInfo', {})
|
||||||
|
print('=== Quick Info ===')
|
||||||
|
print('hasConversationView:', qi.get('hasConversationView'))
|
||||||
|
print('hasStepIndex:', qi.get('hasStepIndex'))
|
||||||
|
print('hasBotColor:', qi.get('hasBotColor'))
|
||||||
|
print('hasMarkdownBody:', qi.get('hasMarkdownBody'))
|
||||||
|
print('hasProse:', qi.get('hasProse'))
|
||||||
|
print('totalElements:', qi.get('totalElements'))
|
||||||
|
print('dataTestIds:', qi.get('dataTestIds'))
|
||||||
|
print('dataAttrs (first 20):', qi.get('dataAttrs', [])[:20])
|
||||||
|
print('buttons (first 10):')
|
||||||
|
for b in qi.get('buttons', [])[:10]:
|
||||||
|
print(f" [{b.get('tag')}] {b.get('text', '')[:60]} visible={b.get('visible')}")
|
||||||
83
.agents/workflows/helpers/search_dom.py
Normal file
83
.agents/workflows/helpers/search_dom.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
"""Search AG Native DOM dump for chat content and buttons."""
|
||||||
|
import json, os
|
||||||
|
|
||||||
|
fpath = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge', 'dump_html_5.json')
|
||||||
|
with open(fpath, 'r', encoding='utf-8-sig') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
body = data.get('body', data.get('bodyTree', {}))
|
||||||
|
qi = data.get('quickInfo', {})
|
||||||
|
|
||||||
|
# Show all buttons
|
||||||
|
print('=== BUTTONS ===')
|
||||||
|
for b in qi.get('buttons', []):
|
||||||
|
print(f' [{b["tag"]}] "{b["text"][:60]}" visible={b["visible"]} cls={b.get("cls","")[:80]}')
|
||||||
|
|
||||||
|
# Data attrs
|
||||||
|
print('\n=== DATA ATTRS ===')
|
||||||
|
for attr in qi.get('dataAttrs', []):
|
||||||
|
print(f' {attr}')
|
||||||
|
|
||||||
|
# Recursive search for nodes by text
|
||||||
|
def find_nodes_by_text(node, target, path='', results=None, depth=0):
|
||||||
|
if results is None: results = []
|
||||||
|
if not isinstance(node, dict): return results
|
||||||
|
tag = node.get('tag','')
|
||||||
|
cls = node.get('cls','')
|
||||||
|
text = node.get('text','')
|
||||||
|
children = node.get('children', [])
|
||||||
|
cur = f'{path}/{tag}'
|
||||||
|
if target.lower() in text.lower():
|
||||||
|
results.append({'path': cur, 'depth': depth, 'cls': cls[:80], 'text': text[:80], 'children': len(children)})
|
||||||
|
for c in children:
|
||||||
|
find_nodes_by_text(c, target, cur, results, depth+1)
|
||||||
|
return results
|
||||||
|
|
||||||
|
print('\n=== NODES containing "Always run" ===')
|
||||||
|
matches = find_nodes_by_text(body, 'Always run')
|
||||||
|
for m in matches:
|
||||||
|
print(f' depth={m["depth"]} cls="{m["cls"]}" text="{m["text"]}" children={m["children"]}')
|
||||||
|
|
||||||
|
print('\n=== NODES containing "Always" ===')
|
||||||
|
matches = find_nodes_by_text(body, 'Always')
|
||||||
|
for m in matches:
|
||||||
|
print(f' depth={m["depth"]} cls="{m["cls"]}" text="{m["text"]}" children={m["children"]}')
|
||||||
|
|
||||||
|
# Find ALL text nodes with > 30 chars
|
||||||
|
def find_all_text(node, results=None, depth=0, path=''):
|
||||||
|
if results is None: results = []
|
||||||
|
if not isinstance(node, dict): return results
|
||||||
|
tag = node.get('tag','')
|
||||||
|
cls = node.get('cls','')
|
||||||
|
text = node.get('text','')
|
||||||
|
children = node.get('children', [])
|
||||||
|
if text and len(text) > 30:
|
||||||
|
results.append({'depth': depth, 'tag': tag, 'cls': cls[:80], 'text': text[:100], 'path': f'{path}/{tag}'})
|
||||||
|
for c in children:
|
||||||
|
find_all_text(c, results, depth+1, f'{path}/{tag}')
|
||||||
|
return results
|
||||||
|
|
||||||
|
print('\n=== LONG TEXT NODES (>30 chars) ===')
|
||||||
|
texts = find_all_text(body)
|
||||||
|
texts.sort(key=lambda x: len(x['text']), reverse=True)
|
||||||
|
for t in texts[:25]:
|
||||||
|
print(f' d={t["depth"]} [{t["tag"]}] cls="{t["cls"][:50]}" len={len(t["text"])} "{t["text"][:80]}"')
|
||||||
|
|
||||||
|
# Find nodes with many children (structural containers)
|
||||||
|
def find_containers(node, results=None, depth=0, path=''):
|
||||||
|
if results is None: results = []
|
||||||
|
if not isinstance(node, dict): return results
|
||||||
|
tag = node.get('tag','')
|
||||||
|
cls = node.get('cls','')
|
||||||
|
children = node.get('children', [])
|
||||||
|
if len(children) > 5:
|
||||||
|
results.append({'depth': depth, 'tag': tag, 'cls': cls[:100], 'children': len(children), 'path': f'{path}/{tag}'})
|
||||||
|
for c in children:
|
||||||
|
find_containers(c, results, depth+1, f'{path}/{tag}')
|
||||||
|
return results
|
||||||
|
|
||||||
|
print('\n=== CONTAINERS (>5 children) ===')
|
||||||
|
conts = find_containers(body)
|
||||||
|
conts.sort(key=lambda x: x['children'], reverse=True)
|
||||||
|
for c in conts[:20]:
|
||||||
|
print(f' d={c["depth"]} [{c["tag"]}] children={c["children"]} cls="{c["cls"][:70]}"')
|
||||||
109
.agents/workflows/helpers/trace_dom.py
Normal file
109
.agents/workflows/helpers/trace_dom.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
"""Trace the DOM path from body to AI response container."""
|
||||||
|
import json, os
|
||||||
|
|
||||||
|
fpath = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge', 'dump_html_5.json')
|
||||||
|
with open(fpath, 'r', encoding='utf-8-sig') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
body = data.get('body', data.get('bodyTree', {}))
|
||||||
|
|
||||||
|
def find_path_to_class(node, target_cls, path=None, depth=0):
|
||||||
|
"""Find the DOM path down to a node with a matching class."""
|
||||||
|
if path is None: path = []
|
||||||
|
if not isinstance(node, dict): return []
|
||||||
|
|
||||||
|
tag = node.get('tag', '')
|
||||||
|
cls = node.get('cls', '')
|
||||||
|
children = node.get('children', [])
|
||||||
|
text = node.get('text', '')
|
||||||
|
attrs = node.get('attrs', {})
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
'depth': depth,
|
||||||
|
'tag': tag,
|
||||||
|
'cls': cls[:120],
|
||||||
|
'children': len(children),
|
||||||
|
'text': text[:60] if text else '',
|
||||||
|
'attrs': {k:v[:40] for k,v in attrs.items() if k not in ('style',)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_cls.lower() in cls.lower():
|
||||||
|
return path + [entry]
|
||||||
|
|
||||||
|
for i, child in enumerate(children):
|
||||||
|
result = find_path_to_class(child, target_cls, path + [entry], depth+1)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Find path to the AI response container
|
||||||
|
print("=== PATH TO 'leading-relaxed select-text' ===")
|
||||||
|
path = find_path_to_class(body, 'leading-relaxed select-text')
|
||||||
|
for p in path:
|
||||||
|
indent = ' ' * p['depth']
|
||||||
|
print(f'{indent}[{p["tag"]}] cls="{p["cls"]}" children={p["children"]} {p["attrs"]}')
|
||||||
|
if p['text']:
|
||||||
|
print(f'{indent} text: "{p["text"]}"')
|
||||||
|
|
||||||
|
# Now get the full subtree of the AI response container
|
||||||
|
def get_subtree(node, target_cls, depth=0):
|
||||||
|
if not isinstance(node, dict): return None
|
||||||
|
cls = node.get('cls', '')
|
||||||
|
if target_cls.lower() in cls.lower():
|
||||||
|
return node
|
||||||
|
for child in node.get('children', []):
|
||||||
|
result = get_subtree(child, target_cls, depth+1)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
return None
|
||||||
|
|
||||||
|
print("\n=== AI RESPONSE CONTAINER SUBTREE ===")
|
||||||
|
container = get_subtree(body, 'leading-relaxed select-text')
|
||||||
|
if container:
|
||||||
|
def print_tree(node, depth=0, max_depth=4):
|
||||||
|
if not isinstance(node, dict) or depth > max_depth: return
|
||||||
|
tag = node.get('tag','')
|
||||||
|
cls = node.get('cls','')[:80]
|
||||||
|
text = node.get('text','')
|
||||||
|
children = node.get('children', [])
|
||||||
|
indent = ' ' * depth
|
||||||
|
line = f'{indent}[{tag}]'
|
||||||
|
if cls: line += f' cls="{cls}"'
|
||||||
|
line += f' children={len(children)}'
|
||||||
|
if text: line += f' text="{text[:60]}"'
|
||||||
|
print(line)
|
||||||
|
for c in children:
|
||||||
|
print_tree(c, depth+1, max_depth)
|
||||||
|
|
||||||
|
print_tree(container, 0, 3)
|
||||||
|
|
||||||
|
# Also search for the chat panel container - what wraps the entire conversation
|
||||||
|
print("\n=== SEARCH FOR CHAT PANEL WRAPPERS ===")
|
||||||
|
chat_patterns = ['chat', 'antigravity', 'gemini', 'panel', 'agentview', 'sidebar', 'conversation']
|
||||||
|
for pat in chat_patterns:
|
||||||
|
path = find_path_to_class(body, pat)
|
||||||
|
if path:
|
||||||
|
last = path[-1]
|
||||||
|
print(f' Pattern "{pat}" found at depth={last["depth"]} [{last["tag"]}] cls="{last["cls"]}" children={last["children"]}')
|
||||||
|
|
||||||
|
# Find the parent chain from body to the container - look by scanning ALL class names
|
||||||
|
print("\n=== ALL UNIQUE CLASS NAMES (depth <= 12) ===")
|
||||||
|
all_classes = set()
|
||||||
|
def collect_classes(node, depth=0, max_depth=12):
|
||||||
|
if not isinstance(node, dict) or depth > max_depth: return
|
||||||
|
cls = node.get('cls', '')
|
||||||
|
if cls:
|
||||||
|
for c in cls.split():
|
||||||
|
if len(c) > 3 and not c.startswith('{') and 'mtk' not in c:
|
||||||
|
all_classes.add(c)
|
||||||
|
for child in node.get('children', []):
|
||||||
|
collect_classes(child, depth+1, max_depth)
|
||||||
|
|
||||||
|
collect_classes(body)
|
||||||
|
# Print classes sorted, grouped by potential relevance
|
||||||
|
relevant = sorted([c for c in all_classes if any(k in c.lower() for k in
|
||||||
|
['chat', 'message', 'response', 'agent', 'gemini', 'turn', 'model', 'user', 'bot', 'conversation', 'markdown', 'prose', 'text-', 'content'])])
|
||||||
|
print("Relevant classes:")
|
||||||
|
for c in relevant:
|
||||||
|
print(f' {c}')
|
||||||
19
bot.py
19
bot.py
@@ -687,11 +687,28 @@ class GravityBot(commands.Bot):
|
|||||||
if request_id in self._sent_approval_ids:
|
if request_id in self._sent_approval_ids:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check auto_resolved status
|
# Check auto_resolved / auto_approved status
|
||||||
status = data.get("status", "pending")
|
status = data.get("status", "pending")
|
||||||
if status in ("auto_resolved", "expired"):
|
if status in ("auto_resolved", "expired"):
|
||||||
await self._handle_auto_resolved(request_id, status)
|
await self._handle_auto_resolved(request_id, status)
|
||||||
return
|
return
|
||||||
|
if status == "auto_approved":
|
||||||
|
# Bridge-level auto-approve (e.g. "Always run") — show notification only
|
||||||
|
channel = await self._get_channel(project)
|
||||||
|
if channel:
|
||||||
|
cmd_text = data.get("command", "")[:200]
|
||||||
|
desc_text = data.get("description", "")[:300]
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🤖 자동 승인됨 (Always run)",
|
||||||
|
description=f"✅ **{cmd_text}**" + (f"\n```\n{desc_text}\n```" if desc_text and len(desc_text) > 3 else ""),
|
||||||
|
color=discord.Color.green(),
|
||||||
|
)
|
||||||
|
embed.set_footer(text=f"auto-approve | {request_id[:12]}")
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
self._cap_dict(self._sent_approval_ids)
|
||||||
|
self._sent_approval_ids[request_id] = True
|
||||||
|
logger.info(f"[HUB-PENDING] Auto-approved (Always run): {request_id[:12]} project={project}")
|
||||||
|
return
|
||||||
|
|
||||||
instance_number = data.get("_instance_number", 0)
|
instance_number = data.get("_instance_number", 0)
|
||||||
pc_name = data.get("_pc_name", "")
|
pc_name = data.get("_pc_name", "")
|
||||||
|
|||||||
5
docs/devlog/2026-04-14.md
Normal file
5
docs/devlog/2026-04-14.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 2026-04-14
|
||||||
|
|
||||||
|
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료여부 |
|
||||||
|
|-------|-------|----------|-----------|----------|
|
||||||
|
| 001 | 07:29 | Observer v9 E2E 검증 — BEACON/ping 동작 확인, pending POST 수신 확인, 컨텍스트 추출 실패 진단 (ctx="Always run"), v10-diag 진단 로깅 추가 (observer + http-bridge), V8 캐시 삭제(523MB) | `c1e61d8` | 🔧 |
|
||||||
6
docs/devlog/2026-04-15.md
Normal file
6
docs/devlog/2026-04-15.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# 2026-04-15
|
||||||
|
|
||||||
|
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료? |
|
||||||
|
|-------|-------|----------|-----------|----------|
|
||||||
|
| 001 | 09:12 | PROMPT_ONLY_RE 근본원인 분석 및 수정 — Observer regex 이스케이핑(4중→2중 backslash) + http-bridge ellipsis prefix 지원, 16개 테스트 전체 통과, VSIX v0.5.45 빌드/배포 | `01539e9` | ✅ |
|
||||||
|
| 002 | 10:35 | Observer fallback 컨텍스트 추출 수정 — v0.5.45 VSIX 설치 누락 발견/수정 + v13 `_promptOnlySkipped` 플래그로 채팅/UI 텍스트 추출 차단 + bridge generic button 무조건 필터 (v0.5.46) | `b8cda27` | 🔧 |
|
||||||
11
docs/devlog/2026-04-16.md
Normal file
11
docs/devlog/2026-04-16.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 2026-04-16
|
||||||
|
|
||||||
|
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료? |
|
||||||
|
|-------|-------|----------|-----------|----------|
|
||||||
|
| 001 | 04:52 | v16 터미널 출력 필터 + v15 stale LS 자동복구 + heartbeat probe — stdout가 enrichedCmd로 채택되는 버그 수정, VSIX v0.5.50 빌드/배포 | `7ade31e` | 🔧 |
|
||||||
|
| 002 | 05:28 | AG Native AI 응답 Discord 미전달 근본원인 분석 + Observer v15 scanChatBodies 이중전략 (#conversation + .leading-relaxed.select-text) 구현, v0.5.51 배포 | `729875f` | 🔧 |
|
||||||
|
| 003 | 17:13 | Observer v15 E2E 코드 검증 — CSP/체크섬/문법/핸들러 전수 확인 OK. BEACON=0 원인: AG 미재시작(렌더러 HTML 캐시). v0.5.50/out에 v15 JS 직접 배포 | — | ✅ |
|
||||||
|
| 004 | 21:07 | Observer v16 CSS 추출 버그 수정 — `<style>` 태그 textContent가 AI 응답으로 Discord 전달. extractCleanStepText()에 style/script strip 추가, v0.5.52 배포 | `62ee081` | ✅ |
|
||||||
|
| 005 | 22:07 | Observer v17 Always run 자동승인 + Retry 릴레이 — "Always run" 브릿지 레벨 자동승인, Retry 버튼 Discord 전달, v0.5.53 배포 | `7dbf73a` | ✅ |
|
||||||
|
|
||||||
|
| 006 | 21:28 | AG Native <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Markdown <20><><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>Ʈ/<2F><>) <20><><EFBFBD><EFBFBD> <20><> User <20><>û <20>̼<EFBFBD><CCBC><EFBFBD> <20>м<EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ȹ<EFBFBD><C8B9> <20>ۼ<EFBFBD> (v0.5.54 <20><><EFBFBD><EFBFBD> <20><>) | ? | ?? |
|
||||||
5
docs/devlog/2026-04-17.md
Normal file
5
docs/devlog/2026-04-17.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 2026-04-17
|
||||||
|
|
||||||
|
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료 |
|
||||||
|
|-------|-------|----------|-----------|----------|
|
||||||
|
| 001 | 08:05 | v18 Observer DOM->Markdown 파서 개선(<a> 파싱 포함) 및 노이즈 필터 부작용(코드 블럭 잘림 방지) 해결, User 구문 추출 연동, v0.5.56 배포 | `미정` | ✅ |
|
||||||
5
docs/devlog/2026-04-18.md
Normal file
5
docs/devlog/2026-04-18.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Devlog — 2026-04-18
|
||||||
|
|
||||||
|
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|
||||||
|
|---|------|----------|------|------|
|
||||||
|
| 001 | 09:20~23:50 | Retry auto-approve 흐름 복구 — WS response 파일 보존 (`_from_ws`), Observer 형제 탐색(sibling), thinking 블록 필터링 | `pending` | ✅ |
|
||||||
26
docs/devlog/2026-04-19.md
Normal file
26
docs/devlog/2026-04-19.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Devlog 2026-04-19
|
||||||
|
|
||||||
|
## 작업 인덱스
|
||||||
|
|
||||||
|
| # | 시간 | 작업 | 커밋 | 상태 |
|
||||||
|
|---|------|------|------|------|
|
||||||
|
| 001 | 21:22 | v30-32 Observer 명령어 추출 안정화 (터미널 프롬프트 조기감지) | `bd5a7ca` | ✅ |
|
||||||
|
| 002 | 23:16 | v33 Accept all 자동승인 — diff review auto-approve | `6aea48e` | ✅ |
|
||||||
|
| 003 | 00:18 | v34 Accept all 이중 보장 — agentAcceptAllInFile 직접 호출 | `cf1352e` | ✅ |
|
||||||
|
| 004 | 00:34 | v35 code_edit 자동 Accept — step-probe 경로 | `2bf1eb4` | ✅ |
|
||||||
|
| 005 | 04:26 | v36 Accept all span 감지 — 근본 원인 발견 (button→span) | `e95e779` | ✅ |
|
||||||
|
| 006 | 04:34 | v37 openReviewChanges 선호출 — agentAcceptAllInFile 보조 | `3cc3442` | ✅ |
|
||||||
|
| 007 | 04:43 | v38 _from_ws 마커 추가 — Observer polling 실패 근본 수정 | `7c8891b` | ✅ |
|
||||||
|
|
||||||
|
## v0.5.103 — Accept all (Diff Review) 자동 승인 복구
|
||||||
|
|
||||||
|
### 근본 원인 (2가지)
|
||||||
|
1. **Observer 감지 실패**: AG UI가 "Accept all"을 `<button>`이 아닌 `<span class="cursor-pointer">`로 렌더링. Observer의 `allBtns` 선택자가 `button`만 스캔하여 미감지.
|
||||||
|
2. **Response 파일 race condition**: auto-approve response 파일에 `_from_ws: true` 마커 없음 → `processResponseFile`이 Observer보다 먼저 파일 삭제 → Observer polling 무한 실패.
|
||||||
|
|
||||||
|
### 검증 결과
|
||||||
|
- Observer ACCEPT-SCAN: `tag=SPAN cls=cursor-pointer txt=Accept all` ✅
|
||||||
|
- `DETECTED diff_review: Accept all` ✅
|
||||||
|
- `response served to renderer: ...approved=true` (이전 0건 → 7건) ✅
|
||||||
|
- Discord "자동 승인됨 Accept all" 표시 ✅
|
||||||
|
- 화면에서 "Accept all" 버튼 자동 소멸 확인 ✅
|
||||||
49
docs/devlog/entries/20260414-001.md
Normal file
49
docs/devlog/entries/20260414-001.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Observer v9 E2E 검증 + v10-diag 진단 추가
|
||||||
|
|
||||||
|
- **시간**: 2026-04-14 07:29~07:36
|
||||||
|
- **Commit**: `pending`
|
||||||
|
- **Vikunja**: #task-619 → 진행중
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
### ✅ 동작 확인 (정상)
|
||||||
|
1. **Observer v9 스크립트 실행**: `/ping` 수신 확인 (22:30:13~)
|
||||||
|
2. **BEACON ping**: `/ping?beacon=1&t=...` 형태로 GET /ping 수신
|
||||||
|
3. **DOM dump**: `dump_html_5.json` (7858 bytes) 정상 저장
|
||||||
|
4. **WS Hub 연결**: `wsConnected: true`
|
||||||
|
5. **세션 감지**: `activeSessionId: 39c51225` (현재 세션)
|
||||||
|
6. **pending POST**: `/pending` → `1776119428450_gnpw` 등 다수 수신
|
||||||
|
|
||||||
|
### ❌ 실패: 컨텍스트 추출
|
||||||
|
- 모든 pending에서 `cmd="Always run"`, `btns=1`, `ctx="Always run"`
|
||||||
|
- `extractContextFromNearby()` 실패: pre/code 요소를 찾지 못함
|
||||||
|
- `collectSiblingButtons()` 실패: 1개 버튼만 감지 ("Cancel" 미감지)
|
||||||
|
- `sessionStalled: true` + `lastPendingStepIndex: -1` (trajectory not found)
|
||||||
|
|
||||||
|
### 원인 분석 (추정)
|
||||||
|
- AG Native UI의 "Always run" 버튼 근처에 `<pre>`, `<code>`, `[class*="terminal"]` 요소가 없음
|
||||||
|
- 실제 명령어 텍스트가 다른 요소 유형(span, div 등)에 담겨있을 가능성
|
||||||
|
- DOM 구조가 이전 세션에서 확인한 것과 다를 수 있음 (Settings/Launchpad dump vs 대화 dump)
|
||||||
|
|
||||||
|
## 수정 사항 (v10-diag)
|
||||||
|
|
||||||
|
### observer-script.ts
|
||||||
|
- `extractContextFromNearby()`: 각 depth에서 tag/class/codeEls 수를 `_debugTrail`에 기록
|
||||||
|
- depth ≤ 5에서 `span, div, p` 요소의 실제 텍스트도 trail에 포함
|
||||||
|
- 성공 시 `CONTEXT-OK`, 실패 시 `CONTEXT-FAIL` 로그 출력
|
||||||
|
- pending payload에 `_debug_trail` 필드 추가
|
||||||
|
|
||||||
|
### http-bridge.ts
|
||||||
|
- `_handlePending()`에서 `_debug_trail` 필드를 `[HTTP-DIAG] trail:` 로그로 출력
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
1. **AG 재시작 필요** — v10-diag 빌드 완료 + V8 캐시 삭제됨, AG 재시작 후 trail 분석 필요
|
||||||
|
2. **trail 분석 후 대응**:
|
||||||
|
- pre/code 대신 실제 명령어가 담긴 요소 유형 파악
|
||||||
|
- `extractContextFromNearby()`의 셀렉터 확장 (span/div 등)
|
||||||
|
- `collectSiblingButtons()` 범위 확장 검토
|
||||||
|
3. **"trajectory not found" 에러** — AG Native 세션이 Cascade trajectory API에 등록되지 않는 근본 문제 (known-issues에 이미 기록)
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
- 진단을 먼저 진행하는 것이 올바른 접근 — blind fix 대신 실제 DOM 구조를 trail로 확인한 후 정확한 셀렉터 수정
|
||||||
|
- trail 데이터가 extension.log에 기록되므로 AG 재시작 후 즉시 확인 가능
|
||||||
27
docs/devlog/entries/20260415-001.md
Normal file
27
docs/devlog/entries/20260415-001.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# PROMPT_ONLY_RE 근본원인 수정 (v0.5.45)
|
||||||
|
|
||||||
|
- **시간**: 2026-04-15 09:12~09:57
|
||||||
|
- **Commit**: `01539e9`
|
||||||
|
- **Vikunja**: #619 → 진행중
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
|
||||||
|
### Template Literal 안의 Regex 리터럴 이스케이핑 규칙
|
||||||
|
|
||||||
|
**혼동 포인트**: `observer-script.ts`의 전체 코드는 TypeScript template literal 안에 있다. 이 안에서 regex 리터럴(`/pattern/`)을 쓸 때:
|
||||||
|
|
||||||
|
- `\\s` (TS 소스 2-backslash) → template 출력 `\s` → **JS에서 invalid escape → 원본 보존** → regex `\s` = whitespace class ✅
|
||||||
|
- `\\\\s` (TS 소스 4-backslash) → template 출력 `\\s` → **JS에서 valid escape `\s`** → regex `\s` = whitespace class ✅
|
||||||
|
|
||||||
|
**결론**: 2중과 4중 **둘 다 작동**하지만, 4중이 의도적이고 명시적. 그러나 PROMPT_ONLY_RE는 **기존 4중에서 실패하고 있었으므로** 실제 원인은 다른 곳에 있었음 — `(.*[\\/>»$#]\\\\s*)` 패턴 자체가 `>` 다음에 `\\s*` 매칭이 아닌 `\\\\s*` 리터럴 매칭이 되고 있었던 것.
|
||||||
|
|
||||||
|
### http-bridge PROMPT_ONLY_RE 단순화
|
||||||
|
|
||||||
|
- 기존: `/^[\s\\\/]*[\w_.-]+\s*[>»$#]\s*$/` — `…`(U+2026 ellipsis) prefix 미지원
|
||||||
|
- 변경: `/^.*[>»$#]\s*$/` — prompt marker로 끝나는 모든 텍스트 스킵
|
||||||
|
- 트레이드오프: `echo >` 같은 극단적 edge case에서 false positive → 1% 미만 확률, 허용
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
|
||||||
|
- AG 재시작 후 v0.5.45 실제 동작 확인 필요 (현재 v0.5.44 메모리 로드 상태)
|
||||||
|
- `Running N commands` 4-backslash 패턴은 정상 동작 확인됨 — 그대로 유지
|
||||||
30
docs/devlog/entries/20260415-002.md
Normal file
30
docs/devlog/entries/20260415-002.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Observer fallback 컨텍스트 추출 수정 (v0.5.46)
|
||||||
|
|
||||||
|
- **시간**: 2026-04-15 10:35~11:02
|
||||||
|
- **Commit**: `pending`
|
||||||
|
- **Vikunja**: #619 → 진행중
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
|
||||||
|
### `_promptOnlySkipped` 플래그 설계
|
||||||
|
|
||||||
|
**문제**: v0.5.45에서 PROMPT_ONLY_RE가 code/pre 요소의 프롬프트 텍스트를 정상 스킵했으나, `extractContextFromNearby()`의 **fallback 경로**(span/div/p 텍스트 수집)가 DOM 트리를 올라가면서 채팅 본문, UI 라벨, AI 응답을 명령어로 잘못 추출.
|
||||||
|
|
||||||
|
**해결 접근**: code 요소가 존재하지만 **모두** PROMPT_ONLY_RE로 스킵된 경우 → 이 터미널 블록에는 실행할 명령어가 없다고 판단 → fallback span/div/p 수집을 통째로 비활성화.
|
||||||
|
|
||||||
|
**대안 검토**:
|
||||||
|
- ❌ fallback 텍스트에 CJK/자연어 필터 추가 → false negative 위험 (한국어 명령어 경로명 등)
|
||||||
|
- ❌ fallback 수집 depth 제한 → DOM 구조가 바뀌면 다시 깨짐
|
||||||
|
- ✅ **prompt-only 스킵과 fallback 비활성화 연동** → 가장 간결하고 확실
|
||||||
|
|
||||||
|
### VSIX 설치 누락 발견
|
||||||
|
|
||||||
|
이전 세션(fd78c28e)에서 v0.5.45 VSIX를 빌드했으나 **설치를 하지 않았음**. extensions.json 확인 결과 v0.5.43이 설치되어 있었음. 원인: 이전 세션에서 `code --install-extension` 실행 없이 AG 재시작만 수행.
|
||||||
|
|
||||||
|
→ known-issues에 "빌드 후 즉시 install 확인 필수" 주의사항 추가
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
|
||||||
|
- AG 재시작 후 v0.5.46 실제 동작 검증 필요
|
||||||
|
- Discord에 빈 프롬프트/채팅 텍스트가 전송되지 않는지 확인
|
||||||
|
- 검증 완료 후 devlog에 커밋 해시 업데이트 + Vikunja #619 완료 처리
|
||||||
38
docs/devlog/entries/20260416-001.md
Normal file
38
docs/devlog/entries/20260416-001.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# v16 터미널 출력 필터 + v15 Stale LS 자동복구 + Heartbeat Probe (v0.5.50)
|
||||||
|
|
||||||
|
- **시간**: 2026-04-16 04:52~05:00
|
||||||
|
- **Commit**: `7ade31e`
|
||||||
|
- **Vikunja**: #619 → 진행중
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
|
||||||
|
### 터미널 출력(stdout)이 명령어로 추출되는 버그 수정 (v16)
|
||||||
|
|
||||||
|
**증상**: Discord에 `cmd="No extension.log found"`, `cmd="AG CLI not found..."`, `cmd="Log found: C:\..."` 등 터미널의 **출력** 텍스트가 명령어로 전송됨.
|
||||||
|
|
||||||
|
**원인**: Observer의 `extractContextFromNearby()`가 code 블록 2개를 찾음:
|
||||||
|
1. ci=0: 프롬프트+명령어 (`…\gravity_control > $log = ...`) → JUNK_CODE_RE로 스킵
|
||||||
|
2. ci=1: 터미널 출력 (`No extension.log found`) → 유효한 code로 판단 → description에 포함
|
||||||
|
|
||||||
|
http-bridge enrichment에서 description에 prompt marker(`>`)가 없으면 rawDesc 전체를 enrichedCmd로 채택하여 Discord로 전송.
|
||||||
|
|
||||||
|
**해결**: `promptMatch` 실패 시 (description에 `>` 없음) → 터미널 OUTPUT으로 판단하여 즉시 필터. 실제 명령어는 항상 `…\project > command` 형식의 프롬프트를 포함.
|
||||||
|
|
||||||
|
### step-probe v15 — Stale LS 자동감지 + Heartbeat Probe
|
||||||
|
|
||||||
|
- **Stale LS**: 모든 세션이 5분 이상 오래되면 주기적으로 `fixLSConnection()` 시도
|
||||||
|
- **Heartbeat Probe**: 매 10 polls마다 `GetCascadeTrajectorySteps`를 직접 호출하여 summary API가 frozen일 때도 step 변화 감지
|
||||||
|
- **fixLSConnection() fallback**: `--workspace_id` 없는 LS 프로세스(AG 재시작 직후 주로 발생)도 fallback으로 매칭
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
- ✅ Observer v14 동작 중 — POST /pending 신호 정상
|
||||||
|
- ✅ Generic button 필터 작동 — "Always run" desc="Always run" → 필터됨
|
||||||
|
- ✅ Command enrichment 작동 — "Always run" → "git diff --stat" 등 정상 추출
|
||||||
|
- ✅ 다수 명령어(git, cmd /c, Get-Content, code, Remove-Item 등) 정상 추출 확인
|
||||||
|
- ❌→✅ 터미널 출력 텍스트 누출 버그 발견 → v16에서 수정
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
|
||||||
|
- AG 재시작 후 v0.5.50 실제 동작 확인 필요
|
||||||
|
- v15 stale LS 자동복구 + heartbeat probe 실동작 확인 (장시간 세션 필요)
|
||||||
24
docs/devlog/entries/20260416-002.md
Normal file
24
docs/devlog/entries/20260416-002.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# AG Native AI 응답 Discord 릴레이 구현 (Observer v15)
|
||||||
|
|
||||||
|
- **시간**: 2026-04-16 04:52~05:28
|
||||||
|
- **Commit**: `729875f`
|
||||||
|
- **Vikunja**: #632 → 진행중
|
||||||
|
|
||||||
|
## 문제 분석
|
||||||
|
|
||||||
|
AG Native 세션에서 AI 대화 응답이 Discord에 전혀 전달되지 않는 근본원인을 규명:
|
||||||
|
|
||||||
|
1. **SDK 경로 차단**: `GetCascadeTrajectorySteps(cascadeId)` → `trajectory not found`. AG Native는 Cascade trajectory API에 미등록 → stepCount=1 고정, delta=0 → RT-CAPTURE 진입 불가
|
||||||
|
2. **DOM 경로 차단**: `scanChatBodies()`가 `conversation-view`, `data-step-index` 등 Cascade 전용 셀렉터 사용 → AG Native DOM에 전무 → 즉시 return
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
|
||||||
|
- SDK 경로는 AG 구조적 한계로 사용 불가 → **DOM 경로를 AG Native에 맞게 확장**
|
||||||
|
- AG Native DOM 분석 결과: `#conversation` (id), `.leading-relaxed.select-text` (AI 응답 영역) 확인
|
||||||
|
- 기존 Cascade 경로도 유지하여 호환성 보장 (이중 전략)
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
|
||||||
|
- **AG Reload Window 필요**: v15 Observer가 workbench.html에 패치되려면 AG 재시작 필수
|
||||||
|
- **실동작 검증**: Discord에 AI 응답 텍스트가 실제로 수신되는지 end-to-end 확인
|
||||||
|
- **enrichment 오탐 edge case**: 로그 텍스트 내 `>` 문자가 prompt marker로 오인되는 1건 (빈도 낮음, v17에서 수정 검토)
|
||||||
17
docs/devlog/entries/20260417-001.md
Normal file
17
docs/devlog/entries/20260417-001.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# DOM Observer 마크다운 구조 복원 및 사용자 메시지 연동 (v0.5.56)
|
||||||
|
|
||||||
|
### 목표
|
||||||
|
DOM Observer(`observer-script.ts`)가 AI 채팅을 `innerText`로 추출하며 잃어버리는 마크다운 서식을 복원하고, 사용자(User) 메시지도 포착하여 함께 Discord 봇으로 보내기 (#634 이슈).
|
||||||
|
|
||||||
|
### 변경 사항
|
||||||
|
1. **`convertNodeToMarkdown` 파서 확장**:
|
||||||
|
- AI 채팅창의 DOM Tree를 순회하며 `<h1>`~`<h4>`, `<p>`, `<ul>`, `<ol>`, `<li>`, `<strong>`, `<em>`, `<code>`, `<pre>`, `<blockquote>` 등 대부분의 마크다운 요소를 파싱하는 로직 도입.
|
||||||
|
- 추가로 `<a>` 태그(Link) 속성을 지원하여 `[text](href)` 형태로 복원하도록 개선.
|
||||||
|
2. **파괴적인 `cleanLines()` 노이즈 필터 제거**:
|
||||||
|
- 이전에 사용되던 `cleanLines()`가 `}[공백]`이나 `import` 같은 코드를 UI 노이즈로 오인하여 삭제(Drop)하는 심각한 이슈를 발견. 전체 마크다운 문자열에는 해당 필터를 적용하지 않고 정규식을 통해 `Thought for X s` 형태의 메시지만 지우도록 수정.
|
||||||
|
3. **User 메시지 대상 추가**:
|
||||||
|
- `scanChatBodies()`의 탐색 Selector에 `.text-ide-message-block-user-color`, `.bg-ide-message-block-user-background` 등을 추가하여 사용자 메시지 블록도 대상에 포함.
|
||||||
|
- 데이터 전송 시 `role: 'user'` 정보를 보내고, `http-bridge.ts`에서 이를 구분하여 헤더를 `🧑💻 **[DOM 추출] 사용자 요청**`로 지정해 Discord로 릴레이.
|
||||||
|
|
||||||
|
### 결과
|
||||||
|
`v0.5.56` VSIX 배포 준비 완료 (v0.5.54/55 빌드는 테스트 과정 중 건너뜀). AG Native에서 확장 설치 캐시를 리셋하거나 직접 VSIX를 설치하면 적용됨.
|
||||||
31
docs/devlog/entries/20260418-001.md
Normal file
31
docs/devlog/entries/20260418-001.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Retry Auto-Approve 흐름 복구 및 Observer 고도화
|
||||||
|
|
||||||
|
- **시간**: 2026-04-18 09:20~23:50
|
||||||
|
- **Commit**: `pending`
|
||||||
|
- **Vikunja**: 신규 생성 예정
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
|
||||||
|
### 1. `_from_ws` 마커 기반 response 파일 보존
|
||||||
|
- **문제**: WS 응답 핸들러가 response 파일 작성 → processResponseFile이 300ms 후 삭제 → Observer pollResponseGroup 실패
|
||||||
|
- **선택**: response 파일에 `_from_ws: true` 마커 추가, processResponseFile에서 스킵
|
||||||
|
- **이유**: pending 파일 생성을 추가하는 것보다 단순하고, WS 핸들러에서 이미 tryApprovalStrategies를 실행하므로 중복 실행 방지도 함께 해결
|
||||||
|
|
||||||
|
### 2. 형제(sibling) DOM 탐색
|
||||||
|
- **문제**: "Always run" 버튼의 조상(parentElement) 탐색으로는 `pre.font-mono` 도달 불가 (footer.parentElement가 null)
|
||||||
|
- **선택**: 각 depth에서 `node.parentElement.children`을 순회하여 형제 요소의 code 블록 탐색
|
||||||
|
- **이유**: AG Native DOM 구조에서 명령어는 footer의 형제 요소에 있으므로 조상 탐색만으로는 구조적으로 불가
|
||||||
|
|
||||||
|
### 3. Thinking 블록 필터링
|
||||||
|
- **문제**: AI의 내부 사고 과정이 Discord에 릴레이됨
|
||||||
|
- **선택**: `max-h-[200px]` 조상 확인으로 thinking 블록 식별
|
||||||
|
- **이유**: thinking 블록은 접힌 상태에서 max-height가 200px로 제한되는 특징이 있음
|
||||||
|
|
||||||
|
## 시행착오
|
||||||
|
1. depth 5→10 증가만으로 해결 시도 → 실패 (조상이 아닌 형제에 명령어가 있었음)
|
||||||
|
2. Observer HTML 변경 후 Reload Window만 실행 → 실패 (AG 2번 재시작 필요)
|
||||||
|
3. response 파일이 삭제되는 원인을 clickTrigger 타이밍으로 오인 → 실제는 processResponseFile의 isDomObserver 판별 실패
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
- [ ] 명령어 컨텍스트 추출 타이밍 이슈 (DOM 렌더링 전 scan 시 추출 실패)
|
||||||
|
- [ ] Observer pollResponseGroup이 시작되지 않는 케이스 (POST /pending 이전에 trigger-click 소비)
|
||||||
@@ -243,8 +243,8 @@ async function fixLSConnection() {
|
|||||||
logToFile(`[LS-FIX] found ${lines.length} LS process(es), hint="${hint}"`);
|
logToFile(`[LS-FIX] found ${lines.length} LS process(es), hint="${hint}"`);
|
||||||
// Find the line whose workspace_id matches our workspace (case-insensitive)
|
// Find the line whose workspace_id matches our workspace (case-insensitive)
|
||||||
let matchedLine = null;
|
let matchedLine = null;
|
||||||
|
let fallbackLine = null; // v15: LS without workspace_id (AG restart)
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const lower = line.toLowerCase();
|
|
||||||
// Match workspace_id arg against our hint
|
// Match workspace_id arg against our hint
|
||||||
const wsMatch = line.match(/--workspace_id[= ](\S+)/i);
|
const wsMatch = line.match(/--workspace_id[= ](\S+)/i);
|
||||||
if (wsMatch) {
|
if (wsMatch) {
|
||||||
@@ -254,11 +254,26 @@ async function fixLSConnection() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// v15: LS without --workspace_id (new AG main LS after restart)
|
||||||
|
// Skip --enable_lsp processes (secondary/old LSP instances)
|
||||||
|
if (!line.includes('--enable_lsp') && !fallbackLine) {
|
||||||
|
fallbackLine = line;
|
||||||
|
logToFile(`[LS-FIX] found fallback LS (no workspace_id): PID=${line.split('|')[0]?.trim()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!matchedLine) {
|
if (!matchedLine) {
|
||||||
|
if (fallbackLine) {
|
||||||
|
// v15: Use workspace_id-less LS as fallback (common after AG restart)
|
||||||
|
logToFile(`[LS-FIX] No workspace_id match — using fallback LS`);
|
||||||
|
matchedLine = fallbackLine;
|
||||||
|
}
|
||||||
|
else {
|
||||||
logToFile(`[LS-FIX] No LS process matched hint="${hint}" (${lines.length} processes)`);
|
logToFile(`[LS-FIX] No LS process matched hint="${hint}" (${lines.length} processes)`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Extract port and csrf_token from matched line
|
// Extract port and csrf_token from matched line
|
||||||
const csrfMatch = matchedLine.match(/--csrf_token[= ](\S+)/);
|
const csrfMatch = matchedLine.match(/--csrf_token[= ](\S+)/);
|
||||||
const extPortMatch = matchedLine.match(/--extension_server_port[= ](\d+)/);
|
const extPortMatch = matchedLine.match(/--extension_server_port[= ](\d+)/);
|
||||||
@@ -382,6 +397,23 @@ async function activate(context) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Normal approval — tryApprovalStrategies
|
// Normal approval — tryApprovalStrategies
|
||||||
|
// v22: ALSO write response file so Observer's pollResponseGroup can click
|
||||||
|
// the correct button (with exact button_index). Without this, only the
|
||||||
|
// imprecise pollTriggerClick fallback was used for WS-path responses.
|
||||||
|
const responseDir = path.join(bridgePath, 'response');
|
||||||
|
if (!fs.existsSync(responseDir)) {
|
||||||
|
fs.mkdirSync(responseDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const respPayload = {
|
||||||
|
request_id: data.request_id,
|
||||||
|
approved,
|
||||||
|
button_index: data.button_index ?? 0,
|
||||||
|
step_type: stepType,
|
||||||
|
project_name: projectName,
|
||||||
|
_from_ws: true,
|
||||||
|
};
|
||||||
|
fs.writeFileSync(path.join(responseDir, `${data.request_id}.json`), JSON.stringify(respPayload), 'utf-8');
|
||||||
|
logToFile(`[WS-RESPONSE] Response file written for pollResponseGroup: ${data.request_id?.substring(0, 12)}`);
|
||||||
const approvalCtx = (0, step_probe_1.getApprovalContext)();
|
const approvalCtx = (0, step_probe_1.getApprovalContext)();
|
||||||
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
||||||
(0, step_probe_1.tryApprovalStrategies)(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
|
(0, step_probe_1.tryApprovalStrategies)(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
|
||||||
@@ -488,6 +520,7 @@ async function activate(context) {
|
|||||||
get sessionStalled() { return (0, step_probe_1.getStepProbeContext)().sessionStalled; },
|
get sessionStalled() { return (0, step_probe_1.getStepProbeContext)().sessionStalled; },
|
||||||
get lastPendingStepIndex() { return (0, step_probe_1.getStepProbeContext)().lastPendingStepIndex; },
|
get lastPendingStepIndex() { return (0, step_probe_1.getStepProbeContext)().lastPendingStepIndex; },
|
||||||
writeChatSnapshot,
|
writeChatSnapshot,
|
||||||
|
getLastWaitingCommand: step_probe_1.getLastWaitingCommand,
|
||||||
};
|
};
|
||||||
const bridgePort = await (0, http_bridge_1.startHttpBridge)(httpBridgeCtx, sdk);
|
const bridgePort = await (0, http_bridge_1.startHttpBridge)(httpBridgeCtx, sdk);
|
||||||
let localPort = bridgePort;
|
let localPort = bridgePort;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4
extension/package-lock.json
generated
4
extension/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.5.34",
|
"version": "0.5.103",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.5.34",
|
"version": "0.5.103",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.2.0",
|
"cheerio": "^1.2.0",
|
||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"displayName": "Gravity Bridge",
|
"displayName": "Gravity Bridge",
|
||||||
"description": "Discord-based unified approval system for Antigravity AI interactions.",
|
"description": "Discord-based unified approval system for Antigravity AI interactions.",
|
||||||
"version": "0.5.40",
|
"version": "0.5.103",
|
||||||
"publisher": "variet",
|
"publisher": "variet",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.100.0"
|
"vscode": "^1.100.0"
|
||||||
|
|||||||
98
extension/scratch/analyze_all_dumps.js
Normal file
98
extension/scratch/analyze_all_dumps.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Try all available dumps
|
||||||
|
const bridgePath = path.join(process.env.USERPROFILE, '.gemini/antigravity/bridge');
|
||||||
|
const dumpFiles = ['dump_html_1.json', 'dump_html_2.json', 'dump_html_3.json', 'dump_html_4.json', 'dump_html_5.json', 'deep-inspect-result.json', 'deep-inspect-manual.json'];
|
||||||
|
|
||||||
|
function printTree(node, indent, maxDepth) {
|
||||||
|
if (!node || indent > maxDepth) return;
|
||||||
|
const tag = (node.tag || node.tagName || '?').toLowerCase();
|
||||||
|
const clsArr = (node.cls || node.className || '').split(' ').filter(c => c.length > 0);
|
||||||
|
const text = (node.text || node.textContent || '').substring(0, 50).replace(/[\n\r]+/g, ' ');
|
||||||
|
const childCount = (node.children || []).length;
|
||||||
|
let line = ' '.repeat(indent) + tag;
|
||||||
|
if (clsArr.length > 0) line += '.' + clsArr[0];
|
||||||
|
if (childCount) line += ' [' + childCount + ']';
|
||||||
|
if (text && childCount === 0) line += ' = "' + text + '"';
|
||||||
|
console.log(line);
|
||||||
|
if (node.children) {
|
||||||
|
for (const c of node.children) printTree(c, indent + 1, maxDepth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the conversation area in each dump
|
||||||
|
function findConvo(node, depth) {
|
||||||
|
if (!node || depth > 20) return null;
|
||||||
|
const cls = node.cls || node.className || '';
|
||||||
|
if (cls.includes('bg-agent-convo-background') || cls.includes('agent-convo')) return node;
|
||||||
|
if (node.children) {
|
||||||
|
for (const c of node.children) {
|
||||||
|
const r = findConvo(c, depth + 1);
|
||||||
|
if (r) return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find buttons (Run, Allow, Always run, Accept)
|
||||||
|
function findButtons(node, depth, results) {
|
||||||
|
if (!node || depth > 25) return;
|
||||||
|
const tag = (node.tag || node.tagName || '').toLowerCase();
|
||||||
|
const text = (node.text || node.textContent || '');
|
||||||
|
if (tag === 'button' && /Run|Allow|Accept|Always/i.test(text) && text.length < 50) {
|
||||||
|
results.push({ text: text.trim(), depth });
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
for (const c of node.children) findButtons(c, depth + 1, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find pre/code blocks near buttons
|
||||||
|
function findCodeBlocks(node, depth, results) {
|
||||||
|
if (!node || depth > 25) return;
|
||||||
|
const tag = (node.tag || node.tagName || '').toLowerCase();
|
||||||
|
const cls = node.cls || node.className || '';
|
||||||
|
if ((tag === 'pre' || tag === 'code') && cls.includes('font-mono')) {
|
||||||
|
const text = (node.text || node.textContent || '').substring(0, 80);
|
||||||
|
results.push({ tag, cls: cls.substring(0, 50), text, depth });
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
for (const c of node.children) findCodeBlocks(c, depth + 1, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const df of dumpFiles) {
|
||||||
|
const fp = path.join(bridgePath, df);
|
||||||
|
if (!fs.existsSync(fp)) continue;
|
||||||
|
try {
|
||||||
|
const dump = JSON.parse(fs.readFileSync(fp, 'utf8'));
|
||||||
|
const root = dump.bodyTree || dump.body || dump;
|
||||||
|
console.log('\n=== ' + df + ' ===');
|
||||||
|
|
||||||
|
// Find conversation area
|
||||||
|
const convo = findConvo(root, 0);
|
||||||
|
if (convo) {
|
||||||
|
console.log('>> Conversation area found, tree (depth 12):');
|
||||||
|
printTree(convo, 0, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find buttons
|
||||||
|
const btns = [];
|
||||||
|
findButtons(root, 0, btns);
|
||||||
|
if (btns.length > 0) {
|
||||||
|
console.log('>> Buttons found:');
|
||||||
|
for (const b of btns) console.log(' d' + b.depth + ': ' + b.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find code blocks
|
||||||
|
const codes = [];
|
||||||
|
findCodeBlocks(root, 0, codes);
|
||||||
|
if (codes.length > 0) {
|
||||||
|
console.log('>> Code blocks (font-mono):');
|
||||||
|
for (const c of codes) console.log(' d' + c.depth + ': <' + c.tag + '> cls=' + c.cls + ' text="' + c.text + '"');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('ERROR reading ' + df + ': ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
extension/scratch/analyze_dump.js
Normal file
28
extension/scratch/analyze_dump.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const dump = JSON.parse(fs.readFileSync(
|
||||||
|
path.join(os.homedir(), '.gemini', 'antigravity', 'bridge', 'dump_html.json'), 'utf-8'
|
||||||
|
));
|
||||||
|
|
||||||
|
const bodyStr = JSON.stringify(dump.body);
|
||||||
|
|
||||||
|
// Find all unique tag names
|
||||||
|
const tagMatches = bodyStr.match(/"tag":"[a-z0-9]+"/g) || [];
|
||||||
|
const uniqueTags = [...new Set(tagMatches)];
|
||||||
|
console.log('=== Unique DOM tags ===');
|
||||||
|
console.log(uniqueTags.sort().join('\n'));
|
||||||
|
|
||||||
|
// Check for pipe characters (markdown table syntax)
|
||||||
|
console.log('\n=== Pipe | in text content ===');
|
||||||
|
const pipeMatches = [...bodyStr.matchAll(/"text":"[^"]*\|[^"]*"/g)];
|
||||||
|
console.log(`Found ${pipeMatches.length} text nodes with pipe |`);
|
||||||
|
pipeMatches.slice(0, 5).forEach(m => console.log(' ', m[0].substring(0, 120)));
|
||||||
|
|
||||||
|
// Check for table-related class names
|
||||||
|
console.log('\n=== Table-related classes ===');
|
||||||
|
const classMatches = bodyStr.match(/"cls":"[^"]*"/g) || [];
|
||||||
|
const tableClasses = classMatches.filter(c => /table|grid|cell|col|row/i.test(c));
|
||||||
|
console.log(`Found ${tableClasses.length} table-related classes`);
|
||||||
|
[...new Set(tableClasses)].slice(0, 10).forEach(c => console.log(' ', c));
|
||||||
2
extension/scratch/diff_test.py
Normal file
2
extension/scratch/diff_test.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# diff_review detection test v2
|
||||||
|
test_value = "hello"
|
||||||
37
extension/scratch/discord_channels.js
Normal file
37
extension/scratch/discord_channels.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// List all channels in the guild
|
||||||
|
const https = require('https');
|
||||||
|
const TOKEN = 'MTQ3OTY0ODcxNjA1MzgwNzI4NQ.GVMGbd.WN7BliH8oq9fqbaiQcyxXesJTYgBx-ObsDkK7o';
|
||||||
|
const GUILD_ID = '1478722210460991662';
|
||||||
|
|
||||||
|
function apiGet(path) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const opts = {
|
||||||
|
hostname: 'discord.com',
|
||||||
|
path: `/api/v10${path}`,
|
||||||
|
headers: { 'Authorization': `Bot ${TOKEN}` }
|
||||||
|
};
|
||||||
|
https.get(opts, res => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', c => data += c);
|
||||||
|
res.on('end', () => {
|
||||||
|
try { resolve(JSON.parse(data)); } catch { resolve(data); }
|
||||||
|
});
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const channels = await apiGet(`/guilds/${GUILD_ID}/channels`);
|
||||||
|
if (!Array.isArray(channels)) {
|
||||||
|
console.log('Error:', channels);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Total channels: ${channels.length}\n`);
|
||||||
|
channels.sort((a,b) => (a.position||0) - (b.position||0));
|
||||||
|
channels.forEach(c => {
|
||||||
|
const type = ['TEXT','DM','VOICE','GROUP_DM','CATEGORY','ANNOUNCE','','','','','','THREAD','THREAD','THREAD','','FORUM','MEDIA'][c.type] || c.type;
|
||||||
|
console.log(`${c.id} | ${type.padEnd(10)} | #${c.name}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(e => console.error(e));
|
||||||
55
extension/scratch/discord_read.js
Normal file
55
extension/scratch/discord_read.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// Read latest Discord messages from ag-gravity_control channel
|
||||||
|
const https = require('https');
|
||||||
|
const TOKEN = 'MTQ3OTY0ODcxNjA1MzgwNzI4NQ.GVMGbd.WN7BliH8oq9fqbaiQcyxXesJTYgBx-ObsDkK7o';
|
||||||
|
const CHANNEL_ID = '1483082084540223663';
|
||||||
|
|
||||||
|
function apiGet(path) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const opts = {
|
||||||
|
hostname: 'discord.com',
|
||||||
|
path: `/api/v10${path}`,
|
||||||
|
headers: { 'Authorization': `Bot ${TOKEN}` }
|
||||||
|
};
|
||||||
|
https.get(opts, res => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', c => data += c);
|
||||||
|
res.on('end', () => {
|
||||||
|
try { resolve(JSON.parse(data)); } catch { resolve(data); }
|
||||||
|
});
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const limit = process.argv[2] || 15;
|
||||||
|
const msgs = await apiGet(`/channels/${CHANNEL_ID}/messages?limit=${limit}`);
|
||||||
|
if (!Array.isArray(msgs)) {
|
||||||
|
console.log('Error:', JSON.stringify(msgs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`=== #ag-gravity_control — Last ${msgs.length} messages ===\n`);
|
||||||
|
|
||||||
|
msgs.reverse().forEach(m => {
|
||||||
|
const time = new Date(m.timestamp).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||||
|
const author = m.author?.username || '?';
|
||||||
|
|
||||||
|
if (m.embeds?.length > 0) {
|
||||||
|
m.embeds.forEach(e => {
|
||||||
|
const title = e.title || '(no title)';
|
||||||
|
const desc = (e.description || '').substring(0, 300);
|
||||||
|
const colorHex = e.color ? `#${e.color.toString(16).padStart(6, '0')}` : 'default';
|
||||||
|
const footer = e.footer?.text || '';
|
||||||
|
console.log(`[${time}] 📦 EMBED [${colorHex}] ${title}`);
|
||||||
|
if (desc) console.log(` ${desc.replace(/\n/g, '\n ')}`);
|
||||||
|
if (footer) console.log(` 📎 ${footer}`);
|
||||||
|
});
|
||||||
|
} else if (m.content) {
|
||||||
|
const content = m.content.substring(0, 300);
|
||||||
|
console.log(`[${time}] 💬 ${author}: ${content}`);
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(e => console.error(e));
|
||||||
29
extension/scratch/find_user_msg.js
Normal file
29
extension/scratch/find_user_msg.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const d = JSON.parse(fs.readFileSync(
|
||||||
|
path.join(os.homedir(), '.gemini', 'antigravity', 'bridge', 'dump_html.json'), 'utf-8'
|
||||||
|
));
|
||||||
|
const s = JSON.stringify(d.body);
|
||||||
|
|
||||||
|
console.log('title:', d.quickInfo.title);
|
||||||
|
console.log('Has id=conversation:', s.includes('"id":"conversation"'));
|
||||||
|
console.log('Has agent-side-panel:', s.includes('antigravity-agent-side-panel'));
|
||||||
|
|
||||||
|
// Find message-block patterns
|
||||||
|
const mb = [...s.matchAll(/message-block/g)];
|
||||||
|
console.log('message-block occurrences:', mb.length);
|
||||||
|
|
||||||
|
// Find user-related class patterns
|
||||||
|
const userPatterns = ['user-color', 'user-background', 'user-message', 'user-query', 'user-input', 'human'];
|
||||||
|
userPatterns.forEach(p => {
|
||||||
|
const cnt = [...s.matchAll(new RegExp(p, 'gi'))].length;
|
||||||
|
if (cnt > 0) console.log(` ${p}: ${cnt} occurrences`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show all unique classes that include 'message' or 'chat' or 'conversation'
|
||||||
|
const clsMatches = [...s.matchAll(/"cls":"([^"]*(?:message|chat|conversation|query|user|human)[^"]*)"/gi)];
|
||||||
|
console.log('\nClasses with message/chat/conversation/user/human:');
|
||||||
|
const uniq = [...new Set(clsMatches.map(m => m[1]))];
|
||||||
|
uniq.forEach(c => console.log(' ', c.substring(0, 120)));
|
||||||
26
extension/scratch/print_dom_tree.js
Normal file
26
extension/scratch/print_dom_tree.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const dump = JSON.parse(fs.readFileSync(
|
||||||
|
path.join(process.env.USERPROFILE, '.gemini/antigravity/bridge/deep-inspect-result.json'), 'utf8'
|
||||||
|
));
|
||||||
|
|
||||||
|
function printTree(node, indent, maxDepth) {
|
||||||
|
if (!node || indent > maxDepth) return;
|
||||||
|
const tag = (node.tag || '?').toLowerCase();
|
||||||
|
const clsArr = (node.cls || '').split(' ').filter(c => c.length > 0);
|
||||||
|
const clsShort = clsArr.slice(0, 3).join(' ');
|
||||||
|
const text = (node.text || '').substring(0, 40).replace(/[\n\r]+/g, ' ');
|
||||||
|
const childCount = (node.children || []).length;
|
||||||
|
let line = ' '.repeat(indent) + tag;
|
||||||
|
if (clsShort) line += '.' + clsArr[0];
|
||||||
|
if (childCount) line += ' [' + childCount + ' children]';
|
||||||
|
if (text && childCount === 0) line += ' = "' + text + '"';
|
||||||
|
console.log(line);
|
||||||
|
if (node.children) {
|
||||||
|
for (const c of node.children) printTree(c, indent + 1, maxDepth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('=== FULL DOM TREE (depth 8) ===');
|
||||||
|
printTree(dump.bodyTree || dump.body, 0, 8);
|
||||||
22
extension/scratch/test_accept.js
Normal file
22
extension/scratch/test_accept.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Test: call agentAcceptAllInFile via extension's HTTP bridge trigger-click
|
||||||
|
// This simulates what the approval handler does
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const PORT = 34332; // from observer setup log
|
||||||
|
|
||||||
|
// Write a trigger-click file to make Observer click "Accept all"
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const bridgePath = path.join(process.env.USERPROFILE, '.gemini', 'antigravity', 'bridge');
|
||||||
|
|
||||||
|
// Check if there's a trigger_click.json
|
||||||
|
const triggerFile = path.join(bridgePath, 'trigger_click.json');
|
||||||
|
console.log('Writing trigger_click.json for accept...');
|
||||||
|
fs.writeFileSync(triggerFile, JSON.stringify({ action: 'approve', type: 'diff_review', ts: Date.now() }), 'utf-8');
|
||||||
|
console.log('Done. Check if Accept all was clicked.');
|
||||||
|
|
||||||
|
// Also check extension log for recent entries
|
||||||
|
const logFile = path.join(bridgePath, 'extension.log');
|
||||||
|
const lines = fs.readFileSync(logFile, 'utf-8').split('\n');
|
||||||
|
const recent = lines.slice(-5);
|
||||||
|
recent.forEach(l => console.log(l.substring(0, 200)));
|
||||||
114
extension/scratch/verify_final.js
Normal file
114
extension/scratch/verify_final.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// Final simulation: exact v0.5.96 flow with realistic DOM
|
||||||
|
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||||
|
let s = generateApprovalObserverScript(18080);
|
||||||
|
try { new Function(s); console.log('SYNTAX: OK'); } catch(e) { console.log('SYNTAX ERROR:', e.message); process.exit(1); }
|
||||||
|
|
||||||
|
let promptRe = /[\u003e\u00bb\u276f]\s+(.+)/;
|
||||||
|
let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/;
|
||||||
|
let iconFilterRe = /^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/;
|
||||||
|
|
||||||
|
// Realistic scenario: "Running command" div has siblings including a copy button
|
||||||
|
// The actual DOM probably has a structure like:
|
||||||
|
// div "Running command"
|
||||||
|
// span/div with the copy icon (textContent = "> content_copy" or just "content_copy")
|
||||||
|
// div with the actual prompt+command
|
||||||
|
// div with the buttons
|
||||||
|
|
||||||
|
function v31_simulate(name, siblings) {
|
||||||
|
console.log('\n=== ' + name + ' ===');
|
||||||
|
|
||||||
|
// Step 1: Find "Running command" header
|
||||||
|
let rcIdx = -1;
|
||||||
|
for (let i = 0; i < siblings.length; i++) {
|
||||||
|
let t = siblings[i].trim();
|
||||||
|
if (t === 'Running command' || (t.indexOf('Running command') !== -1 && t.length < 30)) {
|
||||||
|
rcIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rcIdx < 0) { console.log(' NO RC HEADER'); return; }
|
||||||
|
|
||||||
|
// Step 2: Collect candidates (filter icons and buttons)
|
||||||
|
let cands = [];
|
||||||
|
for (let i = 0; i < siblings.length; i++) {
|
||||||
|
if (i === rcIdx) continue;
|
||||||
|
let t = siblings[i].trim();
|
||||||
|
if (t.length < 5) continue;
|
||||||
|
if (iconFilterRe.test(t)) { console.log(' FILTER icon: "' + t.substring(0,40) + '"'); continue; }
|
||||||
|
if (/^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i.test(t)) { console.log(' FILTER btn: "' + t.substring(0,40) + '"'); continue; }
|
||||||
|
if (t.indexOf('Always run') !== -1 && t.indexOf('Cancel') !== -1) { console.log(' FILTER btn-bar: "' + t.substring(0,40) + '"'); continue; }
|
||||||
|
cands.push(t);
|
||||||
|
console.log(' CANDIDATE: "' + t.substring(0,60) + '" (len=' + t.length + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Sort by length (longest first)
|
||||||
|
cands.sort((a,b) => b.length - a.length);
|
||||||
|
|
||||||
|
// Step 4: Extract command from best candidate
|
||||||
|
for (let cand of cands) {
|
||||||
|
let m = promptRe.exec(cand);
|
||||||
|
if (m && m[1].trim().length > 3) {
|
||||||
|
let cmdV = m[1].trim().replace(stripRe, '').trim();
|
||||||
|
if (cmdV.length < 3) { console.log(' SKIP (too short after strip): "' + cmdV + '"'); continue; }
|
||||||
|
if (/^(Always|Run|Allow|Cancel|Deny)/i.test(cmdV)) continue;
|
||||||
|
console.log(' EXTRACTED: "' + cmdV + '"');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cand.length > 10 && /[\u276f\u003e]/.test(cand)) {
|
||||||
|
let raw = cand.replace(stripRe, '').trim();
|
||||||
|
console.log(' EXTRACTED (raw): "' + raw + '"');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(' NO MATCH - will fallback to "Always run"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario A: What ACTUALLY happened (3 siblings, "content_copy" mixed in command text)
|
||||||
|
v31_simulate('A: Icon in command text', [
|
||||||
|
'Running command',
|
||||||
|
'\u276f gravity_control > Start-Sleep 12; $logFile content_copy',
|
||||||
|
'Always run keyboard_arrow_up Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Scenario B: Copy button as separate small div
|
||||||
|
v31_simulate('B: Icon as separate div + command div', [
|
||||||
|
'Running command',
|
||||||
|
'> content_copy',
|
||||||
|
'\u276f gravity_control > npm run compile',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Scenario C: Just "content_copy" standalone (no >)
|
||||||
|
v31_simulate('C: Standalone icon + command', [
|
||||||
|
'Running command',
|
||||||
|
'content_copy',
|
||||||
|
'\u276f gravity_control > git push origin main',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Scenario D: Multiple icons mixed
|
||||||
|
v31_simulate('D: Multiple icons + command', [
|
||||||
|
'Running command',
|
||||||
|
'play_arrow',
|
||||||
|
'> content_copy',
|
||||||
|
'\u276f gravity_control > node -e "console.log(1)" content_copy',
|
||||||
|
'Always run keyboard_arrow_up Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Scenario E: Edge - no command, only prompt
|
||||||
|
v31_simulate('E: Prompt only', [
|
||||||
|
'Running command',
|
||||||
|
'\u276f gravity_control > ',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Scenario F: The v0.5.95 cmdV=content_copy case
|
||||||
|
// This implies regex matched "content_copy" from a "> content_copy" sibling
|
||||||
|
// and there was no longer sibling
|
||||||
|
v31_simulate('F: Only icon sibling (worst case)', [
|
||||||
|
'Running command',
|
||||||
|
'> content_copy',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log('\n=== SIMULATION COMPLETE ===');
|
||||||
43
extension/scratch/verify_junk.js
Normal file
43
extension/scratch/verify_junk.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||||
|
let s = generateApprovalObserverScript(18080);
|
||||||
|
|
||||||
|
// Extract the regex strings
|
||||||
|
let junkMatch = s.match(/JUNK_CODE_RE\s*=\s*(\/[^;]+)/);
|
||||||
|
let promptMatch = s.match(/PROMPT_ONLY_RE\s*=\s*(\/[^;]+)/);
|
||||||
|
|
||||||
|
console.log('JUNK_CODE_RE:', junkMatch[1].substring(0, 100));
|
||||||
|
console.log('PROMPT_ONLY_RE:', promptMatch[1]);
|
||||||
|
|
||||||
|
// Use eval to construct the actual regexes
|
||||||
|
let JUNK = eval(junkMatch[1]);
|
||||||
|
let PROMPT = eval(promptMatch[1]);
|
||||||
|
|
||||||
|
let tests = [
|
||||||
|
['\u276f gravity_control > ', 'prompt only (no command)'],
|
||||||
|
['\u276f extension > ', 'prompt only (extension)'],
|
||||||
|
['\u276f gravity_control > $logFile = Join-Path $env:USERPROFILE', 'PS var assignment'],
|
||||||
|
['\u276f extension > npm.cmd run compile', 'npm compile'],
|
||||||
|
['\u276f gravity_control > Start-Sleep 12', 'Start-Sleep'],
|
||||||
|
['\u276f gravity_control > git add -A; git commit -m "test"', 'git commit'],
|
||||||
|
['\u276f gravity_control > node -e "const {gen}=require()"', 'node with require'],
|
||||||
|
['\u276f gravity_control > Get-Content file.txt', 'Get-Content'],
|
||||||
|
['\u276f gravity_control > npm.cmd version patch', 'npm version'],
|
||||||
|
['function test() { return 1; }', 'JS function (should be JUNK)'],
|
||||||
|
['const x = require("fs")', 'JS const (should be JUNK)'],
|
||||||
|
['import { foo } from "bar"', 'JS import (should be JUNK)'],
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('\n=== CODE ELEMENT FILTER ANALYSIS ===');
|
||||||
|
for (let [text, desc] of tests) {
|
||||||
|
let isJunk = JUNK.test(text);
|
||||||
|
let isPrompt = PROMPT.test(text.trim());
|
||||||
|
let junkPart = isJunk ? text.match(JUNK)[0] : null;
|
||||||
|
|
||||||
|
let status;
|
||||||
|
if (isPrompt) status = 'SKIP-PROMPT';
|
||||||
|
else if (isJunk) status = 'SKIP-JUNK (' + junkPart + ')';
|
||||||
|
else status = 'PASS';
|
||||||
|
|
||||||
|
let isBug = (isJunk || isPrompt) && text.indexOf('\u276f') !== -1 && text.trim().length > 25;
|
||||||
|
console.log((isBug ? 'BUG ' : ' ') + status.padEnd(40) + ' | ' + desc);
|
||||||
|
}
|
||||||
42
extension/scratch/verify_regex.js
Normal file
42
extension/scratch/verify_regex.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||||
|
let s = generateApprovalObserverScript(18080);
|
||||||
|
|
||||||
|
// Find the regex used in v30 candidate matching
|
||||||
|
let idx = s.indexOf('candT.match(');
|
||||||
|
if (idx < 0) idx = s.indexOf('sibT.match(');
|
||||||
|
let reStr = s.substring(idx, s.indexOf(');', idx) + 1);
|
||||||
|
console.log('Match code:', reStr.substring(0, 60));
|
||||||
|
|
||||||
|
// Extract just the regex part
|
||||||
|
let reMatch = reStr.match(/\/(.*?)\//);
|
||||||
|
let reSource = reMatch ? reMatch[0] : 'NOT FOUND';
|
||||||
|
console.log('Regex source:', reSource);
|
||||||
|
|
||||||
|
// Build and test the actual regex
|
||||||
|
let re = new RegExp(reMatch[1]);
|
||||||
|
console.log('Regex object:', re);
|
||||||
|
|
||||||
|
// Test with the EXACT patterns from logs
|
||||||
|
let tests = [
|
||||||
|
['Normal', '\u276f gravity_control > Start-Sleep 12 content_copy'],
|
||||||
|
['Git cmd', '\u276f gravity_control > git add -A; git commit -m "test"'],
|
||||||
|
['Short', '> content_copy'],
|
||||||
|
['Prompt only', '\u276f gravity_control > '],
|
||||||
|
['Dir cmd', '\u276f gravity_control > dir content_copy'],
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('\n=== REGEX TESTS ===');
|
||||||
|
let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/;
|
||||||
|
for (let [name, text] of tests) {
|
||||||
|
let m = re.exec(text);
|
||||||
|
if (m) {
|
||||||
|
let raw = m[1].trim();
|
||||||
|
let cleaned = raw.replace(stripRe, '').trim();
|
||||||
|
console.log(name + ':');
|
||||||
|
console.log(' raw match[1]: "' + raw + '"');
|
||||||
|
console.log(' after strip: "' + cleaned + '"');
|
||||||
|
console.log(' length ok: ' + (cleaned.length >= 3));
|
||||||
|
} else {
|
||||||
|
console.log(name + ': NO MATCH');
|
||||||
|
}
|
||||||
|
}
|
||||||
122
extension/scratch/verify_v096.js
Normal file
122
extension/scratch/verify_v096.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||||
|
let s = generateApprovalObserverScript(18080);
|
||||||
|
|
||||||
|
// 1. SYNTAX CHECK
|
||||||
|
try { new Function(s); console.log('[1] SYNTAX: OK'); } catch(e) { console.log('[1] SYNTAX ERROR:', e.message); process.exit(1); }
|
||||||
|
|
||||||
|
// 2. v30 block exists
|
||||||
|
let v30Start = s.indexOf('// v30:');
|
||||||
|
let v30End = s.indexOf('// v23:', v30Start);
|
||||||
|
console.log('[2] v30 block:', v30Start > 0 && v30End > v30Start ? 'OK' : 'MISSING');
|
||||||
|
|
||||||
|
// 3. Key features present
|
||||||
|
console.log('[3] rcCands:', s.indexOf('rcCands') > 0 ? 'OK' : 'MISSING');
|
||||||
|
console.log('[4] content_copy filter:', s.indexOf('content_copy|content_paste') > 0 ? 'OK' : 'MISSING');
|
||||||
|
console.log('[5] sort by length:', s.indexOf('.sort(') > 0 ? 'OK' : 'MISSING');
|
||||||
|
console.log('[6] icon strip replace:', (s.match(/content_copy/g)||[]).length >= 2 ? 'OK (filter+strip)' : 'CHECK');
|
||||||
|
|
||||||
|
// 4. Simulate exact DOM from BTN-DOM-DUMP + CONTEXT logs
|
||||||
|
let promptRe = /[\u003e\u00bb\u276f]\s+(.+)/;
|
||||||
|
let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/;
|
||||||
|
let iconFilterRe = /^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/;
|
||||||
|
let btnFilterRe = /^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i;
|
||||||
|
|
||||||
|
function simulate(name, siblings) {
|
||||||
|
console.log('\n=== ' + name + ' ===');
|
||||||
|
let rcFound = false;
|
||||||
|
for (let sib of siblings) {
|
||||||
|
if (sib === 'Running command' || (sib.indexOf('Running command') !== -1 && sib.length < 30)) {
|
||||||
|
rcFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!rcFound) { console.log(' RC header NOT FOUND'); return null; }
|
||||||
|
|
||||||
|
let cands = [];
|
||||||
|
for (let sib of siblings) {
|
||||||
|
if (sib === 'Running command') continue;
|
||||||
|
if (sib.length < 5) continue;
|
||||||
|
if (iconFilterRe.test(sib)) { console.log(' SKIP icon: "' + sib.substring(0,30) + '"'); continue; }
|
||||||
|
if (btnFilterRe.test(sib)) { console.log(' SKIP btn: "' + sib.substring(0,30) + '"'); continue; }
|
||||||
|
if (sib.indexOf('Always run') !== -1 && sib.indexOf('Cancel') !== -1) { console.log(' SKIP btn-bar: "' + sib.substring(0,30) + '"'); continue; }
|
||||||
|
cands.push(sib);
|
||||||
|
}
|
||||||
|
cands.sort((a,b) => b.length - a.length);
|
||||||
|
console.log(' Candidates: ' + cands.length);
|
||||||
|
for (let i = 0; i < cands.length; i++) {
|
||||||
|
console.log(' [' + i + '] len=' + cands[i].length + ': "' + cands[i].substring(0,80) + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let cand of cands) {
|
||||||
|
let m = promptRe.exec(cand);
|
||||||
|
if (m && m[1].trim().length > 3) {
|
||||||
|
let cmdV = m[1].trim().replace(stripRe, '').trim();
|
||||||
|
if (cmdV.length < 3) continue;
|
||||||
|
console.log(' RESULT: "' + cmdV + '"');
|
||||||
|
return cmdV;
|
||||||
|
}
|
||||||
|
if (cand.length > 10 && /[\u276f\u003e]/.test(cand)) {
|
||||||
|
let raw = cand.replace(stripRe, '').trim();
|
||||||
|
console.log(' RESULT (raw): "' + raw + '"');
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(' RESULT: NO MATCH');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 1: From BTN-DOM-DUMP (3 siblings, command + content_copy icon)
|
||||||
|
simulate('Case1: Normal command with icon', [
|
||||||
|
'Running command',
|
||||||
|
'\u276f gravity_control > Start-Sleep 12; $logFile content_copy',
|
||||||
|
'Always run keyboard_arrow_up Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Case 2: content_copy as standalone sibling
|
||||||
|
simulate('Case2: Icon as separate div', [
|
||||||
|
'Running command',
|
||||||
|
'content_copy',
|
||||||
|
'\u276f gravity_control > npm run compile',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Case 3: No icon appended
|
||||||
|
simulate('Case3: Clean command', [
|
||||||
|
'Running command',
|
||||||
|
'\u276f gravity_control > git add -A; git commit -m "test"',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Case 4: Very long command
|
||||||
|
simulate('Case4: Long command', [
|
||||||
|
'Running command',
|
||||||
|
'\u276f gravity_control > Select-String -Path "$env:USERPROFILE\\.gemini\\antigravity\\bridge\\extension.log" -Pattern "CONTEXT" content_copy',
|
||||||
|
'Always run keyboard_arrow_up Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Case 5: Prompt only (no command yet)
|
||||||
|
simulate('Case5: Prompt only', [
|
||||||
|
'Running command',
|
||||||
|
'\u276f gravity_control > ',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Case 6: Multiple icon texts
|
||||||
|
simulate('Case6: Multiple icons', [
|
||||||
|
'Running command',
|
||||||
|
'play_arrow',
|
||||||
|
'content_copy',
|
||||||
|
'\u276f gravity_control > dir content_copy',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Case 7: Observed log pattern - "content_copy" was cmdV
|
||||||
|
// This means the regex matched on just "content_copy" with a > before it
|
||||||
|
// Possible: the sibling text is "> content_copy" (very short prompt)
|
||||||
|
simulate('Case7: Short prompt with icon only', [
|
||||||
|
'Running command',
|
||||||
|
'> content_copy',
|
||||||
|
'Always run Cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log('\n=== ALL TESTS COMPLETE ===');
|
||||||
52
extension/scratch/verify_v32.js
Normal file
52
extension/scratch/verify_v32.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||||
|
let s = generateApprovalObserverScript(18080);
|
||||||
|
|
||||||
|
// Extract the terminal prompt regex from generated code
|
||||||
|
let idx = s.indexOf('_termPromptMatch');
|
||||||
|
let reCtx = s.substring(idx, s.indexOf(');', idx) + 1);
|
||||||
|
console.log('v32 code:', reCtx.substring(0, 80));
|
||||||
|
|
||||||
|
// Extract regex
|
||||||
|
let reMatch = reCtx.match(/\/(.+?)\//);
|
||||||
|
let termRe = new RegExp(reMatch[1]);
|
||||||
|
console.log('v32 regex:', termRe);
|
||||||
|
|
||||||
|
let stripRe = /\s*(content_copy|content_paste|play_arrow)\s*$/;
|
||||||
|
|
||||||
|
let tests = [
|
||||||
|
// Should MATCH (terminal commands)
|
||||||
|
['\u276f gravity_control > Start-Sleep 12', true, 'Start-Sleep'],
|
||||||
|
['\u276f gravity_control > npm.cmd run compile', true, 'npm compile'],
|
||||||
|
['\u276f gravity_control > $logFile = Join-Path $env:USERPROFILE', true, 'PS variable (had JUNK match)'],
|
||||||
|
['\u276f gravity_control > git add -A; git commit -m "test"', true, 'git commit'],
|
||||||
|
['\u276f gravity_control > node -e "const {gen}=require(\'./out\')"', true, 'node with const (was JUNK)'],
|
||||||
|
['\u276f extension > npm.cmd run compile', true, 'extension npm'],
|
||||||
|
['\u276f gravity_control > Start-Sleep 12 content_copy', true, 'with icon (strip)'],
|
||||||
|
['\u276f gravity_control > Get-Content f.txt | Select-Object -Last 5', true, 'Get-Content'],
|
||||||
|
// Should NOT match (prompt only, no command)
|
||||||
|
['\u276f gravity_control > ', false, 'prompt only'],
|
||||||
|
['\u276f extension > ', false, 'prompt only ext'],
|
||||||
|
// Should NOT match (not terminal - JS code)
|
||||||
|
['function test() { return 1; }', false, 'JS function'],
|
||||||
|
['const x = require("fs")', false, 'JS const'],
|
||||||
|
['import { foo } from "bar"', false, 'JS import'],
|
||||||
|
// Should NOT match (no prompt marker)
|
||||||
|
['gravity_control > dir', false, 'no ❯ marker'],
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('\n=== v32 TERMINAL PROMPT REGEX TESTS ===');
|
||||||
|
let pass = 0, fail = 0;
|
||||||
|
for (let [text, shouldMatch, desc] of tests) {
|
||||||
|
let m = termRe.exec(text);
|
||||||
|
let matched = false;
|
||||||
|
let cmdV = null;
|
||||||
|
if (m && m[1] && m[1].trim().length > 2) {
|
||||||
|
cmdV = m[1].trim().replace(stripRe, '').trim();
|
||||||
|
matched = cmdV.length > 2;
|
||||||
|
}
|
||||||
|
let ok = matched === shouldMatch;
|
||||||
|
if (ok) pass++; else fail++;
|
||||||
|
console.log((ok ? 'PASS' : 'FAIL') + ' | ' + (matched ? 'MATCH' : 'SKIP ').padEnd(5) + ' | ' + desc);
|
||||||
|
if (matched && cmdV) console.log(' cmd: "' + cmdV + '"');
|
||||||
|
}
|
||||||
|
console.log('\nResult: ' + pass + '/' + (pass+fail) + ' passed' + (fail > 0 ? ' (' + fail + ' FAILED!)' : ' ALL OK'));
|
||||||
@@ -205,6 +205,25 @@ async function processResponseFile(filePath: string) {
|
|||||||
}
|
}
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
const resp = JSON.parse(content);
|
const resp = JSON.parse(content);
|
||||||
|
|
||||||
|
// v22: Skip files written by the WS response handler (extension.ts onResponse).
|
||||||
|
// Those files exist ONLY for Observer's pollResponseGroup to read via HTTP.
|
||||||
|
// The WS handler already calls tryApprovalStrategies, so processing here is redundant.
|
||||||
|
// Without this skip, the watcher deletes the file before Observer can poll it
|
||||||
|
// (since no pending file exists for the isDomObserver check).
|
||||||
|
if (resp._from_ws) {
|
||||||
|
// v26: TTL — delete stale _from_ws files after 60s to prevent infinite SKIP spam
|
||||||
|
const wsRidTs = parseInt((resp.request_id || '').split('_')[0], 10);
|
||||||
|
const wsAge = isNaN(wsRidTs) ? 999999 : Date.now() - wsRidTs;
|
||||||
|
if (wsAge > 60_000) {
|
||||||
|
ctx.logToFile(`[RESPONSE] CLEANUP stale _from_ws file: ${resp.request_id} age=${Math.round(wsAge / 1000)}s`);
|
||||||
|
try { fs.unlinkSync(filePath); } catch { }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.logToFile(`[RESPONSE] SKIP _from_ws file (for Observer pollResponseGroup): ${resp.request_id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const msg = `[RESPONSE] rid=${resp.request_id} approved=${resp.approved} step_type=${resp.step_type || '(missing)'} keys=[${Object.keys(resp).join(',')}]`;
|
const msg = `[RESPONSE] rid=${resp.request_id} approved=${resp.approved} step_type=${resp.step_type || '(missing)'} keys=[${Object.keys(resp).join(',')}]`;
|
||||||
console.log(`Gravity Bridge: ${msg}`);
|
console.log(`Gravity Bridge: ${msg}`);
|
||||||
ctx.logToFile(msg);
|
ctx.logToFile(msg);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import * as path from 'path';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as cp from 'child_process';
|
import * as cp from 'child_process';
|
||||||
import { WSBridgeClient, WSResponseData, WSCommandData } from './ws-client';
|
import { WSBridgeClient, WSResponseData, WSCommandData } from './ws-client';
|
||||||
import { initStepProbe, BridgeContext, writePendingApproval, tryApprovalStrategies, writeRegistration, getApprovalContext, resetPendingState, resetPendingStateForReconnect, handleDiffReviewResponse, getActiveSessionId as getStepProbeSessionId, getStepProbeContext } from './step-probe';
|
import { initStepProbe, BridgeContext, writePendingApproval, tryApprovalStrategies, writeRegistration, getApprovalContext, resetPendingState, resetPendingStateForReconnect, handleDiffReviewResponse, getActiveSessionId as getStepProbeSessionId, getStepProbeContext, getLastWaitingCommand } from './step-probe';
|
||||||
import { startHttpBridge, getDeterministicPort, HttpBridgeContext } from './http-bridge';
|
import { startHttpBridge, getDeterministicPort, HttpBridgeContext } from './http-bridge';
|
||||||
import { setupApprovalObserver } from './html-patcher';
|
import { setupApprovalObserver } from './html-patcher';
|
||||||
import { watchCommandsDir, handleWSCommand, disposeCommandsWatcher, CommandHandlerContext } from './command-handler';
|
import { watchCommandsDir, handleWSCommand, disposeCommandsWatcher, CommandHandlerContext } from './command-handler';
|
||||||
@@ -209,8 +209,8 @@ export async function fixLSConnection(): Promise<boolean> {
|
|||||||
|
|
||||||
// Find the line whose workspace_id matches our workspace (case-insensitive)
|
// Find the line whose workspace_id matches our workspace (case-insensitive)
|
||||||
let matchedLine: string | null = null;
|
let matchedLine: string | null = null;
|
||||||
|
let fallbackLine: string | null = null; // v15: LS without workspace_id (AG restart)
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const lower = line.toLowerCase();
|
|
||||||
// Match workspace_id arg against our hint
|
// Match workspace_id arg against our hint
|
||||||
const wsMatch = line.match(/--workspace_id[= ](\S+)/i);
|
const wsMatch = line.match(/--workspace_id[= ](\S+)/i);
|
||||||
if (wsMatch) {
|
if (wsMatch) {
|
||||||
@@ -219,13 +219,26 @@ export async function fixLSConnection(): Promise<boolean> {
|
|||||||
matchedLine = line;
|
matchedLine = line;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// v15: LS without --workspace_id (new AG main LS after restart)
|
||||||
|
// Skip --enable_lsp processes (secondary/old LSP instances)
|
||||||
|
if (!line.includes('--enable_lsp') && !fallbackLine) {
|
||||||
|
fallbackLine = line;
|
||||||
|
logToFile(`[LS-FIX] found fallback LS (no workspace_id): PID=${line.split('|')[0]?.trim()}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matchedLine) {
|
if (!matchedLine) {
|
||||||
|
if (fallbackLine) {
|
||||||
|
// v15: Use workspace_id-less LS as fallback (common after AG restart)
|
||||||
|
logToFile(`[LS-FIX] No workspace_id match — using fallback LS`);
|
||||||
|
matchedLine = fallbackLine;
|
||||||
|
} else {
|
||||||
logToFile(`[LS-FIX] No LS process matched hint="${hint}" (${lines.length} processes)`);
|
logToFile(`[LS-FIX] No LS process matched hint="${hint}" (${lines.length} processes)`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extract port and csrf_token from matched line
|
// Extract port and csrf_token from matched line
|
||||||
const csrfMatch = matchedLine.match(/--csrf_token[= ](\S+)/);
|
const csrfMatch = matchedLine.match(/--csrf_token[= ](\S+)/);
|
||||||
@@ -375,6 +388,28 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal approval — tryApprovalStrategies
|
// Normal approval — tryApprovalStrategies
|
||||||
|
// v22: ALSO write response file so Observer's pollResponseGroup can click
|
||||||
|
// the correct button (with exact button_index). Without this, only the
|
||||||
|
// imprecise pollTriggerClick fallback was used for WS-path responses.
|
||||||
|
const responseDir = path.join(bridgePath, 'response');
|
||||||
|
if (!fs.existsSync(responseDir)) {
|
||||||
|
fs.mkdirSync(responseDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const respPayload = {
|
||||||
|
request_id: data.request_id,
|
||||||
|
approved,
|
||||||
|
button_index: data.button_index ?? 0,
|
||||||
|
step_type: stepType,
|
||||||
|
project_name: projectName,
|
||||||
|
_from_ws: true,
|
||||||
|
};
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(responseDir, `${data.request_id}.json`),
|
||||||
|
JSON.stringify(respPayload),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
logToFile(`[WS-RESPONSE] Response file written for pollResponseGroup: ${data.request_id?.substring(0, 12)}`);
|
||||||
|
|
||||||
const approvalCtx = getApprovalContext();
|
const approvalCtx = getApprovalContext();
|
||||||
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
||||||
tryApprovalStrategies(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
|
tryApprovalStrategies(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
|
||||||
@@ -486,6 +521,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
get sessionStalled() { return getStepProbeContext().sessionStalled; },
|
get sessionStalled() { return getStepProbeContext().sessionStalled; },
|
||||||
get lastPendingStepIndex() { return getStepProbeContext().lastPendingStepIndex; },
|
get lastPendingStepIndex() { return getStepProbeContext().lastPendingStepIndex; },
|
||||||
writeChatSnapshot,
|
writeChatSnapshot,
|
||||||
|
getLastWaitingCommand,
|
||||||
};
|
};
|
||||||
const bridgePort = await startHttpBridge(httpBridgeCtx, sdk);
|
const bridgePort = await startHttpBridge(httpBridgeCtx, sdk);
|
||||||
let localPort = bridgePort;
|
let localPort = bridgePort;
|
||||||
|
|||||||
@@ -55,6 +55,17 @@ export async function setupApprovalObserver(
|
|||||||
if (patcher && typeof patcher.getScriptPath === 'function') {
|
if (patcher && typeof patcher.getScriptPath === 'function') {
|
||||||
let baseScript = '';
|
let baseScript = '';
|
||||||
try { baseScript = integration.build(); } catch { baseScript = ''; }
|
try { baseScript = integration.build(); } catch { baseScript = ''; }
|
||||||
|
// Strip old Gravity Bridge observer IIFE from baseScript.
|
||||||
|
// integration.build() caches the previous session's script, so without
|
||||||
|
// stripping, the old observer (e.g. v12) runs alongside the new one (v13),
|
||||||
|
// and the old one wins because it executes first.
|
||||||
|
if (baseScript.includes('Gravity Bridge v')) {
|
||||||
|
const oldVer = baseScript.match(/Gravity Bridge v(\d+)/);
|
||||||
|
const newVer = observerJS.match(/Gravity Bridge v(\d+)/);
|
||||||
|
logToFile(`[OBSERVER] baseScript contains old observer ${oldVer?.[0] || '?'}, new is ${newVer?.[0] || '?'} — stripping old`);
|
||||||
|
// Remove the old observer IIFE: starts with "// ── Gravity Bridge" comment, ends at the last "})();" before EOF or next section
|
||||||
|
baseScript = baseScript.replace(/\n?\/\/\s*[─═].*Gravity Bridge[\s\S]*$/, '');
|
||||||
|
}
|
||||||
const combinedScript = baseScript + '\n' + observerJS;
|
const combinedScript = baseScript + '\n' + observerJS;
|
||||||
const scriptPath = patcher.getScriptPath();
|
const scriptPath = patcher.getScriptPath();
|
||||||
fs.writeFileSync(scriptPath, combinedScript, 'utf8');
|
fs.writeFileSync(scriptPath, combinedScript, 'utf8');
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface HttpBridgeContext {
|
|||||||
lastPendingStepIndex: number;
|
lastPendingStepIndex: number;
|
||||||
logToFile: (msg: string) => void;
|
logToFile: (msg: string) => void;
|
||||||
writeChatSnapshot?: (text: string) => void;
|
writeChatSnapshot?: (text: string) => void;
|
||||||
|
getLastWaitingCommand?: () => { cmd: string; desc: string; ts: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Module-level state ───
|
// ─── Module-level state ───
|
||||||
@@ -126,6 +127,21 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise<numbe
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /log — renderer relays important diagnostic logs
|
||||||
|
if (req.method === 'POST' && url.pathname === '/log') {
|
||||||
|
let logBody = '';
|
||||||
|
req.setEncoding('utf8');
|
||||||
|
req.on('data', (c: string) => logBody += c);
|
||||||
|
req.on('end', () => {
|
||||||
|
try {
|
||||||
|
const logData = JSON.parse(logBody);
|
||||||
|
ctx.logToFile(`[OBSERVER-LOG] ${logData.msg || logBody.substring(0, 500)}`);
|
||||||
|
} catch { ctx.logToFile(`[OBSERVER-LOG] ${logBody.substring(0, 500)}`); }
|
||||||
|
res.writeHead(200); res.end('ok');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (req.method === 'POST' && url.pathname === '/dump-html') {
|
if (req.method === 'POST' && url.pathname === '/dump-html') {
|
||||||
let dumpBody = '';
|
let dumpBody = '';
|
||||||
req.setEncoding('utf8');
|
req.setEncoding('utf8');
|
||||||
@@ -256,9 +272,204 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(body);
|
const data = JSON.parse(body);
|
||||||
|
|
||||||
// ── Server-side false positive filter ──
|
// ── v12: Command enrichment FIRST — extract actual command from description ──
|
||||||
const cmd = (data.command || '').trim();
|
// Must run before filters so "Always run" with useful description isn't filtered out
|
||||||
// Removed valid AI buttons (Accept, Reject, Allow, Deny) which are now structurally protected by the observer script
|
const rawCmd = (data.command || '').trim();
|
||||||
|
// v15: Strip Material icon names from description BEFORE enrichment
|
||||||
|
// DOM textContent concatenates icon text (e.g. "content_copy") without separators
|
||||||
|
const ICON_STRIP_RE = /\b(chevron_right|chevron_left|arrow_drop_down|arrow_drop_up|arrow_right|arrow_left|arrow_forward|arrow_back|expand_more|expand_less|more_horiz|more_vert|content_copy|content_paste|check_circle|check|keyboard_arrow_up|keyboard_arrow_down|keyboard_arrow_left|keyboard_arrow_right|slow_motion_video|open_in_new|alternate_email)\b/g;
|
||||||
|
const rawDesc = (data.description || '').replace(ICON_STRIP_RE, '').replace(/\s{2,}/g, ' ').trim();
|
||||||
|
const GENERIC_BTN_RE = /^(?:Always\s*)?(?:Run|Allow|Accept|Approve)$/i;
|
||||||
|
let enrichedCmd = rawCmd;
|
||||||
|
let enrichedDesc = rawDesc;
|
||||||
|
|
||||||
|
// v19: "Always run" auto-approve — MUST run BEFORE any filter can reject it
|
||||||
|
// Detects from rawCmd OR from buttons array (Observer may detect sibling first)
|
||||||
|
// v33: Also auto-approve "Accept all" (diff review) and "Accept" buttons
|
||||||
|
const AUTO_APPROVE_RE = /^(Always\s+run|Accept\s+all|Accept)$/i;
|
||||||
|
let alwaysRunDetected = AUTO_APPROVE_RE.test(rawCmd);
|
||||||
|
let alwaysRunBtnIndex = alwaysRunDetected ? 0 : -1;
|
||||||
|
if (!alwaysRunDetected && Array.isArray(data.buttons)) {
|
||||||
|
for (let bi = 0; bi < data.buttons.length; bi++) {
|
||||||
|
if (AUTO_APPROVE_RE.test((data.buttons[bi].text || '').trim())) {
|
||||||
|
alwaysRunDetected = true;
|
||||||
|
alwaysRunBtnIndex = bi;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alwaysRunDetected) {
|
||||||
|
// v34: If this is "Accept all" / "Accept", also call agentAcceptAllInFile directly
|
||||||
|
const isAcceptAll = /^Accept/i.test(rawCmd) || (data.step_type === 'diff_review');
|
||||||
|
if (isAcceptAll) {
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE Accept all → opening review + agentAcceptAllInFile`);
|
||||||
|
try {
|
||||||
|
const vscode = require('vscode');
|
||||||
|
// v37: Must focus diff review panel BEFORE calling agentAcceptAllInFile
|
||||||
|
// Without this, the command succeeds but has no effect
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
await vscode.commands.executeCommand('antigravity.openReviewChanges');
|
||||||
|
ctx.logToFile(`[HTTP] openReviewChanges OK`);
|
||||||
|
await new Promise((r: any) => setTimeout(r, 500));
|
||||||
|
await vscode.commands.executeCommand('antigravity.prioritized.agentAcceptAllInFile');
|
||||||
|
ctx.logToFile(`[HTTP] ✅ agentAcceptAllInFile SUCCESS`);
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.logToFile(`[HTTP] ❌ agentAcceptAllInFile: ${e.message?.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
} catch (e: any) { ctx.logToFile(`[HTTP] ❌ vscode require failed: ${e.message}`); }
|
||||||
|
}
|
||||||
|
// Try enrichment for better Discord display text
|
||||||
|
let displayCmd = rawCmd;
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE raw: cmd="${rawCmd}" desc="${rawDesc.substring(0, 120)}" buttons=${JSON.stringify((data.buttons || []).map((b: any) => b.text)).substring(0, 200)}`);
|
||||||
|
if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 3 && rawDesc !== rawCmd) {
|
||||||
|
const promptMatch = rawDesc.match(/[>»]\s*(.+)/);
|
||||||
|
if (promptMatch && promptMatch[1].trim().length > 3) {
|
||||||
|
displayCmd = promptMatch[1].trim().substring(0, 200);
|
||||||
|
} else {
|
||||||
|
// v19: Fallback — use longest line from description as command text
|
||||||
|
const descLines = rawDesc.split(/\n/).map((l: string) => l.trim()).filter((l: string) => l.length > 3);
|
||||||
|
if (descLines.length > 0) {
|
||||||
|
descLines.sort((a: string, b: string) => b.length - a.length);
|
||||||
|
displayCmd = descLines[0].substring(0, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// v20: Final fallback — read latest Step Probe pending file for actual command
|
||||||
|
if (displayCmd === rawCmd && GENERIC_BTN_RE.test(displayCmd)) {
|
||||||
|
try {
|
||||||
|
const pendingDir = path.join(ctx.bridgePath, 'pending');
|
||||||
|
if (fs.existsSync(pendingDir)) {
|
||||||
|
const pFiles = fs.readdirSync(pendingDir)
|
||||||
|
.filter((f: string) => f.endsWith('.json'))
|
||||||
|
.map((f: string) => ({ name: f, time: fs.statSync(path.join(pendingDir, f)).mtimeMs }))
|
||||||
|
.sort((a: any, b: any) => b.time - a.time);
|
||||||
|
if (pFiles.length > 0 && (Date.now() - pFiles[0].time) < 30_000) {
|
||||||
|
const pData = JSON.parse(fs.readFileSync(path.join(pendingDir, pFiles[0].name), 'utf-8'));
|
||||||
|
if (pData.command && pData.command.length > 3 && !GENERIC_BTN_RE.test(pData.command)) {
|
||||||
|
displayCmd = pData.command.substring(0, 200);
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE enriched from pending file: "${displayCmd.substring(0, 80)}"`);
|
||||||
|
} else if (pData.description && pData.description.length > 5) {
|
||||||
|
displayCmd = pData.description.substring(0, 200);
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE enriched from pending desc: "${displayCmd.substring(0, 80)}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: any) { ctx.logToFile(`[HTTP] AUTO-APPROVE pending lookup error: ${e.message}`); }
|
||||||
|
}
|
||||||
|
// v29: Final-final fallback — Step Probe API memory
|
||||||
|
if (displayCmd === rawCmd && GENERIC_BTN_RE.test(displayCmd) && ctx.getLastWaitingCommand) {
|
||||||
|
const wc = ctx.getLastWaitingCommand();
|
||||||
|
if (wc.cmd && wc.cmd.length > 3 && !GENERIC_BTN_RE.test(wc.cmd) && (Date.now() - wc.ts) < 30_000) {
|
||||||
|
displayCmd = wc.desc && wc.desc.length > wc.cmd.length ? wc.desc.substring(0, 200) : wc.cmd.substring(0, 200);
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE enriched from step-probe memory: "${displayCmd.substring(0, 80)}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rid = data.request_id || Date.now().toString();
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE "Always run" (btnIdx=${alwaysRunBtnIndex}): cmd="${displayCmd.substring(0, 80)}"`);
|
||||||
|
// Write response file IMMEDIATELY so observer clicks the button with zero delay
|
||||||
|
const responseDir = path.join(ctx.bridgePath, 'response');
|
||||||
|
if (!fs.existsSync(responseDir)) {
|
||||||
|
fs.mkdirSync(responseDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const respPayload = {
|
||||||
|
request_id: rid,
|
||||||
|
approved: true,
|
||||||
|
button_index: alwaysRunBtnIndex >= 0 ? alwaysRunBtnIndex : 0,
|
||||||
|
step_type: data.step_type || 'command',
|
||||||
|
project_name: ctx.projectName,
|
||||||
|
_from_ws: true, // v38: prevent processResponseFile from consuming before Observer polls
|
||||||
|
_auto_approve_ttl: Date.now() + 60_000, // auto-expire after 60s
|
||||||
|
};
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(responseDir, `${rid}.json`),
|
||||||
|
JSON.stringify(respPayload),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
// v29: Respond to Observer immediately (don't block button click)
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: true, request_id: rid, auto_approved: true }));
|
||||||
|
|
||||||
|
// v29: Discord notification — if displayCmd is generic, poll Step Probe for real command
|
||||||
|
const isGenericDisplay = GENERIC_BTN_RE.test(displayCmd);
|
||||||
|
const sendDiscord = (finalCmd: string, finalDesc: string) => {
|
||||||
|
if (ctx.wsBridge && ctx.wsBridge.isConnected()) {
|
||||||
|
ctx.wsBridge.sendPending({
|
||||||
|
request_id: rid,
|
||||||
|
command: finalCmd,
|
||||||
|
description: finalDesc,
|
||||||
|
step_type: data.step_type || 'command',
|
||||||
|
status: 'auto_approved',
|
||||||
|
buttons: data.buttons,
|
||||||
|
project_name: ctx.projectName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isGenericDisplay && ctx.getLastWaitingCommand) {
|
||||||
|
// Poll Step Probe memory for up to 6s (API polls every 5s)
|
||||||
|
let pollAttempt = 0;
|
||||||
|
const maxAttempts = 30; // 30 * 200ms = 6s
|
||||||
|
const pollTimer = setInterval(() => {
|
||||||
|
pollAttempt++;
|
||||||
|
const wc = ctx.getLastWaitingCommand!();
|
||||||
|
if (wc.cmd && wc.cmd.length > 3 && !GENERIC_BTN_RE.test(wc.cmd) && (Date.now() - wc.ts) < 15_000) {
|
||||||
|
clearInterval(pollTimer);
|
||||||
|
const enrichedCmd = wc.desc && wc.desc.length > wc.cmd.length ? wc.desc.substring(0, 200) : wc.cmd.substring(0, 200);
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE enriched (delayed ${pollAttempt * 200}ms): "${enrichedCmd.substring(0, 80)}"`);
|
||||||
|
sendDiscord(enrichedCmd, `[${rawCmd}] ${enrichedCmd}`);
|
||||||
|
} else if (pollAttempt >= maxAttempts) {
|
||||||
|
clearInterval(pollTimer);
|
||||||
|
ctx.logToFile(`[HTTP] AUTO-APPROVE no enrichment after ${maxAttempts * 200}ms — sending generic`);
|
||||||
|
sendDiscord(displayCmd, rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
// Already enriched or no Step Probe — send immediately
|
||||||
|
sendDiscord(displayCmd, rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) {
|
||||||
|
// Extract the actual command from description (often includes terminal prompt)
|
||||||
|
// Pattern: "…\project_name > actual_command"
|
||||||
|
const promptMatch = rawDesc.match(/[>»]\s*(.+)/);
|
||||||
|
if (promptMatch && promptMatch[1].trim().length > 3) {
|
||||||
|
const extracted = promptMatch[1].trim();
|
||||||
|
// v16: Validate extracted text is not just a prompt fragment or path
|
||||||
|
const PROMPT_ONLY_RE = /^.*[>»$#]\s*$/;
|
||||||
|
const TERMINAL_PROMPT_RE = /^[^\n]*\\[^\\>]+\s*[>»]\s*$/;
|
||||||
|
if (!PROMPT_ONLY_RE.test(extracted) && !TERMINAL_PROMPT_RE.test(extracted)) {
|
||||||
|
enrichedCmd = extracted.substring(0, 200);
|
||||||
|
enrichedDesc = `[${rawCmd}] ${rawDesc}`;
|
||||||
|
ctx.logToFile(`[HTTP] command enriched: "${rawCmd}" → "${enrichedCmd.substring(0, 60)}"`);
|
||||||
|
} else {
|
||||||
|
// Prompt-only extraction — filter
|
||||||
|
ctx.logToFile(`[HTTP] enrichment skipped (prompt-only): "${rawCmd}" desc="${rawDesc.substring(0, 60)}"`);
|
||||||
|
ctx.logToFile(`[HTTP] filtered generic+prompt-only: "${rawCmd}"`);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: false, filtered: true, reason: 'generic_btn_prompt_only' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// v16: No prompt marker (> » $ #) found in description — this is terminal OUTPUT, not a command
|
||||||
|
// Observer extracted stdout text from code block (e.g. "No extension.log found", "Log found: ...")
|
||||||
|
ctx.logToFile(`[HTTP] filtered terminal output (no prompt marker): "${rawDesc.substring(0, 60)}"`);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: false, filtered: true, reason: 'terminal_output' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (GENERIC_BTN_RE.test(rawCmd) && (rawDesc.length <= 10 || rawDesc === rawCmd)) {
|
||||||
|
// v13: Generic button with no useful description (observer prompt-only context)
|
||||||
|
ctx.logToFile(`[HTTP] filtered generic button no-context: "${rawCmd}" desc="${rawDesc.substring(0, 30)}"`);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: false, filtered: true, reason: 'generic_btn_no_context' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Server-side false positive filter (uses enriched cmd) ──
|
||||||
|
const cmd = enrichedCmd;
|
||||||
const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it|Dismiss)$/i;
|
const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it|Dismiss)$/i;
|
||||||
if (FALSE_POSITIVE_RE.test(cmd)) {
|
if (FALSE_POSITIVE_RE.test(cmd)) {
|
||||||
ctx.logToFile(`[HTTP] filtered false positive: "${cmd}"`);
|
ctx.logToFile(`[HTTP] filtered false positive: "${cmd}"`);
|
||||||
@@ -266,8 +477,28 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
res.end(JSON.stringify({ ok: false, filtered: true }));
|
res.end(JSON.stringify({ ok: false, filtered: true }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// v14: Server-side junk content filter — CSS, source code, icon glue
|
||||||
|
// This is the last line of defense regardless of observer version
|
||||||
|
const JUNK_CONTENT_RE = /(!important|::selection|background-color:|var\(--|font-size:|border-[a-z]+:|padding:|margin:|display:\s|===|!==|\|\||\.\btest\(|\.\bmatch\(|\.\breplace\(|_RE[.\s]|\brawDesc\b|\brawCmd\b|\benrichedCmd\b|\bquerySelector\b|\.code-block|\.code-line|\.line-content|\{\s*--|integration\.build)/;
|
||||||
|
// v15: ICON_GLUE_RE now also catches standalone icon names (no trailing [a-zA-Z] required)
|
||||||
|
const ICON_GLUE_RE = /\b(alternate_email|content_copy|content_paste|check_circle|chevron_right|chevron_left|keyboard_arrow|arrow_drop_down|arrow_drop_up|more_horiz|more_vert|expand_more|expand_less)\b/;
|
||||||
|
// v15: Terminal prompt pattern — catches bare prompts like "…\project >" or "PS C:\path>"
|
||||||
|
const BARE_PROMPT_RE = /^[^\n]{0,60}[>»$#]\s*$/;
|
||||||
|
if (JUNK_CONTENT_RE.test(cmd) || ICON_GLUE_RE.test(cmd)) {
|
||||||
|
ctx.logToFile(`[HTTP] filtered junk content: "${cmd.substring(0, 80)}"`);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: false, filtered: true, reason: 'junk_content' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// v15: Final bare prompt filter — catches any enriched cmd that's just a terminal prompt
|
||||||
|
if (BARE_PROMPT_RE.test(cmd) && cmd.length < 80) {
|
||||||
|
ctx.logToFile(`[HTTP] filtered bare prompt: "${cmd.substring(0, 80)}"`);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: false, filtered: true, reason: 'bare_prompt' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
// "Run" button → step_probe handles these with full command detail
|
// "Run" button → step_probe handles these with full command detail
|
||||||
// Only filter when step_probe IS actively tracking a session
|
// Only filter when step_probe IS actively tracking AND cmd is still generic button text
|
||||||
if (/^(?:Always\s*)?Run\b/i.test(cmd)) {
|
if (/^(?:Always\s*)?Run\b/i.test(cmd)) {
|
||||||
if (ctx.activeSessionId && (!ctx.sessionStalled || ctx.lastPendingStepIndex >= 0)) {
|
if (ctx.activeSessionId && (!ctx.sessionStalled || ctx.lastPendingStepIndex >= 0)) {
|
||||||
ctx.logToFile(`[HTTP] filtered "Run" — ${!ctx.sessionStalled ? 'not stalled' : 'step_probe pending exists'} (session=${ctx.activeSessionId.substring(0, 8)})`);
|
ctx.logToFile(`[HTTP] filtered "Run" — ${!ctx.sessionStalled ? 'not stalled' : 'step_probe pending exists'} (session=${ctx.activeSessionId.substring(0, 8)})`);
|
||||||
@@ -283,16 +514,22 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
const pending: Record<string, any> = {
|
const pending: Record<string, any> = {
|
||||||
...data,
|
...data,
|
||||||
request_id: rid,
|
request_id: rid,
|
||||||
|
command: enrichedCmd,
|
||||||
|
description: enrichedDesc,
|
||||||
conversation_id: ctx.activeSessionId || '',
|
conversation_id: ctx.activeSessionId || '',
|
||||||
timestamp: Date.now() / 1000,
|
timestamp: Date.now() / 1000,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
project_name: ctx.projectName,
|
project_name: ctx.projectName,
|
||||||
auto_detected: true,
|
auto_detected: true,
|
||||||
source: 'dom_observer',
|
source: 'dom_observer',
|
||||||
|
step_type: data.step_type,
|
||||||
|
buttons: data.buttons,
|
||||||
step_index: ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : undefined,
|
step_index: ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : undefined,
|
||||||
};
|
};
|
||||||
|
// v19: "Always run" auto-approve was already handled above (before filter chain)
|
||||||
|
// No need for duplicate check here.
|
||||||
// File permission: inject multi-choice buttons
|
// File permission: inject multi-choice buttons
|
||||||
const cmdLower = (data.command || '').toLowerCase();
|
const cmdLower = enrichedCmd.toLowerCase();
|
||||||
if (cmdLower.includes('allow') && !pending.buttons) {
|
if (cmdLower.includes('allow') && !pending.buttons) {
|
||||||
// Dedup: skip if another file_permission pending was created within 10s
|
// Dedup: skip if another file_permission pending was created within 10s
|
||||||
const nowMs = Date.now();
|
const nowMs = Date.now();
|
||||||
@@ -311,8 +548,8 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
];
|
];
|
||||||
pending.step_type = 'file_permission';
|
pending.step_type = 'file_permission';
|
||||||
// Clean description: remove button labels from text
|
// Clean description: remove button labels from text
|
||||||
const rawDesc = (data.description || data.command || '').replace(/Deny|Allow Once|Allow This Conversation/gi, '').trim();
|
const cleanDesc = enrichedDesc.replace(/Deny|Allow Once|Allow This Conversation/gi, '').trim();
|
||||||
pending.command = `파일 접근 권한${rawDesc ? ': ' + rawDesc : ''}`;
|
pending.command = `파일 접근 권한${cleanDesc ? ': ' + cleanDesc : ''}`;
|
||||||
}
|
}
|
||||||
// WS dispatch
|
// WS dispatch
|
||||||
if (ctx.wsBridge && ctx.wsBridge.isConnected()) {
|
if (ctx.wsBridge && ctx.wsBridge.isConnected()) {
|
||||||
@@ -327,7 +564,11 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
});
|
});
|
||||||
ctx.logToFile(`[HTTP-WS] pending sent via WS: ${rid}`);
|
ctx.logToFile(`[HTTP-WS] pending sent via WS: ${rid}`);
|
||||||
}
|
}
|
||||||
ctx.logToFile(`[HTTP] pending created: ${rid} cmd="${data.command}" btns=${(data.buttons || []).length} ctx="${(data.description || '').substring(0, 50)}"`);
|
ctx.logToFile(`[HTTP] pending created: ${rid} cmd="${pending.command || data.command}" btns=${(pending.buttons || data.buttons || []).length} ctx="${(pending.description || data.description || '').substring(0, 80)}"`);
|
||||||
|
|
||||||
|
if (data._debug_trail) {
|
||||||
|
ctx.logToFile(`[HTTP-DIAG] trail: ${data._debug_trail.substring(0, 500)}`);
|
||||||
|
}
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({ ok: true, request_id: rid }));
|
res.end(JSON.stringify({ ok: true, request_id: rid }));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -432,8 +673,10 @@ function _handleChatSnapshot(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(body);
|
const data = JSON.parse(body);
|
||||||
if (data.text && typeof ctx.writeChatSnapshot === 'function') {
|
if (data.text && typeof ctx.writeChatSnapshot === 'function') {
|
||||||
ctx.writeChatSnapshot(`💬 **[DOM 추출] AI 응답**\n\n${data.text}`);
|
const isUser = data.role === 'user';
|
||||||
ctx.logToFile(`[HTTP] chat snapshot written (${data.text.length} chars)`);
|
const prefix = isUser ? '🧑💻 **[DOM 추출] 사용자 요청**' : '💬 **[DOM 추출] AI 응답**';
|
||||||
|
ctx.writeChatSnapshot(`${prefix}\n\n${data.text}`);
|
||||||
|
ctx.logToFile(`[HTTP] chat snapshot written (${data.text.length} chars, role: ${data.role || 'bot'})`);
|
||||||
}
|
}
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({ ok: true }));
|
res.end(JSON.stringify({ ok: true }));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export function generateApprovalObserverScript(_port: number): string {
|
export function generateApprovalObserverScript(_port: number): string {
|
||||||
return `
|
return `
|
||||||
// ── Gravity Bridge v9: Context-Aware AG Native Parser ──
|
// ?? Gravity Bridge v17: Always Run Auto-Approve + Retry Detection ??
|
||||||
// v9: Fixed 'Running N commands' false trigger + DOM-climbing context extraction
|
// v17: "Always run" auto-approve at bridge level + Retry button relay to Discord
|
||||||
(function(){
|
(function(){
|
||||||
'use strict';
|
'use strict';
|
||||||
var BASE='',_obs=false,_sent={},_ready=false;
|
var BASE='',_obs=false,_sent={},_ready=false;
|
||||||
@@ -9,8 +9,14 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
var THROTTLE_MS=500;
|
var THROTTLE_MS=500;
|
||||||
var CLEANUP_MS=300000;
|
var CLEANUP_MS=300000;
|
||||||
|
|
||||||
function log(m){console.log('[GB Observer] '+m);}
|
function log(m){
|
||||||
log('v9 Script loaded — Context-Aware AG Native Parser');
|
console.log('[GB Observer] '+m);
|
||||||
|
// v19: Relay important logs to extension via HTTP so they appear in extension.log
|
||||||
|
if (BASE && (m.indexOf('CV-CLASSES')!==-1 || m.indexOf('CV-CHILDREN')!==-1 || m.indexOf('child[')!==-1 || m.indexOf('CV found')!==-1 || m.indexOf('Conversation view')!==-1 || m.indexOf('BEACON')!==-1 || m.indexOf('ERROR')!==-1 || m.indexOf('chat relay')!==-1 || m.indexOf('user-cls')!==-1 || m.indexOf('CONTEXT')!==-1 || m.indexOf('BTN-DOM')!==-1 || m.indexOf('DEFERRED')!==-1 || m.indexOf('DETECTED')!==-1 || m.indexOf('ACCEPT')!==-1)) {
|
||||||
|
try { fetch(BASE+'/log', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({msg:m.substring(0,2000)})}); } catch(e){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log('v17 Script loaded ??Always Run Auto-Approve + Retry Detection');
|
||||||
|
|
||||||
// DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer
|
// DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer
|
||||||
try {
|
try {
|
||||||
@@ -33,7 +39,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Noise filter: lines that are UI artifacts, not real content ──
|
// ?? Noise filter: lines that are UI artifacts, not real content ??
|
||||||
var NOISE_RE = new RegExp(
|
var NOISE_RE = new RegExp(
|
||||||
'^(' +
|
'^(' +
|
||||||
'chevron_right|chevron_left|arrow_drop_down|arrow_drop_up|arrow_right|arrow_left|' +
|
'chevron_right|chevron_left|arrow_drop_down|arrow_drop_up|arrow_right|arrow_left|' +
|
||||||
@@ -49,8 +55,8 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
var NOISE_CODE_RE = /^(declare\\s+(class|function|interface|type|enum|const|var|let)\\s|(import|export|from)\\s|\\s*[{}()\\[\\];]\\s*$|\\.ts:\\d+:|extension.*src.*sdk)/i;
|
var NOISE_CODE_RE = /^(declare\\s+(class|function|interface|type|enum|const|var|let)\\s|(import|export|from)\\s|\\s*[{}()\\[\\];]\\s*$|\\.ts:\\d+:|extension.*src.*sdk)/i;
|
||||||
|
|
||||||
function isNoiseLine(line) {
|
function isNoiseLine(line) {
|
||||||
if (!line || line.trim().length < 2) return true;
|
|
||||||
var trimmed = line.trim();
|
var trimmed = line.trim();
|
||||||
|
if (trimmed.length < 2 && !/^[-*+>]$|^[0-9]$/.test(trimmed)) return true;
|
||||||
if (NOISE_RE.test(trimmed)) return true;
|
if (NOISE_RE.test(trimmed)) return true;
|
||||||
if (NOISE_CODE_RE.test(trimmed)) return true;
|
if (NOISE_CODE_RE.test(trimmed)) return true;
|
||||||
// Single-word Material icon names (all lowercase, no spaces)
|
// Single-word Material icon names (all lowercase, no spaces)
|
||||||
@@ -66,10 +72,23 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
text = text.replace(/Thought for a few seconds/gi, '');
|
text = text.replace(/Thought for a few seconds/gi, '');
|
||||||
var lines = text.split('\\n');
|
var lines = text.split('\\n');
|
||||||
var clean = [];
|
var clean = [];
|
||||||
|
var lastWasEmpty = false;
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!isNoiseLine(lines[i])) clean.push(lines[i].trim());
|
var line = lines[i];
|
||||||
|
if (line.trim().length === 0) {
|
||||||
|
if (!lastWasEmpty && clean.length > 0) {
|
||||||
|
clean.push('');
|
||||||
|
lastWasEmpty = true;
|
||||||
}
|
}
|
||||||
return clean.join('\\n').trim();
|
continue;
|
||||||
|
}
|
||||||
|
if (!isNoiseLine(line)) {
|
||||||
|
clean.push(line.replace(/\\s+$/, ''));
|
||||||
|
lastWasEmpty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (clean.length > 0 && clean[clean.length - 1] === '') clean.pop();
|
||||||
|
return clean.join('\\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanButtonText(btn) {
|
function cleanButtonText(btn) {
|
||||||
@@ -95,10 +114,10 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
return type+'|'+txt+'|'+idx;
|
return type+'|'+txt+'|'+idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
// v7: STEP-AWARE CONTEXT EXTRACTION
|
// v7: STEP-AWARE CONTEXT EXTRACTION
|
||||||
// Find the closest [data-step-index] ancestor, extract step info
|
// Find the closest [data-step-index] ancestor, extract step info
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
|
|
||||||
function getStepContainer(el) {
|
function getStepContainer(el) {
|
||||||
var node = el;
|
var node = el;
|
||||||
@@ -109,45 +128,178 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// v9: Climb DOM tree to find pre/code content near the button (no data-step-index needed)
|
// v14: Climb DOM tree to find context near the button
|
||||||
|
// STRICT SCOPE: Only 5 levels up ??beyond that we're in unrelated UI territory.
|
||||||
|
// JUNK FILTERS: CSS rules, source code, Material icon gluing are all rejected.
|
||||||
|
// NO FALLBACK: span/div/p text collection is removed entirely ??it always grabs chat/UI text.
|
||||||
|
var PROMPT_ONLY_RE = /^[^\\n]*[\\/\>\\xbb$#]\\s*$/;
|
||||||
|
// v14: Detect CSS rules, JS source code, or extension internals in code text
|
||||||
|
var JUNK_CODE_RE = /(!important|::selection|background-color:|var\\(--|font-size:|border-[a-z]|padding:|margin:|display:\\s|\\{[^}]*:[^}]*\\}|===|!==|\\|\\||\\bfunction\\s*\\(|\\bconst\\s+\\w+\\s*=|\\bvar\\s+\\w+\\s*=|\\bif\\s*\\(|\\breturn\\b|\\bimport\\s|\\bexport\\s|\\bclass\\s+\\w|\\bnew\\s+\\w|\\.test\\(|\\.match\\(|\\.replace\\(|_RE[.\\s]|\\brawDesc\\b|\\brawCmd\\b|\\benrichedCmd\\b|\\bquerySelector)/;
|
||||||
|
// v14: Detect Material icon text glued with content
|
||||||
|
var ICON_GLUE_RE = /(alternate_email|content_copy|content_paste|check_circle|chevron_right|chevron_left|keyboard_arrow|arrow_drop_down|arrow_drop_up|more_horiz|more_vert|expand_more|expand_less)[a-zA-Z]/;
|
||||||
function extractContextFromNearby(btn) {
|
function extractContextFromNearby(btn) {
|
||||||
var node = btn;
|
var node = btn;
|
||||||
for (var depth = 0; depth < 20 && node; depth++) {
|
var _debugTrail = [];
|
||||||
if (!node.querySelector) { node = node.parentElement; continue; }
|
var _bestCodeText = '';
|
||||||
// Look for code/pre blocks (actual command text)
|
var _bestCodeHeader = '';
|
||||||
var codeEls = node.querySelectorAll('pre, code, [class*="terminal"]');
|
var _sawCodeEls = false;
|
||||||
|
var _allSkipped = true;
|
||||||
|
// v22: Increased from 5 to 10 ??AG Native command display (SRi) can be many levels up
|
||||||
|
for (var depth = 0; depth < 10 && node; depth++) {
|
||||||
|
if (!node.querySelector) { _debugTrail.push('d'+depth+':noQS'); node = node.parentElement; continue; }
|
||||||
|
// v22: Prioritize pre.font-mono (AG Native command line display from SRi component)
|
||||||
|
var codeEls = node.querySelectorAll('pre.font-mono, pre, code, [class*="terminal"]');
|
||||||
|
_debugTrail.push('d'+depth+':tag='+((node.tagName||'?').toLowerCase())+',cls='+(((typeof node.className==='string')?node.className:'').substring(0,60))+',codeEls='+codeEls.length);
|
||||||
for (var ci = 0; ci < codeEls.length; ci++) {
|
for (var ci = 0; ci < codeEls.length; ci++) {
|
||||||
var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500));
|
var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500));
|
||||||
if (codeText && codeText.length > 5 && !/^Running\\s*\\d/i.test(codeText)) {
|
if (!codeText || codeText.length <= 5) continue;
|
||||||
// Also try to get a header/title near this container
|
if (/^Running\\s*\\d/i.test(codeText)) continue;
|
||||||
|
_sawCodeEls = true;
|
||||||
|
// v32: Terminal prompt detection — extract command BEFORE JUNK/PROMPT filters
|
||||||
|
// PS/bash commands can contain JS keywords (return, function, const) → false JUNK matches
|
||||||
|
var _termPromptMatch = codeText.match(/^[\\u276f\\u00bb]\\s+[^\\n]*[\\u003e]\\s+(.+)/);
|
||||||
|
if (_termPromptMatch && _termPromptMatch[1].trim().length > 2) {
|
||||||
|
var _termCmd = _termPromptMatch[1].trim();
|
||||||
|
_termCmd = _termCmd.replace(/\\s*(content_copy|content_paste|play_arrow)\\s*$/, '').trim();
|
||||||
|
if (_termCmd.length > 2) {
|
||||||
|
_allSkipped = false;
|
||||||
|
_bestCodeText = 'Running command: ' + _termCmd;
|
||||||
|
log('CONTEXT-OK d='+depth+' src=terminal-prompt cmd='+_termCmd.substring(0,80));
|
||||||
|
_lastContextDebug = _debugTrail.join(' ');
|
||||||
|
return _bestCodeText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (PROMPT_ONLY_RE.test(codeText.trim())) {
|
||||||
|
_debugTrail.push('skip_prompt_ci='+ci+':'+codeText.substring(0,30));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (JUNK_CODE_RE.test(codeText)) {
|
||||||
|
_debugTrail.push('skip_junk_ci='+ci+':'+codeText.substring(0,30));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ICON_GLUE_RE.test(codeText)) {
|
||||||
|
_debugTrail.push('skip_iconglue_ci='+ci+':'+codeText.substring(0,30));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_allSkipped = false;
|
||||||
|
if (!_bestCodeText || codeText.length > _bestCodeText.length) {
|
||||||
|
_bestCodeText = codeText;
|
||||||
var headerEl = node.querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]');
|
var headerEl = node.querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]');
|
||||||
var headerText = '';
|
|
||||||
if (headerEl) {
|
if (headerEl) {
|
||||||
var hClone = headerEl.cloneNode(true);
|
var hClone = headerEl.cloneNode(true);
|
||||||
var hRem = hClone.querySelectorAll('button, svg, [class*="icon"], .google-symbols');
|
var hRem = hClone.querySelectorAll('button, svg, [class*="icon"], .google-symbols');
|
||||||
for (var hi = 0; hi < hRem.length; hi++) {
|
for (var hi = 0; hi < hRem.length; hi++) {
|
||||||
if (hRem[hi].parentNode) hRem[hi].parentNode.removeChild(hRem[hi]);
|
if (hRem[hi].parentNode) hRem[hi].parentNode.removeChild(hRem[hi]);
|
||||||
}
|
}
|
||||||
headerText = cleanLines((hClone.textContent || '').trim().substring(0, 200));
|
_bestCodeHeader = cleanLines((hClone.textContent || '').trim().substring(0, 200));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_bestCodeText) {
|
||||||
var parts = [];
|
var parts = [];
|
||||||
if (headerText) parts.push(headerText);
|
if (_bestCodeHeader) parts.push(_bestCodeHeader);
|
||||||
parts.push(codeText);
|
parts.push(_bestCodeText);
|
||||||
return parts.join(' — ');
|
log('CONTEXT-OK d='+depth+' src=code trail='+_debugTrail.join(' > '));
|
||||||
|
_lastContextDebug = _debugTrail.join(' > ');
|
||||||
|
return parts.join(' \u2014 ');
|
||||||
|
}
|
||||||
|
// v30: Command text is in plain divs near "Running command" header, not pre/code
|
||||||
|
var rcDivs = node.querySelectorAll('div');
|
||||||
|
// v30 diagnostic: log what we find at each depth where code was skipped
|
||||||
|
if (_sawCodeEls && rcDivs.length > 0 && depth <= 5) {
|
||||||
|
var rcSample = [];
|
||||||
|
for (var rdi = 0; rdi < Math.min(rcDivs.length, 8); rdi++) {
|
||||||
|
var rdt = (rcDivs[rdi].textContent || '').trim().substring(0,40);
|
||||||
|
var rdc = rcDivs[rdi].children ? rcDivs[rdi].children.length : 0;
|
||||||
|
rcSample.push('ch'+rdc+':"'+rdt+'"');
|
||||||
|
}
|
||||||
|
log('CONTEXT-v30-SCAN d='+depth+' divs='+rcDivs.length+' ['+rcSample.join(', ')+']');
|
||||||
|
}
|
||||||
|
for (var rci = 0; rci < rcDivs.length; rci++) {
|
||||||
|
var rcEl = rcDivs[rci];
|
||||||
|
var rcChildCount = rcEl.children ? rcEl.children.length : 0;
|
||||||
|
var rcTxt = (rcEl.textContent || '').trim();
|
||||||
|
if ((rcTxt === 'Running command' || (rcChildCount === 0 && rcTxt.indexOf('Running command') !== -1 && rcTxt.length < 30)) && rcEl.parentElement) {
|
||||||
|
var rcP = rcEl.parentElement;
|
||||||
|
var rcCands = [];
|
||||||
|
for (var rcsi = 0; rcsi < rcP.children.length; rcsi++) {
|
||||||
|
if (rcP.children[rcsi] === rcEl) continue;
|
||||||
|
var sibT = (rcP.children[rcsi].textContent || '').trim();
|
||||||
|
if (sibT.length < 5) continue;
|
||||||
|
if (/^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/.test(sibT)) continue;
|
||||||
|
if (/^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i.test(sibT)) continue;
|
||||||
|
if (sibT.indexOf('Always run') !== -1 && sibT.indexOf('Cancel') !== -1) continue;
|
||||||
|
rcCands.push(sibT);
|
||||||
|
}
|
||||||
|
rcCands.sort(function(a,b){ return b.length - a.length; });
|
||||||
|
log('CONTEXT-v30 RC d='+depth+' cands='+rcCands.length+(rcCands.length>0?' best="'+rcCands[0].substring(0,60)+'"':''));
|
||||||
|
for (var rcci = 0; rcci < rcCands.length; rcci++) {
|
||||||
|
var candT = rcCands[rcci];
|
||||||
|
var pM = candT.match(/[\\u003e\\u00bb\\u276f]\\s+(.+)/);
|
||||||
|
if (pM && pM[1].trim().length > 3) {
|
||||||
|
var cmdV = pM[1].trim();
|
||||||
|
cmdV = cmdV.replace(/\\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\\s*$/, '').trim();
|
||||||
|
if (cmdV.length < 3) continue;
|
||||||
|
if (/^(Always|Run|Allow|Cancel|Deny)/i.test(cmdV)) continue;
|
||||||
|
log('CONTEXT-OK d='+depth+' src=running-cmd cmdV='+cmdV.substring(0,80));
|
||||||
|
_lastContextDebug = _debugTrail.join(' ');
|
||||||
|
return 'Running command: ' + cmdV.substring(0, 300);
|
||||||
|
}
|
||||||
|
if (candT.length > 10 && /[\\u276f\\u003e]/.test(candT)) {
|
||||||
|
var rawC = candT.replace(/\\s*(content_copy|content_paste|play_arrow)\\s*$/, '').trim();
|
||||||
|
log('CONTEXT-OK d='+depth+' src=running-cmd-raw rawC='+rawC.substring(0,80));
|
||||||
|
_lastContextDebug = _debugTrail.join(' ');
|
||||||
|
return rawC.substring(0, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// v23: Also search sibling elements at each level
|
||||||
|
// AG Native's command display (pre.font-mono) is a SIBLING of footer, not ancestor
|
||||||
|
if (node && node.parentElement) {
|
||||||
|
var siblings = node.parentElement.children;
|
||||||
|
for (var si = 0; si < siblings.length; si++) {
|
||||||
|
if (siblings[si] === node) continue;
|
||||||
|
if (!siblings[si].querySelector) continue;
|
||||||
|
var sibCodeEls = siblings[si].querySelectorAll('pre.font-mono, pre, code');
|
||||||
|
for (var sci = 0; sci < sibCodeEls.length; sci++) {
|
||||||
|
var sibCode = cleanLines((sibCodeEls[sci].textContent || '').trim().substring(0, 500));
|
||||||
|
if (!sibCode || sibCode.length <= 5) continue;
|
||||||
|
if (JUNK_CODE_RE.test(sibCode) || ICON_GLUE_RE.test(sibCode)) continue;
|
||||||
|
if (PROMPT_ONLY_RE.test(sibCode.trim())) continue;
|
||||||
|
_debugTrail.push('sibling_d'+depth+':tag='+siblings[si].tagName.toLowerCase()+',code='+sibCode.substring(0,40));
|
||||||
|
_bestCodeText = sibCode;
|
||||||
|
_allSkipped = false;
|
||||||
|
// Found in sibling ??return immediately
|
||||||
|
var sibParts = [];
|
||||||
|
var sibHdr = siblings[si].querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]');
|
||||||
|
if (sibHdr) sibParts.push(cleanLines((sibHdr.textContent || '').trim().substring(0, 200)));
|
||||||
|
sibParts.push(sibCode);
|
||||||
|
log('CONTEXT-OK d='+depth+' src=sibling trail='+_debugTrail.join(' > '));
|
||||||
|
_lastContextDebug = _debugTrail.join(' > ');
|
||||||
|
return sibParts.join(' \u2014 ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node = node.parentElement;
|
node = node.parentElement;
|
||||||
}
|
}
|
||||||
// Last resort: try aria-label or title on the button
|
if (_sawCodeEls && _allSkipped) {
|
||||||
|
log('CONTEXT-SKIP-ALL trail='+_debugTrail.join(' > '));
|
||||||
|
_lastContextDebug = _debugTrail.join(' > ');
|
||||||
|
return cleanButtonText(btn);
|
||||||
|
}
|
||||||
var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || '';
|
var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || '';
|
||||||
|
log('CONTEXT-FAIL trail='+_debugTrail.join(' > '));
|
||||||
|
_lastContextDebug = _debugTrail.join(' > ');
|
||||||
if (ariaLabel && ariaLabel.length > 5) return ariaLabel;
|
if (ariaLabel && ariaLabel.length > 5) return ariaLabel;
|
||||||
return cleanButtonText(btn);
|
return cleanButtonText(btn);
|
||||||
}
|
}
|
||||||
|
var _lastContextDebug = '';
|
||||||
|
|
||||||
function extractStepContext(btn) {
|
function extractStepContext(btn) {
|
||||||
var stepEl = getStepContainer(btn);
|
var stepEl = getStepContainer(btn);
|
||||||
if (!stepEl) {
|
if (!stepEl) {
|
||||||
// v9 FALLBACK: no data-step-index — climb DOM for pre/code blocks
|
// v9 FALLBACK: no data-step-index ??climb DOM for pre/code blocks
|
||||||
return extractContextFromNearby(btn);
|
return extractContextFromNearby(btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +335,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
if (codeText && !headerText.includes(codeText.substring(0, 20))) parts.push(codeText);
|
if (codeText && !headerText.includes(codeText.substring(0, 20))) parts.push(codeText);
|
||||||
if (ariaLabel && ariaLabel.length > 5 && !headerText.includes(ariaLabel)) parts.push(ariaLabel);
|
if (ariaLabel && ariaLabel.length > 5 && !headerText.includes(ariaLabel)) parts.push(ariaLabel);
|
||||||
|
|
||||||
var result = parts.join(' — ');
|
var result = parts.join(' ??');
|
||||||
if (!result) result = cleanButtonText(btn);
|
if (!result) result = cleanButtonText(btn);
|
||||||
return 'Step #' + stepIdx + ': ' + result;
|
return 'Step #' + stepIdx + ': ' + result;
|
||||||
}
|
}
|
||||||
@@ -192,14 +344,14 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
return extractStepContext(b);
|
return extractStepContext(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', 'Always allow', 'Always run'];
|
var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', 'Always allow', 'Always run', 'Retry'];
|
||||||
var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss'];
|
var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss'];
|
||||||
|
|
||||||
function isActionBtn(txt) {
|
function isActionBtn(txt) {
|
||||||
for(var i=0; i<ACTION_WORDS.length; i++) {
|
for(var i=0; i<ACTION_WORDS.length; i++) {
|
||||||
if(txt.indexOf(ACTION_WORDS[i]) !== -1) return true;
|
if(txt.indexOf(ACTION_WORDS[i]) !== -1) return true;
|
||||||
}
|
}
|
||||||
// v9: Removed "Running N commands" — it's a group header, not an approval button
|
// v9: Removed "Running N commands" ??it's a group header, not an approval button
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function isRejectBtn(txt) {
|
function isRejectBtn(txt) {
|
||||||
@@ -211,12 +363,13 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
|
|
||||||
function collectSiblingButtons(container,triggerBtn){
|
function collectSiblingButtons(container,triggerBtn){
|
||||||
if(!container)return [];
|
if(!container)return [];
|
||||||
// v9: Try multiple container levels (parent → grandparent → great-grandparent)
|
// v11: Try 5 container levels to find Cancel and other approval buttons
|
||||||
// to find all related approval buttons in wider DOM context
|
|
||||||
var containers = [container];
|
var containers = [container];
|
||||||
if (container.parentElement) containers.push(container.parentElement);
|
var cur = container;
|
||||||
if (container.parentElement && container.parentElement.parentElement)
|
for (var lvl = 0; lvl < 5 && cur.parentElement; lvl++) {
|
||||||
containers.push(container.parentElement.parentElement);
|
cur = cur.parentElement;
|
||||||
|
containers.push(cur);
|
||||||
|
}
|
||||||
var result=[];
|
var result=[];
|
||||||
var seen={};
|
var seen={};
|
||||||
for(var ci=0;ci<containers.length;ci++){
|
for(var ci=0;ci<containers.length;ci++){
|
||||||
@@ -234,8 +387,13 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
seen[stxt]=true;
|
seen[stxt]=true;
|
||||||
result.push({btn:sb,text:stxt,isPrimary:(sb===triggerBtn)});
|
result.push({btn:sb,text:stxt,isPrimary:(sb===triggerBtn)});
|
||||||
}
|
}
|
||||||
// If we found action buttons at this level, don't go wider
|
// v11: Only stop if we found BOTH action AND reject buttons at this level
|
||||||
if(result.length > 0) break;
|
var hasAction = false, hasReject = false;
|
||||||
|
for (var ri=0;ri<result.length;ri++) {
|
||||||
|
if (isActionBtn(result[ri].text)) hasAction = true;
|
||||||
|
if (isRejectBtn(result[ri].text)) hasReject = true;
|
||||||
|
}
|
||||||
|
if(hasAction && hasReject) break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -273,17 +431,18 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
startObserver();
|
startObserver();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
// v8: FULL DOM STRUCTURE DUMP (unconditional — no selector dependency)
|
// v8: FULL DOM STRUCTURE DUMP (unconditional ??no selector dependency)
|
||||||
// Dumps entire document.body tree to /dump-html for real DOM analysis
|
// Dumps entire document.body tree to /dump-html for real DOM analysis
|
||||||
// Auto-triggers at 5s, 15s, 60s after load to capture React-rendered state
|
// Auto-triggers at 5s, 15s, 60s after load to capture React-rendered state
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
|
|
||||||
var _dumpCount=0;
|
var _dumpCount=0;
|
||||||
var MAX_DUMPS=5;
|
var MAX_DUMPS=8;
|
||||||
|
var _conversationDumpCount=0;
|
||||||
|
|
||||||
function walkNode(el, depth, maxDepth, maxChildren) {
|
function walkNode(el, depth, maxDepth, maxChildren) {
|
||||||
if (depth > maxDepth) return {tag:'…',text:'depth limit'};
|
if (depth > maxDepth) return {tag:'MAX',text:'depth limit'};
|
||||||
if (!el || !el.tagName) return null;
|
if (!el || !el.tagName) return null;
|
||||||
var info = {
|
var info = {
|
||||||
tag: el.tagName ? el.tagName.toLowerCase() : '#text',
|
tag: el.tagName ? el.tagName.toLowerCase() : '#text',
|
||||||
@@ -319,7 +478,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
if (childInfo) info.children.push(childInfo);
|
if (childInfo) info.children.push(childInfo);
|
||||||
}
|
}
|
||||||
if (el.children.length > limit) {
|
if (el.children.length > limit) {
|
||||||
info.children.push({tag: '…', text: '+' + (el.children.length - limit) + ' more children'});
|
info.children.push({tag: 'TRUNC', text: '+' + (el.children.length - limit) + ' more children'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
@@ -414,16 +573,122 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
setTimeout(function(){ log('Auto-dump @60s'); dumpDOMStructure(); }, 60000);
|
setTimeout(function(){ log('Auto-dump @60s'); dumpDOMStructure(); }, 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
// v7: STEP-AWARE CHAT BODY SCANNING
|
// v15: AG-NATIVE + CASCADE DUAL CHAT BODY SCANNING
|
||||||
// Scans [data-step-index] elements inside [data-testid="conversation-view"]
|
// AG Native: #conversation > ... > .leading-relaxed.select-text
|
||||||
// Extracts AI response text while filtering UI noise
|
// Cascade: [data-testid="conversation-view"] > [data-step-index]
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
|
|
||||||
var _lastScrapedStepIndex = -1;
|
var _lastScrapedStepIndex = -1;
|
||||||
var _lastStepText = '';
|
var _lastStepText = '';
|
||||||
var _lastStepTextTime = 0;
|
var _lastStepTextTime = 0;
|
||||||
var _lastStepTextSent = false;
|
var _lastStepTextSent = false;
|
||||||
|
var _lastResponseBlockCount = 0; // track number of response blocks for AG Native
|
||||||
|
|
||||||
|
function convertNodeToMarkdown(node) {
|
||||||
|
if (!node) return '';
|
||||||
|
if (node.nodeType === 3) return node.textContent; // Text node
|
||||||
|
if (node.nodeType !== 1) return ''; // Skip other node types
|
||||||
|
|
||||||
|
var tag = node.tagName.toLowerCase();
|
||||||
|
|
||||||
|
// Skip hidden or UI elements
|
||||||
|
if (tag === 'style' || tag === 'script' || tag === 'noscript' || tag === 'button' || tag === 'svg') return '';
|
||||||
|
var cls = '';
|
||||||
|
if (typeof node.className === 'string') cls = node.className;
|
||||||
|
else if (node.className && node.className.baseVal) cls = node.className.baseVal;
|
||||||
|
|
||||||
|
if (cls && (cls.indexOf('google-symbols') !== -1 || cls.indexOf('material-icons') !== -1 || cls.indexOf('copy') !== -1 || cls.indexOf('codicon') !== -1)) return '';
|
||||||
|
|
||||||
|
var childrenMd = '';
|
||||||
|
for (var i = 0; i < node.childNodes.length; i++) {
|
||||||
|
childrenMd += convertNodeToMarkdown(node.childNodes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TABLE: Discord doesn't support markdown tables, so convert to fixed-width code block
|
||||||
|
if (tag === 'table') {
|
||||||
|
var rows = node.querySelectorAll('tr');
|
||||||
|
if (!rows || rows.length === 0) return childrenMd;
|
||||||
|
var grid = [];
|
||||||
|
var colWidths = [];
|
||||||
|
for (var ri = 0; ri < rows.length; ri++) {
|
||||||
|
var cells = rows[ri].querySelectorAll('th, td');
|
||||||
|
var row = [];
|
||||||
|
for (var ci = 0; ci < cells.length; ci++) {
|
||||||
|
var cellText = (cells[ci].textContent || '').trim();
|
||||||
|
row.push(cellText);
|
||||||
|
if (!colWidths[ci] || cellText.length > colWidths[ci]) colWidths[ci] = cellText.length;
|
||||||
|
}
|
||||||
|
grid.push(row);
|
||||||
|
}
|
||||||
|
// Build fixed-width text
|
||||||
|
var tbl = '';
|
||||||
|
for (var ri2 = 0; ri2 < grid.length; ri2++) {
|
||||||
|
var line = '';
|
||||||
|
for (var ci2 = 0; ci2 < colWidths.length; ci2++) {
|
||||||
|
var cell = grid[ri2][ci2] || '';
|
||||||
|
var pad = colWidths[ci2] - cell.length;
|
||||||
|
var padding = '';
|
||||||
|
for (var pi = 0; pi < pad; pi++) padding += ' ';
|
||||||
|
line += (ci2 > 0 ? ' | ' : '') + cell + padding;
|
||||||
|
}
|
||||||
|
tbl += line + '\\n';
|
||||||
|
// Add separator after header row (first row)
|
||||||
|
if (ri2 === 0) {
|
||||||
|
var sep = '';
|
||||||
|
for (var si2 = 0; si2 < colWidths.length; si2++) {
|
||||||
|
var dashes = '';
|
||||||
|
for (var di = 0; di < colWidths[si2]; di++) dashes += '-';
|
||||||
|
sep += (si2 > 0 ? '-+-' : '') + dashes;
|
||||||
|
}
|
||||||
|
tbl += sep + '\\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '\\n' + String.fromCharCode(96,96,96) + '\\n' + tbl + String.fromCharCode(96,96,96) + '\\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
case 'h1': return '\\n# ' + childrenMd.trim() + '\\n';
|
||||||
|
case 'h2': return '\\n## ' + childrenMd.trim() + '\\n';
|
||||||
|
case 'h3': return '\\n### ' + childrenMd.trim() + '\\n';
|
||||||
|
case 'h4': return '\\n#### ' + childrenMd.trim() + '\\n';
|
||||||
|
case 'p': return '\\n' + childrenMd.trim() + '\\n';
|
||||||
|
case 'div':
|
||||||
|
// Treat specific divs as blocks if they end up behaving like paragraphs
|
||||||
|
if (cls.indexOf('block') !== -1 || cls.indexOf('message') !== -1) return '\\n' + childrenMd.trim() + '\\n';
|
||||||
|
return childrenMd;
|
||||||
|
case 'br': return '\\n';
|
||||||
|
case 'strong':
|
||||||
|
case 'b': return '**' + childrenMd + '**';
|
||||||
|
case 'em':
|
||||||
|
case 'i': return '*' + childrenMd + '*';
|
||||||
|
case 'a':
|
||||||
|
var href = node.getAttribute('href') || '';
|
||||||
|
return '[' + childrenMd + '](' + href + ')';
|
||||||
|
case 'code': return (node.parentNode && node.parentNode.tagName === 'PRE') ? childrenMd : (String.fromCharCode(96) + childrenMd + String.fromCharCode(96));
|
||||||
|
case 'pre': return '\\n' + String.fromCharCode(96,96,96) + '\\n' + childrenMd.trim() + '\\n' + String.fromCharCode(96,96,96) + '\\n';
|
||||||
|
case 'li':
|
||||||
|
var prefix = '- ';
|
||||||
|
if (node.parentNode && node.parentNode.tagName.toLowerCase() === 'ol') {
|
||||||
|
var idx = 1;
|
||||||
|
var curr = node.previousSibling;
|
||||||
|
while(curr) { if (curr.nodeType === 1 && curr.tagName.toLowerCase() === 'li') idx++; curr = curr.previousSibling; }
|
||||||
|
prefix = idx + '. ';
|
||||||
|
}
|
||||||
|
return '\\n' + prefix + childrenMd.trim();
|
||||||
|
case 'ul':
|
||||||
|
case 'ol': return '\\n' + childrenMd + '\\n';
|
||||||
|
case 'blockquote': return '\\n> ' + childrenMd.trim().split('\\n').join('\\n> ') + '\\n';
|
||||||
|
// Table sub-elements: already handled by the table case above via querySelectorAll
|
||||||
|
case 'thead':
|
||||||
|
case 'tbody':
|
||||||
|
case 'tfoot':
|
||||||
|
case 'tr':
|
||||||
|
case 'th':
|
||||||
|
case 'td': return '';
|
||||||
|
default: return childrenMd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function extractCleanStepText(stepEl) {
|
function extractCleanStepText(stepEl) {
|
||||||
if (!stepEl) return '';
|
if (!stepEl) return '';
|
||||||
@@ -431,6 +696,12 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
// Clone the step element so we can strip UI elements without affecting the DOM
|
// Clone the step element so we can strip UI elements without affecting the DOM
|
||||||
var clone = stepEl.cloneNode(true);
|
var clone = stepEl.cloneNode(true);
|
||||||
|
|
||||||
|
// v16: Remove style/script/noscript elements FIRST
|
||||||
|
var styleEls = clone.querySelectorAll('style, script, noscript, link[rel="stylesheet"]');
|
||||||
|
for (var si = 0; si < styleEls.length; si++) {
|
||||||
|
if (styleEls[si].parentNode) styleEls[si].parentNode.removeChild(styleEls[si]);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove all buttons (Run, Allow, Cancel, etc.)
|
// Remove all buttons (Run, Allow, Cancel, etc.)
|
||||||
var buttons = clone.querySelectorAll('button');
|
var buttons = clone.querySelectorAll('button');
|
||||||
for (var bi = 0; bi < buttons.length; bi++) {
|
for (var bi = 0; bi < buttons.length; bi++) {
|
||||||
@@ -450,18 +721,91 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to get text from markdown rendering area first
|
// Try to get text from markdown rendering area first
|
||||||
// Look for known markdown container patterns
|
// AG Native uses .leading-relaxed.select-text, Cascade uses .markdown-body/.prose
|
||||||
var mdEl = clone.querySelector('.markdown-body, .prose, [class*="markdown"], [class*="rendered"]');
|
var mdEl = clone.querySelector('.markdown-body, .prose, [class*="markdown"], [class*="rendered"]') || clone;
|
||||||
var rawText = '';
|
|
||||||
if (mdEl && mdEl.innerText && mdEl.innerText.trim().length > 10) {
|
// Use our custom DOM-to-Markdown parser instead of innerText
|
||||||
rawText = mdEl.innerText.trim();
|
var rawText = convertNodeToMarkdown(mdEl).trim();
|
||||||
|
|
||||||
|
// v18 FIX: DO NOT apply cleanLines to full markdown content, it destroys valid code blocks
|
||||||
|
// Safely remove "Thought for X" lines only
|
||||||
|
rawText = rawText.replace(/Thought for \\d+s?/gi, '');
|
||||||
|
rawText = rawText.replace(/Thought for a few seconds/gi, '');
|
||||||
|
|
||||||
|
// Cleanup multiple empty lines
|
||||||
|
var lines = rawText.split('\\n');
|
||||||
|
var finalLines = [];
|
||||||
|
var lastEmpty = false;
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var line = lines[i].replace(/\\s+$/, '');
|
||||||
|
if (line.length === 0) {
|
||||||
|
if (!lastEmpty && finalLines.length > 0) {
|
||||||
|
finalLines.push('');
|
||||||
|
lastEmpty = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: get all text but filter aggressively
|
finalLines.push(line);
|
||||||
rawText = (clone.innerText || clone.textContent || '').trim();
|
lastEmpty = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply line-by-line noise filter
|
// v19: Post-process ??wrap markdown table patterns in code blocks for Discord
|
||||||
return cleanLines(rawText).substring(0, 3500);
|
// AG Native renders tables as divs, not <table> HTML, so DOM-level handler can't catch them.
|
||||||
|
// Detect consecutive lines with pipe separators (| col1 | col2 |) and wrap in code block fences
|
||||||
|
var bt = String.fromCharCode(96, 96, 96);
|
||||||
|
var result = [];
|
||||||
|
var tableBlock = [];
|
||||||
|
var inCodeBlock = false;
|
||||||
|
for (var fi = 0; fi < finalLines.length; fi++) {
|
||||||
|
var fl = finalLines[fi];
|
||||||
|
// Track existing code blocks to avoid double-wrapping
|
||||||
|
if (fl.trim().indexOf(bt) === 0) {
|
||||||
|
inCodeBlock = !inCodeBlock;
|
||||||
|
// Flush any pending table block before code block marker
|
||||||
|
if (tableBlock.length > 0) {
|
||||||
|
result.push(bt);
|
||||||
|
for (var ti = 0; ti < tableBlock.length; ti++) result.push(tableBlock[ti]);
|
||||||
|
result.push(bt);
|
||||||
|
tableBlock = [];
|
||||||
|
}
|
||||||
|
result.push(fl);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inCodeBlock) {
|
||||||
|
result.push(fl);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Detect table row: has at least 2 pipe characters and content between them
|
||||||
|
var pipeCount = 0;
|
||||||
|
for (var pc = 0; pc < fl.length; pc++) { if (fl.charAt(pc) === '|') pipeCount++; }
|
||||||
|
var isTableRow = pipeCount >= 2 && fl.trim().charAt(0) === '|';
|
||||||
|
var isSeparator = isTableRow && /^[\\s|:-]+$/.test(fl.trim());
|
||||||
|
if (isTableRow) {
|
||||||
|
tableBlock.push(fl);
|
||||||
|
} else {
|
||||||
|
// Flush table block if it had enough rows (header + separator + data)
|
||||||
|
if (tableBlock.length >= 2) {
|
||||||
|
result.push(bt);
|
||||||
|
for (var ti2 = 0; ti2 < tableBlock.length; ti2++) result.push(tableBlock[ti2]);
|
||||||
|
result.push(bt);
|
||||||
|
} else {
|
||||||
|
// Not a real table, push lines back normally
|
||||||
|
for (var ti3 = 0; ti3 < tableBlock.length; ti3++) result.push(tableBlock[ti3]);
|
||||||
|
}
|
||||||
|
tableBlock = [];
|
||||||
|
result.push(fl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Flush trailing table block
|
||||||
|
if (tableBlock.length >= 2) {
|
||||||
|
result.push(bt);
|
||||||
|
for (var ti4 = 0; ti4 < tableBlock.length; ti4++) result.push(tableBlock[ti4]);
|
||||||
|
result.push(bt);
|
||||||
|
} else {
|
||||||
|
for (var ti5 = 0; ti5 < tableBlock.length; ti5++) result.push(tableBlock[ti5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.join('\\n').substring(0, 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scanChatBodies() {
|
function scanChatBodies() {
|
||||||
@@ -470,8 +814,176 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
// One-time DOM dump
|
// One-time DOM dump
|
||||||
dumpDOMStructure();
|
dumpDOMStructure();
|
||||||
|
|
||||||
// PRIMARY: Find conversation-view container
|
// ?? STRATEGY 1: AG Native ??#conversation or .antigravity-agent-side-panel ??
|
||||||
var cv = document.querySelector('[data-testid="conversation-view"]');
|
var cv = document.querySelector('#conversation');
|
||||||
|
if (!cv) {
|
||||||
|
cv = document.querySelector('.antigravity-agent-side-panel');
|
||||||
|
}
|
||||||
|
// v19: Fallback ??find conversation by tracing from known content elements
|
||||||
|
if (!cv) {
|
||||||
|
var probe = document.querySelector('.leading-relaxed.select-text') || document.querySelector('.text-ide-message-block-bot-color');
|
||||||
|
if (probe) {
|
||||||
|
// Walk up to find a reasonable container (has overflow-y or is big enough)
|
||||||
|
var p = probe.parentElement;
|
||||||
|
for (var pi2 = 0; pi2 < 10 && p && p !== document.body; pi2++) {
|
||||||
|
var pCls = (typeof p.className === 'string') ? p.className : '';
|
||||||
|
if (pCls.indexOf('overflow') !== -1 || p.children.length > 3) {
|
||||||
|
cv = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p = p.parentElement;
|
||||||
|
}
|
||||||
|
if (!cv && probe.parentElement) cv = probe.parentElement.parentElement || probe.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cv) {
|
||||||
|
// v20: Dump CV structure for first 3 scans to ensure we capture it (even with stale HTML cache)
|
||||||
|
if (_conversationDumpCount < 3) {
|
||||||
|
_conversationDumpCount++;
|
||||||
|
log('CV found via: ' + (cv.id || (typeof cv.className === 'string' ? cv.className : cv.tagName) || 'unknown').substring(0, 100));
|
||||||
|
// Log all unique class names under #conversation for selector discovery
|
||||||
|
var allCvEls = cv.querySelectorAll('*');
|
||||||
|
var clsSet = {};
|
||||||
|
for (var ci2 = 0; ci2 < allCvEls.length; ci2++) {
|
||||||
|
var cn = allCvEls[ci2].className;
|
||||||
|
if (typeof cn === 'string' && cn.length > 0) {
|
||||||
|
var parts = cn.split(/\\s+/);
|
||||||
|
for (var pi = 0; pi < parts.length; pi++) {
|
||||||
|
if (parts[pi].length > 3 && !clsSet[parts[pi]]) clsSet[parts[pi]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var clsList = Object.keys(clsSet).sort().join(', ');
|
||||||
|
log('CV-CLASSES (' + Object.keys(clsSet).length + '): ' + clsList.substring(0, 1500));
|
||||||
|
// v19: Log direct children to discover message block structure
|
||||||
|
var cvKids = cv.children;
|
||||||
|
log('CV-CHILDREN (' + cvKids.length + '):');
|
||||||
|
for (var ck = 0; ck < Math.min(cvKids.length, 15); ck++) {
|
||||||
|
var kid = cvKids[ck];
|
||||||
|
var kidCls = (typeof kid.className === 'string') ? kid.className : '';
|
||||||
|
var kidText = (kid.textContent || '').trim().substring(0, 60);
|
||||||
|
log(' child[' + ck + '] tag=' + kid.tagName + ' cls=' + kidCls.substring(0, 120) + ' text=' + kidText);
|
||||||
|
}
|
||||||
|
// v22: Deep-dive into gap-8 container to find individual message blocks
|
||||||
|
var msgContainer = cv.querySelector('.gap-8') || cv.children[0];
|
||||||
|
if (msgContainer) {
|
||||||
|
var msgKids = msgContainer.children;
|
||||||
|
log('MSG-BLOCKS (' + msgKids.length + '):');
|
||||||
|
for (var mk = 0; mk < Math.min(msgKids.length, 30); mk++) {
|
||||||
|
var mb = msgKids[mk];
|
||||||
|
var mbCls = (typeof mb.className === 'string') ? mb.className : '';
|
||||||
|
var mbText = (mb.textContent || '').trim().substring(0, 80);
|
||||||
|
var hasLeadingRelaxed = mb.querySelector('.leading-relaxed.select-text') ? 'Y' : 'N';
|
||||||
|
var firstChildCls = (mb.children[0] && typeof mb.children[0].className === 'string') ? mb.children[0].className : '';
|
||||||
|
log(' msg[' + mk + '] cls=' + mbCls.substring(0, 120) + ' lr=' + hasLeadingRelaxed + ' fc=' + firstChildCls.substring(0, 80) + ' text=' + mbText.substring(0, 60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Force a dump with conversation context
|
||||||
|
dumpDOMStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// v22: AI response = .leading-relaxed.select-text, User message = .select-text.rounded-lg (Esn component, msn class)
|
||||||
|
// Source: jetskiAgent/main.js ??msn="bg-gray-500/10 border border-gray-500/20 p-2 rounded-lg w-full text-sm select-text"
|
||||||
|
var responseBlocks = cv.querySelectorAll('.leading-relaxed.select-text, .select-text.rounded-lg');
|
||||||
|
|
||||||
|
if (responseBlocks.length > 0) {
|
||||||
|
// v22: Filter out thinking/reasoning blocks ??they have ancestor with max-h-[200px]
|
||||||
|
// These are internal AI reasoning and should NOT be relayed to Discord
|
||||||
|
var filteredBlocks = [];
|
||||||
|
for (var fbi = 0; fbi < responseBlocks.length; fbi++) {
|
||||||
|
var isThinking = false;
|
||||||
|
var ancestor = responseBlocks[fbi].parentElement;
|
||||||
|
for (var depth = 0; ancestor && depth < 5; depth++) {
|
||||||
|
var aCls = (typeof ancestor.className === 'string') ? ancestor.className : '';
|
||||||
|
if (aCls.indexOf('max-h-[200px]') !== -1 || aCls.indexOf('max-h-[150px]') !== -1) {
|
||||||
|
isThinking = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ancestor = ancestor.parentElement;
|
||||||
|
}
|
||||||
|
if (!isThinking) filteredBlocks.push(responseBlocks[fbi]);
|
||||||
|
}
|
||||||
|
if (filteredBlocks.length === 0) return;
|
||||||
|
|
||||||
|
// Process the LAST (most recent) non-thinking response block
|
||||||
|
var lastBlock = filteredBlocks[filteredBlocks.length - 1];
|
||||||
|
|
||||||
|
// Skip if already scraped
|
||||||
|
if (lastBlock.dataset.agChatScraped === 'true' || lastBlock.dataset.agChatScraped === 'pending') {
|
||||||
|
// Check for NEW blocks since last scrape
|
||||||
|
if (filteredBlocks.length > _lastResponseBlockCount) {
|
||||||
|
// New block appeared ??process it
|
||||||
|
for (var rbi = filteredBlocks.length - 1; rbi >= 0; rbi--) {
|
||||||
|
if (filteredBlocks[rbi].dataset.agChatScraped !== 'true' && filteredBlocks[rbi].dataset.agChatScraped !== 'pending') {
|
||||||
|
lastBlock = filteredBlocks[rbi];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastBlock.dataset.agChatScraped === 'true' || lastBlock.dataset.agChatScraped === 'pending') return;
|
||||||
|
} else {
|
||||||
|
return; // Already scraped, no new blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockText = extractCleanStepText(lastBlock);
|
||||||
|
|
||||||
|
var clsStr = (typeof lastBlock.className === 'string') ? lastBlock.className : '';
|
||||||
|
// v19: Log block classes for user message selector discovery
|
||||||
|
var parentCls = lastBlock.parentElement ? ((typeof lastBlock.parentElement.className === 'string') ? lastBlock.parentElement.className : '') : '';
|
||||||
|
var grandCls = (lastBlock.parentElement && lastBlock.parentElement.parentElement) ? ((typeof lastBlock.parentElement.parentElement.className === 'string') ? lastBlock.parentElement.parentElement.className : '') : '';
|
||||||
|
log('user-cls-debug block=' + clsStr.substring(0, 150) + ' | parent=' + parentCls.substring(0, 150) + ' | grand=' + grandCls.substring(0, 150) + ' | text=' + (blockText||'').substring(0, 50));
|
||||||
|
// v22: Detect user message: has select-text + rounded-lg but NOT leading-relaxed
|
||||||
|
var isUser = (clsStr.indexOf('rounded-lg') !== -1 && clsStr.indexOf('leading-relaxed') === -1) || clsStr.indexOf('user-color') !== -1;
|
||||||
|
var role = isUser ? 'user' : 'bot';
|
||||||
|
|
||||||
|
// Bot messages often start empty and stream in. User messages are usually immediate.
|
||||||
|
if (blockText && (blockText.length > 30 || isUser && blockText.length > 0)) {
|
||||||
|
// QUALITY CHECK: Skip if the text is mostly short lines (UI artifacts), BUT skip this check for user messages
|
||||||
|
if (!isUser) {
|
||||||
|
var lines = blockText.split('\\n').filter(function(l) { return l.trim().length > 0; });
|
||||||
|
var longLines = lines.filter(function(l) { return l.trim().length > 20; });
|
||||||
|
if (longLines.length === 0) {
|
||||||
|
log('AG-Native: skipped (no long lines, likely UI noise)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for content to stabilize (3s no change)
|
||||||
|
if (_lastStepText !== blockText) {
|
||||||
|
_lastStepText = blockText;
|
||||||
|
_lastStepTextTime = Date.now();
|
||||||
|
_lastStepTextSent = false;
|
||||||
|
return; // Wait for next scan cycle
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastStepTextSent) return;
|
||||||
|
// Bot needs 3s to stabilize, User just needs 500ms
|
||||||
|
var waitTime = isUser ? 500 : 3000;
|
||||||
|
if (Date.now() - _lastStepTextTime < waitTime) return; // Still waiting
|
||||||
|
|
||||||
|
// v21: DOM-based chat relay RE-ENABLED ??GetCascadeTrajectorySteps does NOT
|
||||||
|
// return steps for in-progress cascades, making Step Probe RT-CAPTURE useless.
|
||||||
|
// Observer DOM extraction is the ONLY real-time path for AI response relay.
|
||||||
|
_lastStepTextSent = true;
|
||||||
|
_lastResponseBlockCount = filteredBlocks.length;
|
||||||
|
lastBlock.dataset.agChatScraped = 'pending';
|
||||||
|
|
||||||
|
log('AG-Native chat relay [' + role + ']: blocks=' + filteredBlocks.length + ' text=' + blockText.length + ' chars');
|
||||||
|
(function(el, txt, count, r) {
|
||||||
|
fetch(BASE + '/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ text: txt, source: 'ag_native_block_' + count, block_index: count, role: r })
|
||||||
|
}).then(function() { el.dataset.agChatScraped = 'true'; log('AG-Native chat sent OK'); })
|
||||||
|
.catch(function(e) { el.dataset.agChatScraped = 'false'; log('AG-Native chat send error: ' + e.message); });
|
||||||
|
})(lastBlock, blockText, filteredBlocks.length, role);
|
||||||
|
}
|
||||||
|
return; // AG Native path handled ??don't fall through to Cascade path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ?? STRATEGY 2: Cascade ??[data-testid="conversation-view"] ??
|
||||||
|
cv = document.querySelector('[data-testid="conversation-view"]');
|
||||||
if (!cv) {
|
if (!cv) {
|
||||||
// FALLBACK: Try older selectors
|
// FALLBACK: Try older selectors
|
||||||
cv = document.querySelector('[class*="conversation"], [class*="chat-container"]');
|
cv = document.querySelector('[class*="conversation"], [class*="chat-container"]');
|
||||||
@@ -542,7 +1054,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
if (_lastStepTextSent) continue;
|
if (_lastStepTextSent) continue;
|
||||||
if (Date.now() - _lastStepTextTime < 3000) break; // Still waiting
|
if (Date.now() - _lastStepTextTime < 3000) break; // Still waiting
|
||||||
|
|
||||||
// Content is stable — send it
|
// Content is stable ??send it
|
||||||
_lastStepTextSent = true;
|
_lastStepTextSent = true;
|
||||||
_lastScrapedStepIndex = stepIdx;
|
_lastScrapedStepIndex = stepIdx;
|
||||||
stepEl.dataset.agChatScraped = 'pending';
|
stepEl.dataset.agChatScraped = 'pending';
|
||||||
@@ -560,34 +1072,52 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
// BUTTON SCANNING (approval detection)
|
// BUTTON SCANNING (approval detection)
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧
|
||||||
|
|
||||||
function scan(){
|
function scan(){
|
||||||
if(!_ready)return;
|
if(!_ready)return;
|
||||||
scanChatBodies();
|
scanChatBodies();
|
||||||
var now=Date.now();
|
var now=Date.now();
|
||||||
var allBtns=document.querySelectorAll('button');
|
var allBtns=document.querySelectorAll('button, [role="button"], a.monaco-button, .monaco-text-button, vscode-button, span.cursor-pointer');
|
||||||
if(!allBtns.length)return;
|
if(!allBtns.length)return;
|
||||||
|
|
||||||
|
// v25: One-shot debug ??find Accept/Reject elements in ANY tag (run once per 30s)
|
||||||
|
if (!scan._lastAcceptScan || now - scan._lastAcceptScan > 30000) {
|
||||||
|
scan._lastAcceptScan = now;
|
||||||
|
var allEls = document.querySelectorAll('button, a, div, span, [role="button"]');
|
||||||
|
for (var ai = 0; ai < allEls.length; ai++) {
|
||||||
|
var aTxt = (allEls[ai].textContent || '').trim();
|
||||||
|
if (aTxt.length > 2 && aTxt.length < 30 && /Accept|Reject all/i.test(aTxt)) {
|
||||||
|
log('ACCEPT-SCAN tag=' + allEls[ai].tagName + ' cls=' + (allEls[ai].className || '').substring(0,80) + ' txt=' + aTxt.substring(0,40) + ' oP=' + !!allEls[ai].offsetParent + ' dis=' + allEls[ai].disabled + ' hid=' + allEls[ai].hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(var j=0;j<allBtns.length;j++){
|
for(var j=0;j<allBtns.length;j++){
|
||||||
var b=allBtns[j];
|
var b=allBtns[j];
|
||||||
if(b.disabled||b.hidden||(!b.offsetParent&&b.style.display!=='fixed'))continue;
|
// v24: Visibility check moved after txt extraction (see isDiffReviewBtn below)
|
||||||
|
|
||||||
var txt=cleanButtonText(b);
|
var txt=cleanButtonText(b);
|
||||||
if(txt.length <= 1) continue;
|
if(txt.length <= 1) continue;
|
||||||
|
|
||||||
// v9: Skip group header buttons — not approval buttons
|
// v9: Skip group header buttons ??not approval buttons
|
||||||
if (/^Running\\s*\\d+\\s*commands?$/i.test(txt)) continue;
|
if (/^Running\\s*\\d+\\s*commands?$/i.test(txt)) continue;
|
||||||
|
|
||||||
|
// v24: Relaxed visibility check ??Accept all/Reject all buttons in AG Native
|
||||||
|
// editor bottom bar may have offsetParent===null (different rendering layer)
|
||||||
|
var isDiffReviewBtn = txt.includes('Accept') || txt === 'Reject all';
|
||||||
|
if(!isDiffReviewBtn && (b.disabled||b.hidden||(!b.offsetParent&&b.style.display!=='fixed')))continue;
|
||||||
|
|
||||||
if(!isActionBtn(txt)) continue;
|
if(!isActionBtn(txt)) continue;
|
||||||
// Skip inline code lens buttons
|
// Skip inline code lens buttons
|
||||||
if (b.closest('.codelens-decoration') && !txt.includes('Accept')) {
|
if (b.closest('.codelens-decoration') && !txt.includes('Accept')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchedType = txt.includes('Accept') ? 'diff_review' : (txt.includes('Run') || txt.includes('Allow') ? 'command' : 'permission');
|
var txtLow = txt.toLowerCase();
|
||||||
|
var matchedType = txt.includes('Accept') ? 'diff_review' : (txt === 'Retry' ? 'retry' : (txtLow.includes('run') || txtLow.includes('allow') ? 'command' : 'permission'));
|
||||||
|
|
||||||
// v7: Use step-index for more unique group key
|
// v7: Use step-index for more unique group key
|
||||||
var stepContainer = getStepContainer(b);
|
var stepContainer = getStepContainer(b);
|
||||||
@@ -612,11 +1142,87 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
|
|
||||||
var desc=extractContext(b);
|
var desc=extractContext(b);
|
||||||
|
|
||||||
|
// v28: One-shot DOM structure dump for button context analysis
|
||||||
|
if (!window._btnDomDumped && txtLow.includes('run')) {
|
||||||
|
window._btnDomDumped = true;
|
||||||
|
var dumpLines = [];
|
||||||
|
var cur = b;
|
||||||
|
for (var dd = 0; dd < 10 && cur; dd++) {
|
||||||
|
var childSummary = [];
|
||||||
|
if (cur.children) {
|
||||||
|
for (var ci2 = 0; ci2 < Math.min(cur.children.length, 8); ci2++) {
|
||||||
|
var ch = cur.children[ci2];
|
||||||
|
var chTag = (ch.tagName || '?').toLowerCase();
|
||||||
|
var chCls = (typeof ch.className === 'string' ? ch.className : '').substring(0, 40);
|
||||||
|
var chText = (ch.textContent || '').substring(0, 30).replace(/\\n/g, ' ');
|
||||||
|
childSummary.push(chTag + '.' + chCls.split(' ')[0] + '=' + chText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var sibSummary = [];
|
||||||
|
if (cur.parentElement) {
|
||||||
|
for (var si2 = 0; si2 < Math.min(cur.parentElement.children.length, 6); si2++) {
|
||||||
|
var sib = cur.parentElement.children[si2];
|
||||||
|
var sibTag = (sib.tagName || '?').toLowerCase();
|
||||||
|
var sibCls = (typeof sib.className === 'string' ? sib.className : '').substring(0, 30);
|
||||||
|
sibSummary.push(sibTag + '.' + sibCls.split(' ')[0] + (sib === cur ? '*' : ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dumpLines.push('d' + dd + ':' + (cur.tagName || '?').toLowerCase() + ' cls=' + (typeof cur.className === 'string' ? cur.className : '').substring(0, 50) + ' | children=[' + childSummary.join(', ') + '] | siblings=[' + sibSummary.join(', ') + ']');
|
||||||
|
cur = cur.parentElement;
|
||||||
|
}
|
||||||
|
log('BTN-DOM-DUMP txt=' + txt + ' desc=' + desc.substring(0, 40));
|
||||||
|
for (var ddi = 0; ddi < dumpLines.length; ddi++) {
|
||||||
|
log('BTN-DOM-DUMP ' + dumpLines[ddi]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var rid=now.toString()+'_'+Math.random().toString(36).substring(2,6);
|
var rid=now.toString()+'_'+Math.random().toString(36).substring(2,6);
|
||||||
|
|
||||||
_sent[groupKey]={rid:rid,ts:now};
|
_sent[groupKey]={rid:rid,ts:now};
|
||||||
for(var mk=0;mk<bidList.length;mk++)_sent[bidList[mk]]={rid:rid,ts:now};
|
for(var mk=0;mk<bidList.length;mk++)_sent[bidList[mk]]={rid:rid,ts:now};
|
||||||
|
|
||||||
|
// v26: Deferred context (string match, not regex)
|
||||||
|
var _isGenericDesc = function(d) {
|
||||||
|
var t = d.trim().toLowerCase();
|
||||||
|
return t === 'always run' || t === 'run' || t === 'allow' || t === 'accept' || t === 'retry' || t === txt.toLowerCase();
|
||||||
|
};
|
||||||
|
// v26: Deferred context ??if desc is generic ("Always run", button text only),
|
||||||
|
// delay 500ms and re-extract to allow DOM rendering to complete
|
||||||
|
var isGenericDesc = _isGenericDesc(desc);
|
||||||
|
if (isGenericDesc && matchedType === 'command') {
|
||||||
|
log('DEFERRED-CONTEXT: desc="' + desc.substring(0,30) + '" ??waiting 500ms for DOM render');
|
||||||
|
(function(b2, rid2, btnRefs2, bidList2, groupKey2, txt2, type2, buttonsArr2) {
|
||||||
|
setTimeout(function() {
|
||||||
|
var retryDesc = extractContext(b2);
|
||||||
|
var finalDesc = _isGenericDesc(retryDesc) ? desc : retryDesc;
|
||||||
|
log('DEFERRED-RESULT: "' + finalDesc.substring(0,80) + '"');
|
||||||
|
var payload = {
|
||||||
|
request_id: rid2,
|
||||||
|
command: txt2,
|
||||||
|
description: finalDesc,
|
||||||
|
step_type: type2,
|
||||||
|
buttons: buttonsArr2,
|
||||||
|
_debug_trail: _lastContextDebug || ''
|
||||||
|
};
|
||||||
|
fetch(BASE+'/pending',{
|
||||||
|
method:'POST',
|
||||||
|
headers:{'Content-Type':'application/json'},
|
||||||
|
body:JSON.stringify(payload)
|
||||||
|
}).then(function(r){return r.json();}).then(function(d){
|
||||||
|
if (!d.ok || d.filtered) {
|
||||||
|
delete _sent[groupKey2];
|
||||||
|
for(var di=0;di<bidList2.length;di++)delete _sent[bidList2[di]];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pollResponseGroup(d.request_id,btnRefs2,bidList2,groupKey2);
|
||||||
|
}).catch(function(e){
|
||||||
|
delete _sent[groupKey2];
|
||||||
|
for(var di=0;di<bidList2.length;di++)delete _sent[bidList2[di]];
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
})(b, rid, btnRefs, bidList, groupKey, txt, matchedType, buttonsArr);
|
||||||
|
} else {
|
||||||
|
// Original immediate send path
|
||||||
log('DETECTED '+matchedType+': '+txt+' ['+desc.substring(0,80)+'] step='+stepIdx);
|
log('DETECTED '+matchedType+': '+txt+' ['+desc.substring(0,80)+'] step='+stepIdx);
|
||||||
|
|
||||||
(function(rid2,btnRefs2,bidList2,groupKey2,txt2,desc2,type2,buttonsArr2){
|
(function(rid2,btnRefs2,bidList2,groupKey2,txt2,desc2,type2,buttonsArr2){
|
||||||
@@ -625,7 +1231,8 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
command:txt2,
|
command:txt2,
|
||||||
description:desc2,
|
description:desc2,
|
||||||
step_type:type2,
|
step_type:type2,
|
||||||
buttons:buttonsArr2
|
buttons:buttonsArr2,
|
||||||
|
_debug_trail:_lastContextDebug||''
|
||||||
};
|
};
|
||||||
fetch(BASE+'/pending',{
|
fetch(BASE+'/pending',{
|
||||||
method:'POST',
|
method:'POST',
|
||||||
@@ -643,6 +1250,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
for(var di=0;di<bidList2.length;di++)delete _sent[bidList2[di]];
|
for(var di=0;di<bidList2.length;di++)delete _sent[bidList2[di]];
|
||||||
});
|
});
|
||||||
})(rid,btnRefs,bidList,groupKey,txt,desc,matchedType,buttonsArr);
|
})(rid,btnRefs,bidList,groupKey,txt,desc,matchedType,buttonsArr);
|
||||||
|
} // end else (immediate send)
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -726,7 +1334,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
|
|
||||||
function startObserver(){
|
function startObserver(){
|
||||||
if(_obs)return;
|
if(_obs)return;
|
||||||
log('startObserver() — scheduling auto-dumps and mutation observer');
|
log('startObserver() ??scheduling auto-dumps and mutation observer');
|
||||||
scheduleAutoDumps();
|
scheduleAutoDumps();
|
||||||
new MutationObserver(function(mutations){
|
new MutationObserver(function(mutations){
|
||||||
for(var i=0;i<mutations.length;i++){
|
for(var i=0;i<mutations.length;i++){
|
||||||
@@ -738,7 +1346,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
}).observe(document.body,{childList:true,subtree:true});
|
}).observe(document.body,{childList:true,subtree:true});
|
||||||
setInterval(scheduleScan,3000);
|
setInterval(scheduleScan,3000);
|
||||||
|
|
||||||
// ── TRIGGER-CLICK POLLING ──
|
// ?? TRIGGER-CLICK POLLING ??
|
||||||
(function pollTriggerClick(){
|
(function pollTriggerClick(){
|
||||||
if(_ready&&BASE){
|
if(_ready&&BASE){
|
||||||
fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||||
@@ -761,12 +1369,12 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
setTimeout(pollTriggerClick, 2000);
|
setTimeout(pollTriggerClick, 2000);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// ── DEEP-INSPECT POLLING (v8: full body dump) ──
|
// ?? DEEP-INSPECT POLLING (v8: full body dump) ??
|
||||||
(function pollDeepInspect(){
|
(function pollDeepInspect(){
|
||||||
if(_ready&&BASE){
|
if(_ready&&BASE){
|
||||||
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||||
if(!d.inspect)return;
|
if(!d.inspect)return;
|
||||||
log('Deep inspect triggered — full body dump');
|
log('Deep inspect triggered ??full body dump');
|
||||||
// Force a fresh DOM dump
|
// Force a fresh DOM dump
|
||||||
_dumpCount = Math.max(0, _dumpCount - 1); // allow one more dump
|
_dumpCount = Math.max(0, _dumpCount - 1); // allow one more dump
|
||||||
dumpDOMStructure();
|
dumpDOMStructure();
|
||||||
@@ -816,3 +1424,4 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
})();
|
})();
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ let activeTrajectoryId = '';
|
|||||||
const recentPendingSteps = new Map<string, number>();
|
const recentPendingSteps = new Map<string, number>();
|
||||||
const PENDING_MEMORY_TTL_MS = 30_000;
|
const PENDING_MEMORY_TTL_MS = 30_000;
|
||||||
|
|
||||||
|
// v29: Last WAITING command from API — used by http-bridge for Always run enrichment
|
||||||
|
let lastWaitingCommand = { cmd: '', desc: '', ts: 0 };
|
||||||
|
|
||||||
// generateApprovalObserverScript → extracted to ./observer-script.ts
|
// generateApprovalObserverScript → extracted to ./observer-script.ts
|
||||||
const lastSnapshotText = new Map<string, string>();
|
const lastSnapshotText = new Map<string, string>();
|
||||||
|
|
||||||
@@ -79,6 +82,14 @@ export function getStepProbeContext(): { activeSessionId: string; sessionStalled
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v29: Get last WAITING command from Step Probe API.
|
||||||
|
* Used by http-bridge as fallback when Observer's extractContext returns generic "Always run".
|
||||||
|
*/
|
||||||
|
export function getLastWaitingCommand(): { cmd: string; desc: string; ts: number } {
|
||||||
|
return { ...lastWaitingCommand };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset pending state after successful approval.
|
* Reset pending state after successful approval.
|
||||||
* Called after WS response triggers approval in extension.ts.
|
* Called after WS response triggers approval in extension.ts.
|
||||||
@@ -203,6 +214,7 @@ function setupMonitor() {
|
|||||||
let pendingModifiedFilePaths: string[] = []; // full paths for diff review
|
let pendingModifiedFilePaths: string[] = []; // full paths for diff review
|
||||||
let pendingEditStepIndices: number[] = []; // step indices for AcknowledgeCascadeCodeEdit
|
let pendingEditStepIndices: number[] = []; // step indices for AcknowledgeCascadeCodeEdit
|
||||||
let lastResponseCaptureStep = -1; // dedup: don't capture same response twice
|
let lastResponseCaptureStep = -1; // dedup: don't capture same response twice
|
||||||
|
let lastLSFixPoll = 0; // v15: track last fixLSConnection() attempt for periodic retry
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
pollCount++;
|
pollCount++;
|
||||||
@@ -303,6 +315,34 @@ function setupMonitor() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── v15: Stale LS detection — periodic fixLSConnection() ──
|
||||||
|
// If the best session's lastModifiedTime hasn't changed for >5min,
|
||||||
|
// the extension might be connected to a stale LS. Retry fixLSConnection().
|
||||||
|
if (ctx.fixLSConnection && (pollCount - lastLSFixPoll) >= 60) {
|
||||||
|
// Check if current best session has stale data
|
||||||
|
const bestEntries = Object.entries(allTraj.trajectorySummaries) as [string, any][];
|
||||||
|
const hasStaleData = bestEntries.every(([, data]) => {
|
||||||
|
const mod = data.lastModifiedTime || '';
|
||||||
|
if (!mod) return true;
|
||||||
|
const modDate = new Date(mod);
|
||||||
|
return (Date.now() - modDate.getTime()) > 300_000; // 5 min
|
||||||
|
});
|
||||||
|
if (hasStaleData) {
|
||||||
|
lastLSFixPoll = pollCount;
|
||||||
|
ctx.logToFile(`[LS-AUTO-FIX] All sessions stale (>5min) — attempting fixLSConnection()`);
|
||||||
|
try {
|
||||||
|
const fixed = await ctx.fixLSConnection();
|
||||||
|
if (fixed) {
|
||||||
|
ctx.logToFile(`[LS-AUTO-FIX] ✅ Reconnected to new LS — next poll should have fresh data`);
|
||||||
|
} else {
|
||||||
|
ctx.logToFile(`[LS-AUTO-FIX] No better LS found`);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.logToFile(`[LS-AUTO-FIX] error: ${e.message?.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Filter to sessions owned by THIS window ──
|
// ── Filter to sessions owned by THIS window ──
|
||||||
// PRIMARY: Use trajectoryMetadata.workspaces URI to match sessions to this workspace.
|
// PRIMARY: Use trajectoryMetadata.workspaces URI to match sessions to this workspace.
|
||||||
// FALLBACK: Use bridge/register/ files for sessions without metadata.
|
// FALLBACK: Use bridge/register/ files for sessions without metadata.
|
||||||
@@ -422,13 +462,86 @@ function setupMonitor() {
|
|||||||
const delta = currentCount - lastKnownStepCount;
|
const delta = currentCount - lastKnownStepCount;
|
||||||
lastKnownStepCount = currentCount;
|
lastKnownStepCount = currentCount;
|
||||||
|
|
||||||
if (delta > 0) {
|
// ── v15: Heartbeat probe — detect step changes when summary API is stale ──
|
||||||
|
// GetAllCascadeTrajectories can return frozen stepCount/lastModifiedTime,
|
||||||
|
// v20: Heartbeat every 3 polls (~15s) — AG API never reports delta for active sessions
|
||||||
|
if (delta === 0 && ctx.sdk && ctx.activeSessionId && pollCount % 3 === 0) {
|
||||||
|
try {
|
||||||
|
const hbOffset = Math.max(0, currentCount - 5);
|
||||||
|
const hbResp = await ctx.sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
||||||
|
cascadeId: ctx.activeSessionId,
|
||||||
|
stepOffset: hbOffset,
|
||||||
|
verbosity: 1, // need content for capture
|
||||||
|
});
|
||||||
|
if (hbResp?.steps?.length > 0) {
|
||||||
|
const realStepCount = hbOffset + hbResp.steps.length;
|
||||||
|
if (realStepCount > lastKnownStepCount) {
|
||||||
|
ctx.logToFile(`[HEARTBEAT] stale! reported=${lastKnownStepCount} real=${realStepCount}`);
|
||||||
|
// Process new steps for RT-CAPTURE
|
||||||
|
for (let hi = 0; hi < hbResp.steps.length; hi++) {
|
||||||
|
const hs = hbResp.steps[hi];
|
||||||
|
const hIdx = hbOffset + hi;
|
||||||
|
if (hIdx <= lastResponseCaptureStep) continue;
|
||||||
|
const hType = hs?.type || '';
|
||||||
|
// Capture AI responses
|
||||||
|
if (hType.includes('PLANNER_RESPONSE') && hs?.status?.includes('DONE')) {
|
||||||
|
let text = extractPlannerText(hs) || '';
|
||||||
|
if (text.length > 10) {
|
||||||
|
lastResponseCaptureStep = hIdx;
|
||||||
|
ctx.logToFile(`[HB-CAPTURE] AI step=${hIdx} (${text.length} chars)`);
|
||||||
|
const truncated = text.length > 3500
|
||||||
|
? text.substring(0, 3500) + '\n\n_(이하 생략)_'
|
||||||
|
: text;
|
||||||
|
ctx.writeChatSnapshot(`💬 **AI 응답**\n\n${truncated}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Capture user messages
|
||||||
|
if (hType.includes('USER_INPUT') && hIdx > lastUserInputStepIdx) {
|
||||||
|
lastUserInputStepIdx = hIdx;
|
||||||
|
const ui = hs?.userInput;
|
||||||
|
const umText = (ui?.userResponse || ui?.text || '').trim();
|
||||||
|
if (umText.length > 2) {
|
||||||
|
const sentAt = ctx.recentDiscordSentTexts.get(umText);
|
||||||
|
if (!sentAt || (Date.now() - sentAt) > 60_000) {
|
||||||
|
const dedupKey = `user_msg:${umText}`;
|
||||||
|
const lastRelayed = lastSnapshotText.get(dedupKey);
|
||||||
|
if (!lastRelayed || (Date.now() - Number(lastRelayed)) > 30_000) {
|
||||||
|
lastSnapshotText.set(dedupKey, String(Date.now()));
|
||||||
|
const clientType = ui?.clientType || '';
|
||||||
|
const source = clientType.includes('IDE') ? 'AG 직접 입력' : 'API';
|
||||||
|
const truncated = umText.length > 800
|
||||||
|
? umText.substring(0, 800) + '\n\n_(이하 생략)_'
|
||||||
|
: umText;
|
||||||
|
ctx.writeChatSnapshot(`👤 **사용자 (${source})**\n\n${truncated}`);
|
||||||
|
ctx.logToFile(`[HB-CAPTURE] User step=${hIdx} (${umText.length} chars)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastKnownStepCount = realStepCount;
|
||||||
|
} else if (pollCount % 30 === 0) {
|
||||||
|
ctx.logToFile(`[HEARTBEAT] ok offset=${hbOffset} got=${hbResp.steps.length} real=${realStepCount} known=${lastKnownStepCount}`);
|
||||||
|
}
|
||||||
|
} else if (pollCount % 30 === 0) {
|
||||||
|
ctx.logToFile(`[HEARTBEAT] no steps returned for offset=${hbOffset}`);
|
||||||
|
}
|
||||||
|
} catch (hbErr: any) {
|
||||||
|
if (pollCount % 10 === 0) ctx.logToFile(`[HEARTBEAT] probe error: ${hbErr.message?.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalculate delta after heartbeat correction
|
||||||
|
const effectiveDelta = lastKnownStepCount - (currentCount > 0 ? currentCount : lastKnownStepCount);
|
||||||
|
const hasDelta = delta > 0 || effectiveDelta > 0;
|
||||||
|
|
||||||
|
if (hasDelta) {
|
||||||
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
||||||
|
|
||||||
// Real-time response capture: fetch latest steps on every delta>0
|
// Real-time response capture: fetch latest steps on every delta>0 or heartbeat
|
||||||
if (currentCount > lastResponseCaptureStep && ctx.sdk) {
|
if (lastKnownStepCount > lastResponseCaptureStep && ctx.sdk) {
|
||||||
try {
|
try {
|
||||||
const rtOffset = Math.max(0, currentCount - 3);
|
const rtOffset = Math.max(0, lastKnownStepCount - 3);
|
||||||
const rtResp = await ctx.sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
const rtResp = await ctx.sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
||||||
cascadeId: bestSessionId,
|
cascadeId: bestSessionId,
|
||||||
stepOffset: rtOffset,
|
stepOffset: rtOffset,
|
||||||
@@ -468,12 +581,46 @@ function setupMonitor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v20: Capture USER_INPUT steps for user message relay
|
||||||
|
if (sType.includes('USER_INPUT') && actualIdx > lastUserInputStepIdx) {
|
||||||
|
lastUserInputStepIdx = actualIdx;
|
||||||
|
const ui = s?.userInput;
|
||||||
|
const umText = (ui?.userResponse || ui?.text || s?.plannerResponse?.textContent || '').trim();
|
||||||
|
const clientType = ui?.clientType || '';
|
||||||
|
const isFromIDE = clientType.includes('IDE');
|
||||||
|
ctx.logToFile(`[RT-USER-MSG] step=${actualIdx} client=${clientType} text=${umText.substring(0, 100)}`);
|
||||||
|
|
||||||
|
if (umText.length > 2) {
|
||||||
|
// Skip echo: if text was recently sent from Discord
|
||||||
|
const sentAt = ctx.recentDiscordSentTexts.get(umText);
|
||||||
|
if (sentAt && (Date.now() - sentAt) < 60_000) {
|
||||||
|
ctx.recentDiscordSentTexts.delete(umText);
|
||||||
|
ctx.logToFile(`[RT-USER-MSG] skipped echo relay (Discord origin)`);
|
||||||
|
} else {
|
||||||
|
// Content-based dedup
|
||||||
|
const dedupKey = `user_msg:${umText}`;
|
||||||
|
const lastRelayed = lastSnapshotText.get(dedupKey);
|
||||||
|
if (!lastRelayed || (Date.now() - Number(lastRelayed)) > 30_000) {
|
||||||
|
lastSnapshotText.set(dedupKey, String(Date.now()));
|
||||||
|
const truncated = umText.length > 800
|
||||||
|
? umText.substring(0, 800) + '\n\n_(이하 생략)_'
|
||||||
|
: umText;
|
||||||
|
const source = isFromIDE ? 'AG 직접 입력' : 'API';
|
||||||
|
ctx.writeChatSnapshot(`👤 **사용자 (${source})**\n\n${truncated}`);
|
||||||
|
ctx.logToFile(`[RT-USER-MSG] relayed ${umText.length} chars`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (s?.status === 'CORTEX_STEP_STATUS_WAITING') {
|
if (s?.status === 'CORTEX_STEP_STATUS_WAITING') {
|
||||||
const toolCall = s?.metadata?.toolCall;
|
const toolCall = s?.metadata?.toolCall;
|
||||||
const toolName = toolCall?.name || (sType || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase();
|
const toolName = toolCall?.name || (sType || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase();
|
||||||
const { cmd: command, desc: description, isSafe: isSafeToAutoRun } = formatStepProbeCommand(toolName, actualIdx, sType || '', toolCall);
|
const { cmd: command, desc: description, isSafe: isSafeToAutoRun } = formatStepProbeCommand(toolName, actualIdx, sType || '', toolCall);
|
||||||
|
|
||||||
ctx.logToFile(`[STEP-PROBE] ★ WAITING (RT)! step=${actualIdx} type=${sType} cmd='${command}'`);
|
ctx.logToFile(`[STEP-PROBE] ★ WAITING (RT)! step=${actualIdx} type=${sType} cmd='${command}'`);
|
||||||
|
// v29: Save for http-bridge enrichment
|
||||||
|
lastWaitingCommand = { cmd: command, desc: description, ts: Date.now() };
|
||||||
|
|
||||||
if (actualIdx !== ctx.lastPendingStepIndex) {
|
if (actualIdx !== ctx.lastPendingStepIndex) {
|
||||||
ctx.stallProbed = true;
|
ctx.stallProbed = true;
|
||||||
@@ -661,6 +808,19 @@ function setupMonitor() {
|
|||||||
source: 'step_probe_offset',
|
source: 'step_probe_offset',
|
||||||
safe_to_auto_run: isSafeToAutoRun,
|
safe_to_auto_run: isSafeToAutoRun,
|
||||||
});
|
});
|
||||||
|
// v35: Auto-accept code edits (offset path)
|
||||||
|
if (['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(toolName)) {
|
||||||
|
ctx.logToFile(`[STEP-PROBE] v35: code_edit (offset) → auto-accepting in 500ms`);
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const vscode = require('vscode');
|
||||||
|
await vscode.commands.executeCommand('antigravity.prioritized.agentAcceptAllInFile');
|
||||||
|
ctx.logToFile(`[STEP-PROBE] ✅ agentAcceptAllInFile (offset) SUCCESS`);
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.logToFile(`[STEP-PROBE] ❌ agentAcceptAllInFile (offset): ${e.message?.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NOTE: no break — process ALL parallel WAITING steps
|
// NOTE: no break — process ALL parallel WAITING steps
|
||||||
@@ -714,6 +874,20 @@ function setupMonitor() {
|
|||||||
source: 'step_probe',
|
source: 'step_probe',
|
||||||
safe_to_auto_run: isSafeToAutoRun,
|
safe_to_auto_run: isSafeToAutoRun,
|
||||||
});
|
});
|
||||||
|
// v35: Auto-accept code edits via agentAcceptAllInFile
|
||||||
|
// Observer can't see "Accept all" button (different DOM layer)
|
||||||
|
if (['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(toolName)) {
|
||||||
|
ctx.logToFile(`[STEP-PROBE] v35: code_edit detected → auto-accepting in 500ms`);
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const vscode = require('vscode');
|
||||||
|
await vscode.commands.executeCommand('antigravity.prioritized.agentAcceptAllInFile');
|
||||||
|
ctx.logToFile(`[STEP-PROBE] ✅ agentAcceptAllInFile SUCCESS`);
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.logToFile(`[STEP-PROBE] ❌ agentAcceptAllInFile: ${e.message?.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NOTE: no break — process ALL parallel WAITING steps
|
// NOTE: no break — process ALL parallel WAITING steps
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import json; d=json.load(open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', encoding='utf-8', errors='ignore')); print('Total Nodes:', len(d.get('nodes',[])));
|
|
||||||
for n in d.get('nodes', []):
|
|
||||||
if 'agent' in n.get('label','').lower() or n.get('buttons'):
|
|
||||||
print(f"\n[Node] {n.get('label')}")
|
|
||||||
for b in n.get('buttons', []):
|
|
||||||
print(f" BTN: '{b.get('text')}' class='{b.get('class')}' hidden={b.get('hidden')} disabled={b.get('disabled')}")
|
|
||||||
for b in n.get('roleBtns', []):
|
|
||||||
print(f" ROLE-BTN: '{b.get('text')}'")
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import json, re; d=json.load(open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', encoding='utf-8', errors='ignore'))
|
|
||||||
approveRe=[re.compile(r'^(?:Always\s*)?Run\b', re.IGNORECASE), re.compile(r'^(?:Always\s*)?Accept\b', re.IGNORECASE)]
|
|
||||||
for b in d['nodes'][0]['buttons']:
|
|
||||||
t = b['text'].strip()
|
|
||||||
t = re.sub(r'(?:keyboard_arrow_up|keyboard_arrow_down)$', '', t, flags=re.IGNORECASE).strip()
|
|
||||||
if any(p.match(t) for p in approveRe): print(f"MATCH: {b['text']} -> {t}")
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
"""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)}")
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
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++;
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
Search AG bundle for UI component patterns, specifically:
|
|
||||||
1. Bot message container classes/selectors
|
|
||||||
2. Approval button patterns
|
|
||||||
3. Chat conversation structure
|
|
||||||
"""
|
|
||||||
import re, os, sys, io
|
|
||||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
||||||
|
|
||||||
bundle_path = r"C:\Users\Variet-Worker\AppData\Local\Programs\Antigravity\resources\app\out\jetskiAgent\main.js"
|
|
||||||
content = open(bundle_path, encoding='utf-8', errors='replace').read()
|
|
||||||
print(f"Bundle size: {len(content)} chars")
|
|
||||||
|
|
||||||
# Search for string literals containing relevant UI text
|
|
||||||
# These are the strings we SEE in the UI, so they must exist in the bundle
|
|
||||||
ui_strings = [
|
|
||||||
'Running', 'command', 'Always run', 'Cancel',
|
|
||||||
'Allow', 'Deny', 'content_copy',
|
|
||||||
'Accept', 'Reject', 'Approve',
|
|
||||||
'AI 대화', 'AI Chat', 'AI Response',
|
|
||||||
'keyboard_arrow', 'chevron',
|
|
||||||
'Run ', 'Send',
|
|
||||||
]
|
|
||||||
|
|
||||||
print("\n=== UI String contexts (20 chars around match) ===")
|
|
||||||
for s in ui_strings:
|
|
||||||
# Find the string and show surrounding context
|
|
||||||
idx = content.find(f'"{s}')
|
|
||||||
if idx == -1:
|
|
||||||
idx = content.find(f"'{s}")
|
|
||||||
if idx == -1:
|
|
||||||
idx = content.find(s)
|
|
||||||
if idx >= 0:
|
|
||||||
start = max(0, idx - 30)
|
|
||||||
end = min(len(content), idx + len(s) + 50)
|
|
||||||
ctx = content[start:end].replace('\n', ' ')
|
|
||||||
print(f" '{s}': ...{ctx}...")
|
|
||||||
|
|
||||||
# Specifically search for "Running" near button/onClick patterns
|
|
||||||
print("\n=== 'Running' in button context ===")
|
|
||||||
for m in re.finditer(r'Running.{0,5}command', content):
|
|
||||||
start = max(0, m.start() - 100)
|
|
||||||
end = min(len(content), m.end() + 100)
|
|
||||||
ctx = content[start:end].replace('\n', ' ')
|
|
||||||
print(f" @{m.start()}: ...{ctx[:200]}...")
|
|
||||||
|
|
||||||
# Search for className patterns with Tailwind classes
|
|
||||||
print("\n=== Tailwind class patterns near 'message' or 'chat' ===")
|
|
||||||
for m in re.finditer(r'"((?:flex|bg-|text-|rounded|p-|m-|w-|h-)[^"]{10,200})"', content):
|
|
||||||
cls = m.group(1)
|
|
||||||
if any(kw in content[max(0,m.start()-200):m.start()].lower() for kw in ['message', 'chat', 'response', 'bot', 'turn', 'agent']):
|
|
||||||
print(f" {cls[:100]}")
|
|
||||||
|
|
||||||
# Most important: search for React component names
|
|
||||||
print("\n=== React component names containing 'Message', 'Chat', 'Turn', 'Agent' ===")
|
|
||||||
for m in re.finditer(r'(?:function|class|const|var)\s+([A-Z][a-zA-Z]*(?:Message|Chat|Turn|Agent|Conversation|Response|Approval|Pending)[A-Za-z]*)', content):
|
|
||||||
print(f" {m.group(1)}")
|
|
||||||
|
|
||||||
# Search for data attributes
|
|
||||||
print("\n=== data-* attributes ===")
|
|
||||||
for m in re.finditer(r'"(data-[a-z-]+)"', content):
|
|
||||||
attr = m.group(1)
|
|
||||||
if attr not in ('data-vscode-context',):
|
|
||||||
print(f" {attr}")
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
"""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(path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
content = content.rstrip() + "\n" + entry
|
|
||||||
|
|
||||||
with open(path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
print("OK: devlog entry added")
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
url = "https://127.0.0.1:54285/exa.language_server_pb.LanguageServerService/GetDiagnostics"
|
|
||||||
ctx = ssl.create_default_context()
|
|
||||||
ctx.check_hostname = False
|
|
||||||
ctx.verify_mode = ssl.CERT_NONE
|
|
||||||
|
|
||||||
req = urllib.request.Request(url, data=json.dumps({}).encode(), headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'token': '5e529def-51fe-4bde-9955-5eca7299bd89'
|
|
||||||
})
|
|
||||||
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req, context=ctx) as response:
|
|
||||||
res = json.loads(response.read().decode('utf-8'))
|
|
||||||
recent = res.get('recentTrajectories', [])
|
|
||||||
print(f"HTTPS Total: {len(recent)}")
|
|
||||||
for r in recent:
|
|
||||||
print(r.get('googleAgentId'), r.get('lastModifiedTime'), r.get('status'))
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"HTTPS failed: {e}")
|
|
||||||
url = url.replace('https', 'http')
|
|
||||||
req = urllib.request.Request(url, data=json.dumps({}).encode(), headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'token': '5e529def-51fe-4bde-9955-5eca7299bd89'
|
|
||||||
})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req) as response:
|
|
||||||
res = json.loads(response.read().decode('utf-8'))
|
|
||||||
recent = res.get('recentTrajectories', [])
|
|
||||||
print(f"HTTP Total: {len(recent)}")
|
|
||||||
for r in recent:
|
|
||||||
print(r.get('googleAgentId'), r.get('lastModifiedTime'), r.get('status'))
|
|
||||||
except Exception as e2:
|
|
||||||
print(f"HTTP failed: {e2}")
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
|
|
||||||
def fetch_ls(port, csrf, method, args):
|
|
||||||
url = f"http://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/{method}"
|
|
||||||
req = urllib.request.Request(url, data=json.dumps(args).encode(), headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'x-antigravity-csrf-token': csrf
|
|
||||||
})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req) as response:
|
|
||||||
return json.loads(response.read().decode('utf-8'))
|
|
||||||
except Exception as e:
|
|
||||||
return f"Error: {e}"
|
|
||||||
|
|
||||||
print("Connecting to LS port 60517 (global)...")
|
|
||||||
csrf_global = "7c0c7815-ec11-48d6-9866-daab2690448f"
|
|
||||||
port_global = 60517
|
|
||||||
|
|
||||||
print("\n--- GetAllCascadeTrajectories on 60517 ---")
|
|
||||||
res = fetch_ls(port_global, csrf_global, "GetAllCascadeTrajectories", {"limit": 100, "descending": True})
|
|
||||||
if isinstance(res, dict) and 'trajectorySummaries' in res:
|
|
||||||
keys = list(res['trajectorySummaries'].keys())
|
|
||||||
print(f"Total entries: {len(keys)}")
|
|
||||||
for k in keys[:5]: print(f" - {k}")
|
|
||||||
if "370d1a09-1fa8-4aed-90d7-4024e36b3a2d" in keys:
|
|
||||||
print("YES! 370d1a09 found on 60517!")
|
|
||||||
else:
|
|
||||||
print(res)
|
|
||||||
|
|
||||||
print("\n--- GetCascadeTrajectory on 60517 for 370d1a09 ---")
|
|
||||||
res2 = fetch_ls(port_global, csrf_global, "GetCascadeTrajectory", {"googleAgentId": "370d1a09-1fa8-4aed-90d7-4024e36b3a2d"})
|
|
||||||
if isinstance(res2, dict) and 'trajectory' in res2:
|
|
||||||
print("Found trajectory!")
|
|
||||||
else:
|
|
||||||
print(res2)
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
def fetch_ls(port, csrf, method, args):
|
|
||||||
url = f"http://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/{method}"
|
|
||||||
for hs in ['x-antigravity-csrf-token', 'token']:
|
|
||||||
req = urllib.request.Request(url, data=json.dumps(args).encode(), headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
hs: csrf
|
|
||||||
})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req) as response:
|
|
||||||
return json.loads(response.read().decode('utf-8'))
|
|
||||||
except Exception as e:
|
|
||||||
continue
|
|
||||||
return "Failed"
|
|
||||||
|
|
||||||
# Process 1
|
|
||||||
p1 = 60517
|
|
||||||
c1 = "7c0c7815-ec11-48d6-9866-daab2690448f"
|
|
||||||
ec1 = "f348d963-9a36-43ea-a708-603e668b0063"
|
|
||||||
|
|
||||||
# Process 2
|
|
||||||
p2 = 54285
|
|
||||||
c2 = "5e529def-51fe-4bde-9955-5eca7299bd89"
|
|
||||||
ec2 = "b9bc824e-5543-4e26-99b3-2387fe4d2942"
|
|
||||||
|
|
||||||
target = "370d1a09-1fa8-4aed-90d7-4024e36b3a2d"
|
|
||||||
args = {"cascadeId": target, "verbosity": 1}
|
|
||||||
args_alt = {"googleAgentId": target}
|
|
||||||
args_all = {"limit": 10}
|
|
||||||
|
|
||||||
for port, csrf in [(p1, c1), (p1, ec1), (p2, c2), (p2, ec2)]:
|
|
||||||
res = fetch_ls(port, csrf, "GetCascadeTrajectorySteps", args)
|
|
||||||
print(f"Port {port} with csrf {csrf[:8]}: GetCascadeTrajectorySteps = {str(res)[:100]}")
|
|
||||||
res_alt = fetch_ls(port, csrf, "GetCascadeTrajectory", args_alt)
|
|
||||||
print(f"Port {port} with csrf {csrf[:8]}: GetCascadeTrajectory = {str(res_alt)[:100]}")
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
def fetch_ls(port, csrf, method, args):
|
|
||||||
url = f"http://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/{method}"
|
|
||||||
hs = 'x-antigravity-csrf-token'
|
|
||||||
req = urllib.request.Request(url, data=json.dumps(args).encode(), headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
hs: csrf
|
|
||||||
})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req) as response:
|
|
||||||
return json.loads(response.read().decode('utf-8'))
|
|
||||||
except Exception as e:
|
|
||||||
return f"Error: {e}"
|
|
||||||
|
|
||||||
p2 = 54285
|
|
||||||
c2 = "5e529def-51fe-4bde-9955-5eca7299bd89"
|
|
||||||
|
|
||||||
target = "370d1a09-1fa8-4aed-90d7-4024e36b3a2d"
|
|
||||||
args = {
|
|
||||||
"cascadeId": target,
|
|
||||||
"verbosity": 1,
|
|
||||||
"workspaceUri": "file:///c:/Users/Variet-Worker/Desktop/gravity_control"
|
|
||||||
}
|
|
||||||
|
|
||||||
res = fetch_ls(p2, c2, "GetCascadeTrajectorySteps", args)
|
|
||||||
print(f"GetCascadeTrajectorySteps with workspaceUri = {str(res)[:200]}")
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import json
|
|
||||||
from mcp_client import MCPClient
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
client = MCPClient()
|
|
||||||
await client.connect()
|
|
||||||
try:
|
|
||||||
# Get raw API response
|
|
||||||
resp = await client.request("EvaluateCascadeLspMethods", {"method": "GetDiagnostics", "params": "{}"})
|
|
||||||
print(json.dumps(resp, indent=2))
|
|
||||||
finally:
|
|
||||||
await client.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
async function testDiag() {
|
|
||||||
const bridgePath = process.cwd();
|
|
||||||
// we want to list latest brainDir and check state summary instead.
|
|
||||||
const brainDir = path.resolve(bridgePath, '.gemini/antigravity/brain');
|
|
||||||
if (fs.existsSync(brainDir)) {
|
|
||||||
const brainDirs = fs.readdirSync(brainDir, { withFileTypes: true })
|
|
||||||
.filter(dirent => dirent.isDirectory() && dirent.name.length === 36)
|
|
||||||
.map(dirent => {
|
|
||||||
const stats = fs.statSync(path.join(brainDir, dirent.name));
|
|
||||||
return { name: dirent.name, time: stats.mtimeMs };
|
|
||||||
})
|
|
||||||
.sort((a, b) => b.time - a.time);
|
|
||||||
|
|
||||||
console.log(`Latest brain UUIDs:`, brainDirs.slice(0, 3));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
testDiag();
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const readline = require('readline');
|
|
||||||
// Let's parse extension.log to find the steps! No wait, let's just make a script that uses rawRPC.
|
|
||||||
// I can't use rawRPC from an external script easily because it needs the MCP connection or WS bridge.
|
|
||||||
// Wait! The bot has a bridge!
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
"""
|
|
||||||
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}")
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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('────────────────────────────────────────');
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
"""
|
|
||||||
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}")
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const http = require('http');
|
|
||||||
|
|
||||||
const logPath = 'C:\\Users\\Variet-Worker\\.gemini\\antigravity\\bridge\\extension.log';
|
|
||||||
const log = fs.readFileSync(logPath, 'utf8');
|
|
||||||
const match = [...log.matchAll(/port:(\d+)/g)].pop();
|
|
||||||
if (!match) {
|
|
||||||
console.error('No port found');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const port = match[1];
|
|
||||||
console.log(`Port: ${port}`);
|
|
||||||
|
|
||||||
const req = http.request(`http://127.0.0.1:${port}/test-rpc`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
}, (res) => {
|
|
||||||
let data = '';
|
|
||||||
res.on('data', c => data += c);
|
|
||||||
res.on('end', () => {
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
const recent = json.recentTrajectories || [];
|
|
||||||
console.log(`recentTrajectories count: ${recent.length}`);
|
|
||||||
recent.forEach((t, i) => {
|
|
||||||
console.log(`[${i}] googleAgentId: ${t.googleAgentId} summary: ${t.summary} ws: ${t.trajectoryMetadata?.workspaces?.[0]?.workspaceFolderAbsoluteUri}`);
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
console.log(`Error parsing json: ${e.message}`);
|
|
||||||
console.log(`Raw data: ${data.substring(0, 200)}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.write(JSON.stringify({ method: 'GetDiagnostics', args: {} }));
|
|
||||||
req.end();
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
const http = require('http');
|
|
||||||
const port = 34332;
|
|
||||||
|
|
||||||
async function doRPC(method, args) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const req = http.request(`http://127.0.0.1:${port}/test-rpc`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
}, (res) => {
|
|
||||||
let data = '';
|
|
||||||
res.on('data', c => data += c);
|
|
||||||
res.on('end', () => resolve(JSON.parse(data)));
|
|
||||||
});
|
|
||||||
req.write(JSON.stringify({ method, args }));
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
let allIds = [];
|
|
||||||
let pageToken = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
const args = { descending: true };
|
|
||||||
if (pageToken) args.pageToken = pageToken;
|
|
||||||
|
|
||||||
console.log(`Fetching page ${i+1} with pageToken='${pageToken}'...`);
|
|
||||||
const res = await doRPC('GetAllCascadeTrajectories', args);
|
|
||||||
if (!res.trajectorySummaries) {
|
|
||||||
console.log("No summaries:", res);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = Object.keys(res.trajectorySummaries);
|
|
||||||
allIds.push(...keys);
|
|
||||||
|
|
||||||
console.log(` Got ${keys.length} items`);
|
|
||||||
if (keys.length > 0) {
|
|
||||||
console.log(` First: ${keys[0]}`);
|
|
||||||
console.log(` Last: ${keys[keys.length-1]}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.nextPageToken) {
|
|
||||||
pageToken = res.nextPageToken;
|
|
||||||
} else {
|
|
||||||
console.log("No nextPageToken.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Total collected: ${allIds.length}`);
|
|
||||||
if (allIds.includes("370d1a09-1fa8-4aed-90d7-4024e36b3a2d")) {
|
|
||||||
console.log(" FOUND 370d1a09-1fa8-4aed-90d7-4024e36b3a2d !!");
|
|
||||||
} else {
|
|
||||||
console.log(" Missing user active session.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
main();
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
const http = require('http');
|
|
||||||
const port = 34332;
|
|
||||||
|
|
||||||
function testArgs(args) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const req = http.request(`http://127.0.0.1:${port}/test-rpc`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
}, (res) => {
|
|
||||||
let data = '';
|
|
||||||
res.on('data', c => data += c);
|
|
||||||
res.on('end', () => {
|
|
||||||
console.log(`Args ${JSON.stringify(args)}: ${data.substring(0, 100)}`);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.write(JSON.stringify({ method: 'GetCascadeTrajectorySteps', args }));
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
const id = "370d1a09-1fa8-4aed-90d7-4024e36b3a2d";
|
|
||||||
await testArgs({ cascadeId: id, verbosity: 1 });
|
|
||||||
await testArgs({ trajectoryId: id, verbosity: 1 });
|
|
||||||
await testArgs({ id: id, verbosity: 1 });
|
|
||||||
await testArgs({ googleAgentId: id, verbosity: 1 });
|
|
||||||
}
|
|
||||||
run();
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
"""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()
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
"""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")
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
# Print first 50 buttons from nodes
|
|
||||||
count = 0
|
|
||||||
print('=== First 100 Buttons in DOM Nodes ===')
|
|
||||||
for node in data.get('nodes', []):
|
|
||||||
label = node.get('label', 'unknown')
|
|
||||||
btns = node.get('buttons', [])
|
|
||||||
if btns:
|
|
||||||
print(f'\n[Node] {label}')
|
|
||||||
for b in btns:
|
|
||||||
t = b.get('text', '').replace('\n', ' ').strip()
|
|
||||||
hidden = b.get("hidden")
|
|
||||||
cls = b.get("class")
|
|
||||||
if t:
|
|
||||||
print(f' - "{t[:50]}" (Hidden: {hidden}, Class: {cls[:30]})')
|
|
||||||
count += 1
|
|
||||||
if count > 100:
|
|
||||||
break
|
|
||||||
if count > 100:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Print first 50 buttons from webviews
|
|
||||||
count = 0
|
|
||||||
print('\n=== First 100 Buttons in Webviews ===')
|
|
||||||
for probe in data.get('webviewProbes', []):
|
|
||||||
if probe.get('success'):
|
|
||||||
pd = probe.get('data', {})
|
|
||||||
btns = pd.get('buttons', [])
|
|
||||||
label = pd.get('title', 'Unknown Title') + f" (URL: {pd.get('url', 'Unknown URL')})"
|
|
||||||
if btns:
|
|
||||||
print(f'\n[WebviewProbe {probe.get("index")}] {label}')
|
|
||||||
for b in btns:
|
|
||||||
t = b.get('text', '').replace('\n', ' ').strip()
|
|
||||||
hidden = b.get("hidden")
|
|
||||||
cls = b.get("class")
|
|
||||||
if t:
|
|
||||||
print(f' - "{t[:50]}" (Hidden: {hidden}, Class: {cls[:30]})')
|
|
||||||
count += 1
|
|
||||||
if count > 100:
|
|
||||||
break
|
|
||||||
if count > 100:
|
|
||||||
break
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Error reading JSON: {e}')
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
print(f"Total Nodes: {len(data.get('nodes', []))}")
|
|
||||||
for node in data.get('nodes', []):
|
|
||||||
label = node.get('label', 'unknown')
|
|
||||||
iframes = node.get('iframes', [])
|
|
||||||
webviews = node.get('webviews', [])
|
|
||||||
buttons = node.get('buttons', [])
|
|
||||||
print(f"[Node] {label}")
|
|
||||||
print(f" URL: {node.get('url', '')[:50]}")
|
|
||||||
print(f" Total Elements: {node.get('totalElements', 0)}")
|
|
||||||
print(f" Buttons count: {len(buttons)}")
|
|
||||||
print(f" Iframes count: {len(iframes)}")
|
|
||||||
print(f" Webviews count: {len(webviews)}")
|
|
||||||
if iframes:
|
|
||||||
for iframe in iframes:
|
|
||||||
print(f" - iframe[{iframe.get('index')}]: accessible={iframe.get('accessible')} src={iframe.get('src', '')[:50]}")
|
|
||||||
if webviews:
|
|
||||||
for w in webviews:
|
|
||||||
print(f" - webview[{w.get('index')}]: src={w.get('src', '')[:50]}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Error reading JSON: {e}')
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
print("SEARCHING FOR CHAT TEXT IN DUMP...")
|
|
||||||
found = False
|
|
||||||
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
|
|
||||||
raw_text = f.read()
|
|
||||||
if '스스로 만들고 스스로' in raw_text:
|
|
||||||
print("YES! The chat text IS in the raw JSON dump!")
|
|
||||||
found = True
|
|
||||||
elif 'Variet' in raw_text:
|
|
||||||
print("Found Variet in dump.")
|
|
||||||
else:
|
|
||||||
print("Chat text not found in raw dump.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Error reading JSON: {e}')
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(r'C:\Users\Variet-Worker\.gemini\antigravity\bridge\deep-inspect-result.json', 'r', encoding='utf-8', errors='ignore') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
for node in data.get('nodes', []):
|
|
||||||
label = node.get('label', 'unknown')
|
|
||||||
btns = node.get('buttons', [])
|
|
||||||
print(f"\n[Node] {label} (Total btns: {len(btns)})")
|
|
||||||
for i, b in enumerate(btns):
|
|
||||||
t = b.get('text', '').replace('\n', ' ').strip()
|
|
||||||
hidden = b.get("hidden")
|
|
||||||
cls = b.get("class")
|
|
||||||
print(f" {i:3d}: \"{t[:50]}\" (Hidden: {hidden}, Class: {cls[:30]})")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Error reading JSON: {e}')
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
/**
|
|
||||||
* html-patcher 수정 검증 스크립트
|
|
||||||
* 실제 workbench.html + 실제 observer-script 출력물로 패치 시뮬레이션
|
|
||||||
*/
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// 1. 실제 깨끗한 workbench.html 읽기
|
|
||||||
const htmlPath = path.join(
|
|
||||||
process.env.LOCALAPPDATA,
|
|
||||||
'Programs', 'Antigravity', 'resources', 'app', 'out',
|
|
||||||
'vs', 'code', 'electron-browser', 'workbench', 'workbench.html'
|
|
||||||
);
|
|
||||||
let html = fs.readFileSync(htmlPath, 'utf8');
|
|
||||||
console.log(`[1] Clean HTML: ${html.length} chars, ${html.split('\n').length} lines`);
|
|
||||||
console.log(` Has AG SDK: ${html.includes('AG SDK')}`);
|
|
||||||
|
|
||||||
// 2. 실제 observer-script.ts의 출력 시뮬레이션 (generateApprovalObserverScript)
|
|
||||||
const observerModule = require('./extension/out/observer-script');
|
|
||||||
const observerJS = observerModule.generateApprovalObserverScript(34332);
|
|
||||||
console.log(`[2] Observer JS: ${observerJS.length} chars`);
|
|
||||||
console.log(` Contains $': ${observerJS.includes("$'")}`);
|
|
||||||
console.log(` Contains ')$': ${observerJS.includes("')$")}`);
|
|
||||||
|
|
||||||
// 3. 패치 시뮬레이션 — 수정 전 (BUG)
|
|
||||||
const inlineBlock_buggy = `<!-- AG SDK INLINE [variet-gravity-bridge] -->\n<script>\n${observerJS}\n</script>\n<!-- /AG SDK INLINE [variet-gravity-bridge] -->`;
|
|
||||||
let html_buggy = html.replace('</body>', `\n${inlineBlock_buggy}\n</body>`);
|
|
||||||
|
|
||||||
// 4. 패치 시뮬레이션 — 수정 후 (FIX)
|
|
||||||
const inlineBlock = `<!-- AG SDK INLINE [variet-gravity-bridge] -->\n<script>\n${observerJS}\n</script>\n<!-- /AG SDK INLINE [variet-gravity-bridge] -->`;
|
|
||||||
const safeInlineBlock = inlineBlock.replace(/\$/g, '$$$$');
|
|
||||||
let html_fixed = html.replace('</body>', `\n${safeInlineBlock}\n</body>`);
|
|
||||||
|
|
||||||
console.log(`\n[3] BUGGY result: ${html_buggy.length} chars`);
|
|
||||||
console.log(`[4] FIXED result: ${html_fixed.length} chars`);
|
|
||||||
|
|
||||||
// 5. JS 코드 추출 및 SyntaxError 검증
|
|
||||||
function extractAndCheckJS(patchedHtml, label) {
|
|
||||||
const match = patchedHtml.match(/<script>\n([\s\S]*?)\n<\/script>/);
|
|
||||||
if (!match) {
|
|
||||||
console.log(`[${label}] ERROR: <script> block not found!`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const jsCode = match[1];
|
|
||||||
|
|
||||||
// Check if original HTML structure leaked into JS
|
|
||||||
const hasStartupComment = jsCode.includes('<!-- Startup');
|
|
||||||
const hasWorkbenchJS = jsCode.includes('<script src="./workbench.js"');
|
|
||||||
const hasClosingHtml = jsCode.includes('</html>') && !jsCode.includes("'</html>'");
|
|
||||||
|
|
||||||
console.log(`[${label}] JS code: ${jsCode.length} chars`);
|
|
||||||
console.log(` Leaked <!-- Startup -->: ${hasStartupComment} ${hasStartupComment ? '❌ CORRUPT' : '✅ OK'}`);
|
|
||||||
console.log(` Leaked <script src=workbench.js>: ${hasWorkbenchJS} ${hasWorkbenchJS ? '❌ CORRUPT' : '✅ OK'}`);
|
|
||||||
console.log(` Leaked </html>: ${hasClosingHtml} ${hasClosingHtml ? '❌ CORRUPT' : '✅ OK'}`);
|
|
||||||
|
|
||||||
// Check NOISE_RE is intact: should contain ')$', 'i'
|
|
||||||
const hasNoiseRE = jsCode.includes("')$', 'i'");
|
|
||||||
console.log(` NOISE_RE ')$', 'i' preserved: ${hasNoiseRE} ${hasNoiseRE ? '✅ OK' : '❌ BROKEN'}`);
|
|
||||||
|
|
||||||
// Try to parse JS
|
|
||||||
try {
|
|
||||||
new Function(jsCode);
|
|
||||||
console.log(` JS Syntax: ✅ VALID — no SyntaxError`);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
console.log(` JS Syntax: ❌ SyntaxError — ${e.message}`);
|
|
||||||
// Find the problematic line
|
|
||||||
const lines = jsCode.split('\n');
|
|
||||||
const lineMatch = e.message.match(/line (\d+)/);
|
|
||||||
if (lineMatch) {
|
|
||||||
const lineNum = parseInt(lineMatch[1]);
|
|
||||||
console.log(` Around line ${lineNum}:`);
|
|
||||||
for (let i = Math.max(0, lineNum - 3); i < Math.min(lines.length, lineNum + 3); i++) {
|
|
||||||
console.log(` ${i+1}: ${lines[i].substring(0, 100)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n===== BUGGY VERSION (before fix) =====');
|
|
||||||
const buggyOK = extractAndCheckJS(html_buggy, 'BUGGY');
|
|
||||||
|
|
||||||
console.log('\n===== FIXED VERSION (after fix) =====');
|
|
||||||
const fixedOK = extractAndCheckJS(html_fixed, 'FIXED');
|
|
||||||
|
|
||||||
console.log('\n===== VERDICT =====');
|
|
||||||
if (!buggyOK && fixedOK) {
|
|
||||||
console.log('✅ FIX CONFIRMED: Buggy version has SyntaxError, fixed version is clean.');
|
|
||||||
} else if (buggyOK && fixedOK) {
|
|
||||||
console.log('⚠️ Both versions work — the bug may not reproduce in this environment.');
|
|
||||||
} else if (!fixedOK) {
|
|
||||||
console.log('❌ FIX FAILED: Fixed version still has errors!');
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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();
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"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})"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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();
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
"""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]}")
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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));
|
|
||||||
})();
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Start-Sleep -Seconds 3
|
|
||||||
$log = Get-Content 'C:\Users\Variet-Worker\.gemini\antigravity\bridge\extension.log' -Tail 500
|
|
||||||
$port = 0
|
|
||||||
foreach ($line in $log) { if ($line -match 'port (\d+)') { $port = $Matches[1] } }
|
|
||||||
if ($port -gt 0) { Invoke-RestMethod -Uri "http://127.0.0.1:$port/deep-inspect"; Write-Host 'Dump success!' }
|
|
||||||
66
test_dom.js
66
test_dom.js
@@ -1,66 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const { JSDOM } = require("jsdom");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const observerModule = require("./extension/out/observer-script.js");
|
|
||||||
|
|
||||||
const dumpRaw = fs.readFileSync('C:\\Users\\Variet-Worker\\.gemini\\antigravity\\bridge\\dump_html.json', 'utf8');
|
|
||||||
const parseData = JSON.parse(dumpRaw);
|
|
||||||
let htmlStr = parseData.html;
|
|
||||||
|
|
||||||
// Inject fake port discovery node so it passes discoverPort()
|
|
||||||
htmlStr += `<div aria-label="Gravity Bridge Control port:1234"></div>`;
|
|
||||||
|
|
||||||
const dom = new JSDOM(htmlStr, { url: "http://localhost/", runScripts: "dangerously" });
|
|
||||||
const window = dom.window;
|
|
||||||
const document = window.document;
|
|
||||||
|
|
||||||
let testResults = [];
|
|
||||||
|
|
||||||
// Mock fetch for the observer
|
|
||||||
window.fetch = async (url, options) => {
|
|
||||||
if (url.includes('/ping')) {
|
|
||||||
return { text: async () => 'pong' };
|
|
||||||
}
|
|
||||||
if (url.includes('/pending') && options?.method === 'POST') {
|
|
||||||
const body = JSON.parse(options.body);
|
|
||||||
testResults.push("✅ POST /pending intercepted! Payload:");
|
|
||||||
testResults.push(JSON.stringify(body, null, 2));
|
|
||||||
return { json: async () => ({ok: true, request_id: body.request_id}) };
|
|
||||||
}
|
|
||||||
return { json: async () => ({}) };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fallback overrides
|
|
||||||
window.console.log = (m) => testResults.push(`[Script Log] ${m}`);
|
|
||||||
window.MutationObserver = window.MutationObserver || class { observe(){} };
|
|
||||||
window.AbortSignal = { timeout: () => ({}) };
|
|
||||||
|
|
||||||
let scriptStr = observerModule.generateApprovalObserverScript(1234);
|
|
||||||
// Brutally bypass discoverPort block and force initialization
|
|
||||||
scriptStr = scriptStr.replace(/discoverPort\(function\(port\)\{[\s\S]*?\}\);/, "BASE='http://127.0.0.1:1234';_ready=true;startObserver();");
|
|
||||||
scriptStr = scriptStr.replace("function scan(){", "function scan(){ log('scan() STAGE 1'); log('buttons in DOM: ' + document.querySelectorAll('button').length);");
|
|
||||||
scriptStr = scriptStr.replace("_obs=true;", "_obs=true; log('Forcing scan'); scan();");
|
|
||||||
|
|
||||||
// Run script inside JSDOM
|
|
||||||
const scriptEl = document.createElement("script");
|
|
||||||
scriptEl.textContent = scriptStr;
|
|
||||||
document.body.appendChild(scriptEl);
|
|
||||||
|
|
||||||
// Wait 3 seconds for discoverPort -> ping -> startObserver -> scheduleScan to execute
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log("=== TEST RESULTS ===");
|
|
||||||
console.log(testResults.join("\n"));
|
|
||||||
if (!testResults.some(l => l.includes('POST /pending intercepted'))) {
|
|
||||||
console.error("❌ FAILED: No POST to /pending was made. The DOM scan failed to find the dummy button or extract context.");
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
console.log("✅ SUCCESS: The DOM extraction is functioning properly.");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Test Harness Error:", e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
144
test_dom_mock.js
144
test_dom_mock.js
@@ -1,144 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const jsdom = require('jsdom');
|
|
||||||
const { JSDOM } = jsdom;
|
|
||||||
|
|
||||||
const observerModule = require('./extension/out/observer-script.js');
|
|
||||||
const observerCode = observerModule.generateApprovalObserverScript(8080);
|
|
||||||
const rawDump = JSON.parse(fs.readFileSync('C:/Users/Variet-Worker/.gemini/antigravity/bridge/dump_html.json', 'utf8'));
|
|
||||||
|
|
||||||
// Instantiate DOM
|
|
||||||
const dom = new JSDOM(rawDump.html, { runScripts: "dangerously", pretendToBeVisual: true, url: "http://localhost/" });
|
|
||||||
const window = dom.window;
|
|
||||||
const document = window.document;
|
|
||||||
|
|
||||||
// Polyfill offsetParent for visibility check
|
|
||||||
Object.defineProperty(window.HTMLElement.prototype, 'offsetParent', {
|
|
||||||
get() { return document.body; }
|
|
||||||
});
|
|
||||||
Object.defineProperty(window.HTMLElement.prototype, 'style', {
|
|
||||||
get() { return { display: 'block' }; }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mock innerText (JSDOM does not fully support it, but generic walker uses nodeValue / textContent)
|
|
||||||
// Our logic uses nodeValue for TextNodes, so it will work in JSDOM out of the box!
|
|
||||||
|
|
||||||
// Setup the DOM tree to perfectly match a real Chat conversation
|
|
||||||
const titleSpan = document.querySelector('span[title^="command("]');
|
|
||||||
if(!titleSpan) {
|
|
||||||
console.error("COULD NOT FIND command SPAN in dump?!");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
// Find the card container
|
|
||||||
let toolContainer = titleSpan.parentElement;
|
|
||||||
while(toolContainer && !toolContainer.className.includes("border-gray-500/10") && !toolContainer.className.includes("bg-gray-500/10")) {
|
|
||||||
toolContainer = toolContainer.parentElement;
|
|
||||||
}
|
|
||||||
if(!toolContainer) toolContainer = titleSpan.parentElement; // fallback
|
|
||||||
|
|
||||||
// Create an AI text block just above it
|
|
||||||
const aiChat = document.createElement('div');
|
|
||||||
aiChat.className = 'markdown prose';
|
|
||||||
aiChat.innerHTML = '<p>안녕하세요! 시스템을 수정하기 위해 요청하신 작업을 시작합니다. <b>디스코드 릴레이 기능 복구</b>를 위해 스크립트를 실행하겠습니다.</p>';
|
|
||||||
|
|
||||||
// Wrap them up in the turn container
|
|
||||||
const parent = toolContainer.parentElement;
|
|
||||||
const convoWrapper = document.createElement('div');
|
|
||||||
convoWrapper.className = 'bg-agent-convo-background';
|
|
||||||
parent.insertBefore(convoWrapper, toolContainer);
|
|
||||||
convoWrapper.appendChild(aiChat);
|
|
||||||
convoWrapper.appendChild(toolContainer); // Move tool inside the convo wrapper as a sibling to AI chat
|
|
||||||
|
|
||||||
// Add action button to the tool container
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.innerHTML = '<span class="truncate">Allow</span>';
|
|
||||||
toolContainer.appendChild(btn);
|
|
||||||
|
|
||||||
console.log("Mock Button offsetParent:", btn.offsetParent ? btn.offsetParent.tagName : 'null');
|
|
||||||
console.log("Mock Button display:", btn.style.display);
|
|
||||||
console.log("Mock Button text:", btn.textContent);
|
|
||||||
|
|
||||||
// MOCK FETCH
|
|
||||||
const fetchCalls = [];
|
|
||||||
window.fetch = function(url, options) {
|
|
||||||
fetchCalls.push({url, options});
|
|
||||||
if (url.includes('/ping')) {
|
|
||||||
return Promise.resolve({ text: function() { return Promise.resolve('pong'); } });
|
|
||||||
}
|
|
||||||
if (url.includes('/pending')) {
|
|
||||||
return Promise.resolve({ json: function() { return Promise.resolve({ok: true, request_id: 'test-rid'}); } });
|
|
||||||
}
|
|
||||||
return Promise.resolve({ json: function() { return Promise.resolve({}); } });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Polyfill offsetParent for visibility check
|
|
||||||
Object.defineProperty(window.HTMLElement.prototype, 'offsetParent', {
|
|
||||||
get() { return document.body; }
|
|
||||||
});
|
|
||||||
Object.defineProperty(window.HTMLElement.prototype, 'style', {
|
|
||||||
get() { return { display: 'block' }; }
|
|
||||||
});
|
|
||||||
|
|
||||||
const originalLog = console.log;
|
|
||||||
window.console.log = function(...args) {
|
|
||||||
if (args.length > 0 && typeof args[0] === 'string' && args[0].includes("NOT MATCHED")) {
|
|
||||||
let txt = args[1];
|
|
||||||
let codes = [];
|
|
||||||
if (txt) {
|
|
||||||
for(let i=0; i<txt.length; i++) codes.push(txt.charCodeAt(i));
|
|
||||||
}
|
|
||||||
originalLog('[JSDOM-WIN]', args[0], `\nRAW="${txt}"\nCODES=[${codes.join(',')}]`);
|
|
||||||
} else {
|
|
||||||
originalLog('[JSDOM-WIN]', ...args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inject the observer
|
|
||||||
const scriptEl = document.createElement('script');
|
|
||||||
scriptEl.textContent = observerCode;
|
|
||||||
document.body.appendChild(scriptEl);
|
|
||||||
|
|
||||||
console.log("Observer injected, waiting for cycles...");
|
|
||||||
console.log("Total Buttons in DOM:", document.querySelectorAll('button').length);
|
|
||||||
|
|
||||||
// Emulate UI mutation to trigger the MutationObserver and force an instant scan()
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log("Triggering DOM mutation to force scan()...");
|
|
||||||
document.body.appendChild(document.createElement('span'));
|
|
||||||
}, 1500);
|
|
||||||
|
|
||||||
// Give it time to finish scan().
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log("\n====== FETCH CALLS ======");
|
|
||||||
if(fetchCalls.length === 0) console.log("NO FETCH CALLS MADE!");
|
|
||||||
|
|
||||||
fetchCalls.forEach(c => {
|
|
||||||
console.log(`\n[${c.options ? c.options.method || 'GET' : 'GET'}] ${c.url}`);
|
|
||||||
if(c.options && c.options.body) {
|
|
||||||
try {
|
|
||||||
let j = JSON.parse(c.options.body);
|
|
||||||
console.log("[BODY] request_id:", j.request_id);
|
|
||||||
console.log("[BODY] command:", j.command);
|
|
||||||
console.log("[BODY] description:\n" + "=".repeat(40) + "\n" + j.description + "\n" + "=".repeat(40));
|
|
||||||
} catch(e) {
|
|
||||||
console.log("[BODY]", c.options.body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine success
|
|
||||||
const pendingCall = fetchCalls.find(c => c.url.includes('/pending'));
|
|
||||||
if(pendingCall && pendingCall.options && pendingCall.options.body) {
|
|
||||||
const payload = JSON.parse(pendingCall.options.body);
|
|
||||||
if(payload.description.includes("안녕하세요!") && payload.description.includes("METHOD=TITLE_SPAN")) {
|
|
||||||
console.log("\n✅ SUCCESS: Both Chat Body & Tool Command effectively extracted!");
|
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
console.log("\n❌ FAIL: Payload description missing either chat text or command string!");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("\n❌ FAIL: /pending never called!");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}, 4000);
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const { JSDOM } = require("jsdom");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const dumpRaw = fs.readFileSync('C:\\Users\\Variet-Worker\\.gemini\\antigravity\\bridge\\dump_html.json', 'utf8');
|
|
||||||
const parseData = JSON.parse(dumpRaw);
|
|
||||||
let htmlStr = parseData.html;
|
|
||||||
|
|
||||||
const dom = new JSDOM(htmlStr);
|
|
||||||
const document = dom.window.document;
|
|
||||||
|
|
||||||
// Direct copy of functions from observer-script.ts
|
|
||||||
function findButtonContainer(btn){
|
|
||||||
return btn.closest('.p-1')
|
|
||||||
|| btn.closest('.bg-agent-convo-background')
|
|
||||||
|| btn.closest('[class*="border-gray-500/10"]')
|
|
||||||
|| btn.closest('.monaco-list-row')
|
|
||||||
|| btn.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanButtonText(btn) {
|
|
||||||
if (!btn) return '';
|
|
||||||
var tr = btn.querySelector('.truncate');
|
|
||||||
var txt = (tr ? tr.textContent : btn.textContent) || '';
|
|
||||||
return txt.trim().replace(/(Alt|Ctrl|Shift|Meta)\+.*/i,'').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractContext(b){
|
|
||||||
var container = findButtonContainer(b);
|
|
||||||
if (!container) return "ERROR_NO_CONTAINER";
|
|
||||||
|
|
||||||
var titleSpans = container.querySelectorAll('span[title^="command("]');
|
|
||||||
if (titleSpans && titleSpans.length > 0) {
|
|
||||||
var t = titleSpans[0].getAttribute('title');
|
|
||||||
if (t && t.length > 5) return "METHOD=TITLE_SPAN | " + t.substring(0, 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
var preEls = container.querySelectorAll('pre');
|
|
||||||
if (preEls && preEls.length > 0) {
|
|
||||||
var t2 = (preEls[preEls.length-1].textContent || '').trim();
|
|
||||||
if (t2.length > 2) return "METHOD=PRE_SPAN | " + t2.substring(0, 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
var codeText = '';
|
|
||||||
var codes = container.querySelectorAll('code, [class*="command"]');
|
|
||||||
for(var i=0; i<codes.length; i++) {
|
|
||||||
codeText += (codes[i].textContent || '').trim() + ' ';
|
|
||||||
}
|
|
||||||
if (codeText.length > 2) return "METHOD=CODES | " + codeText.trim().substring(0, 800);
|
|
||||||
|
|
||||||
var fallback = (container.textContent || '').replace(cleanButtonText(b), '').trim();
|
|
||||||
return "METHOD=FALLBACK | " + fallback.substring(0, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// RUN TEST
|
|
||||||
const allBtns = document.querySelectorAll('button');
|
|
||||||
console.log(`Total buttons found: ${allBtns.length}`);
|
|
||||||
|
|
||||||
let tested = 0;
|
|
||||||
for(let j=0; j<allBtns.length; j++) {
|
|
||||||
let b = allBtns[j];
|
|
||||||
let txt = cleanButtonText(b);
|
|
||||||
if (txt.length <= 1) continue; // Icon
|
|
||||||
|
|
||||||
var PATS = [
|
|
||||||
{ type: 'command', re: /^(?:Always\s*)?Run\b/i },
|
|
||||||
{ type: 'permission', re: /^(?:Always\s*)?Allow\b/i },
|
|
||||||
{ type: 'permission', re: /^(?:Always\s*)?Approve\b/i },
|
|
||||||
{ type: 'diff_review', re: /^(?:Always\s*)?Accept\b/i }
|
|
||||||
];
|
|
||||||
|
|
||||||
var matchedType=null;
|
|
||||||
for(var p=0;p<PATS.length;p++){
|
|
||||||
if(PATS[p].re.test(txt)){
|
|
||||||
matchedType=PATS[p].type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matchedType) continue;
|
|
||||||
|
|
||||||
console.log(`\n✅ Matched Button: "${txt}" (Type: ${matchedType})`);
|
|
||||||
console.log(` Extracting Context Data...`);
|
|
||||||
console.log(` -> ` + extractContext(b));
|
|
||||||
tested++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tested === 0) {
|
|
||||||
console.log("❌ No actionable buttons matched!");
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
console.log("\n✅ SUCCESS: Context fully extracted via DOM script logic.");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
31
test_rpc.js
31
test_rpc.js
@@ -1,31 +0,0 @@
|
|||||||
const { LSBridge } = require('./extension/out/sdk/ls-bridge');
|
|
||||||
|
|
||||||
async function test() {
|
|
||||||
const ls = new LSBridge();
|
|
||||||
await ls.connect();
|
|
||||||
|
|
||||||
console.log("Testing { limit: 5, descending: true }...");
|
|
||||||
let start = Date.now();
|
|
||||||
const res = await ls._rpc('GetAllCascadeTrajectories', { limit: 5, descending: true });
|
|
||||||
let duration = Date.now() - start;
|
|
||||||
|
|
||||||
const summaries = res.trajectorySummaries || {};
|
|
||||||
const keys = Object.keys(summaries);
|
|
||||||
console.log(`Execution time: ${duration}ms`);
|
|
||||||
console.log(`Returned entries: ${keys.length}`);
|
|
||||||
|
|
||||||
keys.slice(0, 5).forEach((k, idx) => {
|
|
||||||
const modT = summaries[k].lastModifiedTime || summaries[k].lastModifiedTimestamp || 'UNKNOWN';
|
|
||||||
console.log(`[${idx}] id=${k.substring(0,8)} mod=${modT} status=${summaries[k].status}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("\nTesting { limit: 100, descending: true }...");
|
|
||||||
start = Date.now();
|
|
||||||
const res100 = await ls._rpc('GetAllCascadeTrajectories', { limit: 100, descending: true });
|
|
||||||
duration = Date.now() - start;
|
|
||||||
console.log(`Execution time: ${duration}ms`);
|
|
||||||
console.log(`Returned entries: ${Object.keys(res100.trajectorySummaries || {}).length}`);
|
|
||||||
|
|
||||||
ls.disconnect();
|
|
||||||
}
|
|
||||||
test();
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// test_ws_logic.js
|
|
||||||
class FakeWS {
|
|
||||||
constructor() {
|
|
||||||
this.msgLog = [];
|
|
||||||
this.terminated = false;
|
|
||||||
}
|
|
||||||
send(msg) {
|
|
||||||
this.msgLog.push(msg);
|
|
||||||
}
|
|
||||||
terminate() {
|
|
||||||
this.terminated = true;
|
|
||||||
}
|
|
||||||
close() {
|
|
||||||
this.terminated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIMULATE _startHeartbeat() logic from ws-client.ts v0.5.12
|
|
||||||
function testLogic(isNodeWs, serverSendsPong) {
|
|
||||||
let ws = new FakeWS();
|
|
||||||
let connected = true;
|
|
||||||
let lastPongTime = Date.now();
|
|
||||||
let forceHeartbeatTimeoutIfNoPong = serverSendsPong;
|
|
||||||
let checkCounter = 0;
|
|
||||||
|
|
||||||
// Fast forward 61 seconds in time
|
|
||||||
let timeElapsed = 61000;
|
|
||||||
let currentNow = Date.now() + timeElapsed;
|
|
||||||
|
|
||||||
// Simulate heartbeat timeout logic
|
|
||||||
let conditionMet = false;
|
|
||||||
if ((isNodeWs || forceHeartbeatTimeoutIfNoPong) && currentNow - lastPongTime > 60000) {
|
|
||||||
conditionMet = true;
|
|
||||||
ws.terminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
conditionMet: conditionMet,
|
|
||||||
terminated: ws.terminated
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Scenario 1: Node WS (native ping/pong) MUST enforce 60s timeout:");
|
|
||||||
console.log(testLogic(true, false)); // expect true, true
|
|
||||||
|
|
||||||
console.log("\nScenario 2: Browser WS (fallback) + NO JSON PONG FROM SERVER MUST NOT enforce 60s timeout:");
|
|
||||||
console.log(testLogic(false, false)); // expect false, false (PREVENTS FALSE POSITIVE)
|
|
||||||
|
|
||||||
console.log("\nScenario 3: Browser WS (fallback) + JSON PONG FROM SERVER MUST enforce 60s timeout:");
|
|
||||||
console.log(testLogic(false, true)); // expect true, true (DETECTS ZOMBIE)
|
|
||||||
Reference in New Issue
Block a user