Compare commits
2 Commits
70dc301dca
...
eef59e6bb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eef59e6bb2 | ||
|
|
a4d7286bce |
@@ -1,253 +1,266 @@
|
||||
# Known Issues & Lessons Learned
|
||||
|
||||
> **이 파일은 SSOT(Single Source of Truth)입니다.**
|
||||
> 디버깅이나 구현 전에 **반드시** 이 파일을 확인하세요.
|
||||
> 세션 종료 시 새로 발견된 이슈를 이 파일에 추가합니다.
|
||||
|
||||
|
||||
> **<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>슂.
|
||||
> 해결 완료된 과거 이슈는 [`known-issues-archive.md`](file:///c:/Users/Variet-Worker/Desktop/gravity_control/.agents/references/known-issues-archive.md)에 보관되어 있습니다.
|
||||
> 비슷한 문제가 재발하면 archive에서 검색하세요.
|
||||
|
||||
> <EFBFBD>꽭<EFBFBD>뀡 醫낅즺 <20>떆 <20>깉濡<EAB989> 諛쒓껄<EC9293>맂 <20>씠<EFBFBD>뒋瑜<EB928B> <20>씠 <20>뙆<EFBFBD>씪<EFBFBD>뿉 異붽<E795B0><EBB6BD><EFBFBD>빀<EFBFBD>땲<EFBFBD>떎.
|
||||
|
||||
|
||||
|
||||
### [2026-04-09] [Bridge] Discord Body Content Missing Due to Step Probe Dummy Payload
|
||||
- **증상**: 대규모 UI 마이그레이션 후, 디스코드 승인 메시지 본문에 실행할 코드/명령어가 완전히 누락되고 "Step #15"와 같은 디폴트 텍스트만 전송됨.
|
||||
- **원인**: Native UI 변경으로 인해 DOM observer가 추출한 버튼 텍스트("Always run")가 `http-bridge.ts` 필터 우회 및 bot.py에서 지연(defer) 처리됨. 반면 `step-probe.ts`가 `GetAllCascadeTrajectories` 폴링을 통해 동시에 발생시킨 dummy pending payload (명령어 상세 내용이 없이 `Step #XX` 라는 텍스트만 포함)가 봇에 의해 먼저 자동 승인되면서 정작 실제 코드 영역 정보가 증발함.
|
||||
- **해결**: `step-probe.ts` 내에 `formatStepProbeCommand` 헬퍼 함수를 추가하여, WAITING 상태 스텝의 `argumentsJson` 데이터를 직접 파싱하고 `CommandLine`, `TargetFile` 등 실제 명령어와 상세 인자/코드를 `command`와 `description`으로 할당하여 브릿지로 넘기도록 패치함. DOM 옵저버의 불안정성과 관계없이 일관된 본문 전달 보장.
|
||||
- **주의**: UI 스크래핑에 의존하는 DOM Observer 방식은 UI 레이아웃, 아이콘 삽입 등에 취약하므로, 상세 페이로드 추출은 항상 100% 신뢰 가능한 SDK RPC(`step-probe.ts`) 데이터를 우선 사용하도록 구성해야 함.
|
||||
> [!TIP]
|
||||
|
||||
> <EFBFBD>빐寃<EFBFBD> <20>셿猷뚮맂 怨쇨굅 <20>씠<EFBFBD>뒋<EFBFBD>뒗 [`known-issues-archive.md`](file:///c:/Users/Variet-Worker/Desktop/gravity_control/.agents/references/known-issues-archive.md)<29>뿉 蹂닿<E8B982><EB8BBF><EFBFBD>릺<EFBFBD>뼱 <20>엳<EFBFBD>뒿<EFBFBD>땲<EFBFBD>떎.
|
||||
|
||||
> 鍮꾩듂<EFBFBD>븳 臾몄젣媛<ECA0A3> <20>옱諛쒗븯硫<EBB8AF> archive<76>뿉<EFBFBD>꽌 寃<><E5AF83>깋<EFBFBD>븯<EFBFBD>꽭<EFBFBD>슂.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
### [2026-04-12] [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` 속성 존재 여부 반드시 확인 필요
|
||||
|
||||
- **증상**: 다중 원격 컴퓨터에서 동일한 프로젝트명으로 실행된 VS Code들이 서로의 `execute JavaScript` (Allow) 승인 신호를 가로채거나 엉뚱한 서버로 보냄.
|
||||
- **원인**: Extension이 `workbench.html`에 스크립트를 주입할 때 결정론적 포트를 하드코딩했는데, 전역 캐시된 HTML 파일을 모든 로컬/원격 연결이 공유하면서 마지막에 열린 프로젝트의 포트 번호로 덮어씌워짐.
|
||||
- **해결**: `extension.ts`에서 상태 표시줄(Status Bar) `tooltip`에 포트를 주입하고, `observer-script.ts`에서 DOM 쿼리를 통해 동적으로 자신의 창(Window)에 할당된 포트를 찾아내도록 수정. `vscode.env.asExternalUri`를 사용하여 포트 충돌 시 우회된 주소까지 로컬 포워딩에 매핑되도록 지원.
|
||||
- **주의**: VS Code UI 코어(HTML) 패치 시, 여러 창(Window)이나 다중 원격 접속 시 환경(Scope) 분리에 각별한 주의가 필요함. 전역 자원에 의존하는 하드코딩 지양.
|
||||
|
||||
### [2026-04-09] [Bridge] Discord Body Content Missing Due to Step Probe Dummy Payload
|
||||
- **利앹긽**: <EFBFBD><EFBFBD><EFBFBD>洹쒕え UI 留덉씠洹몃젅<EBAA83>씠<EFBFBD>뀡 <20>썑, <20>뵒<EFBFBD>뒪肄붾뱶 <20>듅<EFBFBD>씤 硫붿떆吏<EB9686> 蹂몃Ц<EBAA83>뿉 <20>떎<EFBFBD>뻾<EFBFBD>븷 肄붾뱶/紐낅졊<EB8285>뼱媛<EBBCB1> <20>셿<EFBFBD>쟾<EFBFBD>엳 <20>늻<EFBFBD>씫<EFBFBD>릺怨<EBA6BA> "Step #15"<22><><EFBFBD> 媛숈<E5AA9B><EC8888> <20>뵒<EFBFBD>뤃<EFBFBD>듃 <20>뀓<EFBFBD>뒪<EFBFBD>듃留<EB9383> <20>쟾<EFBFBD>넚<EFBFBD>맖.
|
||||
- **<EFBFBD>썝<EFBFBD>씤**: Native UI 蹂<>寃쎌쑝濡<EC919D> <20>씤<EFBFBD>빐 DOM observer媛<72> 異붿텧<EBB6BF>븳 踰꾪듉 <20>뀓<EFBFBD>뒪<EFBFBD>듃("Always run")媛<> `http-bridge.ts` <20>븘<EFBFBD>꽣 <20>슦<EFBFBD>쉶 諛<> bot.py<70>뿉<EFBFBD>꽌 吏<><EFA79E>뿰(defer) 泥섎━<EC848E>맖. 諛섎㈃ `step-probe.ts`媛<EFBFBD> `GetAllCascadeTrajectories` <20>뤃留곸쓣 <20>넻<EFBFBD>빐 <20>룞<EFBFBD>떆<EFBFBD>뿉 諛쒖깮<EC9296>떆<EFBFBD>궓 dummy pending payload (紐낅졊<EB8285>뼱 <20>긽<EFBFBD>꽭 <20>궡<EFBFBD>슜<EFBFBD>씠 <20>뾾<EFBFBD>씠 `Step #XX` <20>씪<EFBFBD>뒗 <20>뀓<EFBFBD>뒪<EFBFBD>듃留<EB9383> <20>룷<EFBFBD>븿)媛<> 遊뉗뿉 <20>쓽<EFBFBD>빐 癒쇱<E79992><EC87B1> <20>옄<EFBFBD>룞 <20>듅<EFBFBD>씤<EFBFBD>릺硫댁꽌 <20>젙<EFBFBD>옉 <20>떎<EFBFBD>젣 肄붾뱶 <20>쁺<EFBFBD>뿭 <20>젙蹂닿<E8B982><EB8BBF> 利앸컻<EC95B8>븿.
|
||||
- **<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>븿.
|
||||
|
||||
|
||||
|
||||
## <20>룷留<EBA3B7>
|
||||
|
||||
|
||||
|
||||
### [2026-03-23] [Extension] Cross-Project DOM Observer Leakage
|
||||
|
||||
- **증상**: 봇이 켜져 있는 상태에서 Discord 채널(g-project-name)을 삭제하면, 봇이 삭제를 인지하지 못하고 새 채널을 생성하지 않으며 메시지도 증발함.
|
||||
- **원인**: ot.py의 self.project_channels 딕셔너리에 채널 객체가 캐시되어 있어, API 호출 없이 캐시된(삭제된) 채널로 메시지를 보내려 시도하다 404 에러 발생 후 실패함.
|
||||
- **해결**: 채널 맵핑이 꼬였을 때는 **Python 봇(Docker 컨테이너)을 재시작**하여 캐시를 초기화하고 채널 목록을 새로 갱신하게 함.
|
||||
- **주의**: 채널 관리는 캐시에 의존하기 때문에 강제로 Discord UI에서 채널을 지웠을 때는 반드시 봇을 재구동해야 함.
|
||||
- **利앹긽**: <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>빐寃<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>썝.
|
||||
|
||||
- **증상**: ariet-llm 창에서 켰으나 gravity_control의 백그라운드 구동 중인 LS에 연결되어 자기 자신 창의 신호를 잡지 못함.
|
||||
- **원인**: 여러 VS Code 창을 띄웠을 때 어떤 창에서는 Antigravity 패널을 누르지 않아 전용 LS가 시작되지 않음. ixLSConnection()이 자기 몫의 LS를 찾지 못하고 fallback으로 기존에 떠 있던 다른 창의 LS에 연결됨.
|
||||
- **해결**: 대상 창에서 Developer: Reload Window 실행 후 **사이드바의 로컬 Antigravity 챗봇 패널을 한 번 열어** 자신의 LS 프로세스를 띄운 뒤에 Gravity Bridge를 Start함.
|
||||
- **주의**: LS는 자동으로 시작되지 않고 사용자가 채팅 패널을 한 번 클릭/활성화해야만 Spawn 됨.
|
||||
- **二쇱쓽**: 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>빟
|
||||
## 🔴 Active/Recent Issues
|
||||
|
||||
- **利앹긽**: 臾댁뾿<EB8C81>씠 <20>옒紐삳릺<EC82B3>뿀<EFBFBD>뒗媛<EB9297>
|
||||
|
||||
- **증상**: UI Tailwind/Native 마이그레이션 및 아이콘 적용 후, Discord 브릿지로 신호가 전송되지 않음.
|
||||
- **원인**: 네이티브 UI 버튼의 `textContent` 추출 시, Codicons 등 아이콘 폰트 문자열(e.g., ` Accept`)이 앞부분에 병합(Gluing)되면서, 기존의 `^` 앵커가 포함된 정규식 매칭(`/^(?:Always\s*)?Run/i`)이 실패함.
|
||||
- **해결**: `observer-script.ts`의 스캔, Sibling 버튼 수집, Webview Trigger-click 등 `textContent`를 추출하는 모든 DOM 읽기 구간에 `txt.replace(/^[^a-zA-Z0-9]+/, '')` 전처리를 적용하여 선행 기호/아이콘을 안전하게 제거.
|
||||
- **주의**: Native UI 컴포넌트 환경에서는 텍스트 노드뿐만 아니라 아이콘/SVG 컴포넌트의 텍스트 글루잉 현상으로 인해 엄격한 시작점(`^`) 정규식이 깨질 수 있으므로, 항상 불필요한 특수문자 전처리를 선행해야 함.
|
||||
- **<2A>썝<EFBFBD>씤**: 洹쇰낯 <20>썝<EFBFBD>씤
|
||||
|
||||
- **<2A>빐寃<EBB990>**: <20>삱諛붾Ⅸ <20>빐寃<EBB990> 諛⑸쾿
|
||||
|
||||
- **二쇱쓽**: <20>옱諛<EC98B1> 諛⑹<E8AB9B><E291B9>瑜<EFBFBD> <20>쐞<EFBFBD>븳 援먰썕
|
||||
|
||||
- **증상**: UI Tailwind/Native 마이그레이션 적용 후, Discord 브릿지로 신호가 전혀 전송되지 않음
|
||||
- **원인**: Agent 패널이 탭/에디터 본문에 직접 렌더링되면서, 기존 오작동 방지 로직(`if (b.closest('.monaco-editor'))`)에 패널 전체 버튼이 포착되어 무시됨
|
||||
- **해결**: 너무 광범위한 `.monaco-editor` 방어를 해제하고, 코드 렌즈 고유 컨테이너인 `.codelens-decoration` 내부일 경우에만 무시하도록 핀포인트 수정
|
||||
- **주의**: DOM 옵저버 필터 조건 작성 시 래퍼 클래스는 UI 디자인 개편(Native, Editor Tab 등 위치 변경)에 매우 취약함. 가장 구체적인 내부 노드 클래스나 타겟 고유 속성을 통해 필터링할 것
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
- **증상**: `guitar_score` 등에서 활성화된 세션의 디스코드 승인 신호를 "계속해서" 잡지 못함. (WS 60초 타임아웃보다 더 치명적으로 신호가 아예 가지 않음)
|
||||
- **원인**: Extension이 활성 세션을 찾기 위해 호출하는 `GetAllCascadeTrajectories` LS API가 `{}`(빈 인자)로 호출될 때, 기본적으로 **10개의 세션만 반환하는 하드 리밋(Pagination Limit)**이 걸려있음. 이로 인해 작업 내역이 누적되면 수많은 최신/진행 중 세션들이 10개 목록에서 밀려나 누락됨. 익스텐션은 세션이 없다고 판단해 강제로 `IDLE` 모드에 진입하며, 승인 대기열(WAITING) 자체를 검사하지 않게 됨.
|
||||
- **해결** (v0.5.14): `v0.5.13`에서 도입했던 `{ limit: 100 }`이 LS 단의 쿼리 과부하로 인한 VS Code UI 프리징(DoS)을 유발하여 롤백하는 중 필수 정렬 파라미터(`descending: true`)까지 소실되었던 실수를 교정함. 최종적으로 `{ limit: 30, descending: true }`를 적용하여 파싱 부하 최소화 및 최신 세션 최상단(Index 0) 조회를 안전하게 구현함.
|
||||
- **주의**: LS의 기본 SQLite/DB 응답 Limit 규칙에 의존하여 전체 데이터 스캔을 수행하는 로직은 언제든 Truncation 이슈(Data Loss)를 유발할 수 있음.
|
||||
|
||||
|
||||
### [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>븿.
|
||||
|
||||
- **증상**: `guitar_score` 등 모든 작업 환경에서 약 60초마다 WebSocket 연결이 끊기고 재연결되는 현상이 반복되며(extension.log에 `Heartbeat timeout` 계속 출력), 그 사이 디스코드 승인 신호를 놓침.
|
||||
- **원인**: Extension이 `ws` 모듈 로드 실패(VS Code 환경 등)로 인해 브라우저 내장 `WebSocket` 객체로 Fallback 됨. 브라우저 WS는 서버의 네이티브 ping을 받아 pong을 자동 응답하지만 JS에 이벤트를 노출하지 않음. 이로 인해 `lastPongTime` 갱신이 불가능해져, `Date.now() - lastPongTime > 60000` 조건이 무조건 통과되어 멀쩡한 연결을 강제 종료함 (False Positive).
|
||||
- **해결** (v0.5.12):
|
||||
1. `hub.py`: `{"type": "heartbeat"}` JSON 메시지 수신 시 명시적으로 `{"type": "pong"}` JSON을 응답하도록 수정.
|
||||
2. `ws-client.ts`: 명시적 `pong` 핸들러 추가. JSON pong 지원 서버거나 Node.js ws를 사용할 때만 60초 타임아웃 검증을 거치도록 조건 보강 (`forceHeartbeatTimeoutIfNoPong`).
|
||||
- **주의**: 브라우저 표준 WebSockets(W3C)는 ping/pong 제어 프레임을 JS로 노출하지 않음. 폴리필/크로스플랫폼 WS 래퍼 사용 시 하트비트는 반드시 JSON 메세지 형태의 Application Layer Ping/Pong으로 풀어내거나, Native WS API 여부를 확실히 체크해야 함.
|
||||
- **<EFBFBD>썝<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>븿.
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>**: 梨꾨꼸 留듯븨<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>븿.
|
||||
|
||||
|
||||
### [2026-03-28] [step-probe] GetCascadeTrajectorySteps UTF-8 에러 무한 루프
|
||||
- **증상**: `guitar_score` 프로젝트에서 `[STEP-PROBE] error: ...invalid UTF-8` 에러가 5초마다 반복되며 Discord 승인 신호가 전달되지 않음.
|
||||
- **원인**: AG LS 서버에서 특정 step의 `CortexStepEphemeralMessage.content`에 바이너리 데이터(이미지 등) 포함 → proto UTF-8 직렬화 500 에러. `catch(e)` 블록에서 `stallProbed=true`를 설정하지 않아 `!ctx.stallProbed` 조건이 항상 true → 5초마다 동일 요청 무한 재시도.
|
||||
- **해결** (v0.5.11): `catch` 블록에서 UTF-8 에러 감지 시 `stepOffset=currentCount-20`으로 fallback 요청. offset도 실패 시 `stallProbed=true` 설정하여 루프 차단. `delta>0` 이벤트 발생 시 L433에서 자동 리셋.
|
||||
- **주의**: `stallProbed=true`는 영구 Lock이 아님 — `delta>0` 시 자동 리셋. UTF-8 에러는 AG 서버 측 문제(이미지/바이너리 데이터가 ephemeral message에 포함)이므로 Extension에서 graceful fallback만 처리.
|
||||
|
||||
### [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> 紐삵븿.
|
||||
|
||||
- **<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>뿰寃곕맖.
|
||||
### [2026-03-28] [approval-handler] stepIndex 미확정 시 wrong-stepIndex RPC 낭비
|
||||
- **증상**: DOM observer 경로로 `terminal_command` pending 생성 후 Discord 승인 시 `HandleCascadeUserInteraction(stepIndex=0)` → `"input not registered for step 0"` → LS reconnect → 재시도 → DOM click fallback으로 저하. (wrong-LS와 동일한 증상이나 다른 원인)
|
||||
- **원인**: `ctx.lastPendingStepIndex=-1` (step-probe가 UTF-8 에러로 WAITING 미감지)임에도 `Math.max(0, -1)=0`으로 clamp되어 존재하지 않는 step 0에 RPC 전송.
|
||||
- **해결** (v0.5.11): `effectiveStepIndex = stepIndex >= 0 ? stepIndex : (lastPendingStepIndex >= 0 ? lastPendingStepIndex : -1)`. `effectiveStepIndex < 0`이면 RPC 블록 전체 skip → DOM click 직행 (기존과 동작 동일, LS reconnect 낭비 제거).
|
||||
- **주의**: 기존 규칙 #14(`uint32`에 음수 금지)와 충돌처럼 보이나, `effectiveStepIndex=-1`일 때 RPC 자체를 **전송하지 않으므로** 위반 아님. RPC 전송 시에는 여전히 유효한 stepIndex만 사용.
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>**: <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>맖.
|
||||
|
||||
|
||||
|
||||
- **증상**: 장시간 자리비움 후 복귀 시 Discord로 승인 신호가 오지 않거나 VS Code UI가 간헐적/지속적으로 멈춤(Freeze).
|
||||
- **원인**:
|
||||
1. `ws.onerror` 발생 후 `onclose` 누락 시 재연결 콜백 호출이 이루어지지 않아 무한 대기 (장시간 마비)
|
||||
2. `ws-client` 재연결 시 누적된 200개 큐를 동기식 burst 전송하여 Hub의 속도 제한(60개/10초)에 걸려 확정 영구 삭제됨
|
||||
3. 로컬 브릿지 `http-bridge.ts`의 과거 유산인 `FALSE_POSITIVE_RE` 정규식이 AI 고유 버튼(Allow, Deny, Accept) 마저 필터링하여 Discord 전송 원천 차단
|
||||
4. `step-probe.ts` 폴링 루프 내 동기식 파일 I/O 사용으로 인한 프리즈
|
||||
- **해결** (v0.5.10): ws-client에 하드 타임아웃 및 50ms Paced-flush 적용, http-bridge의 정규식 기능 완화, step-probe 비동기 I/O 전환 체제 적용, observer-script의 필터된 신호 무한 HTTP 폴링 방어 코드 반영.
|
||||
- **주의**: Extension 내부 로직 버그였으므로 Hub(Python) 코드는 건드리지 않음. Hub 속도 제한은 정상 방어 기제이므로 클라이언트 단의 Pacing이 올바른 방향임.
|
||||
### [2026-03-24] DOM Observer /trigger-click 렌더링 순서 오작동 및 False Positive 프리징
|
||||
- **증상**: v0.5.9 패치 이후 코딩 시 Agent 화면이 끊임없이 서명 대기(Pending) 상태로 멈춤. 또는 디스코드에서 `Approve` 시 에디터 내의 엉뚱한 `Run Test`(코드 렌즈)를 클릭함.
|
||||
- **원인**: 텍스트와 정규식(`/^Run/i` 등)에만 의존하여 `querySelectorAll`을 수행할 경우, DOM 트리에 렌더링된 수많은 VS Code 네이티브 코드 렌즈 버튼을 Agent 버튼보다 먼저 찾아버리는 발생 위치(Context)의 한계점.
|
||||
- **해결** (v0.5.10):
|
||||
1. 감지(Scan): `isVSCodeMainWindow` 및 탐색 노드 `isBodyRoot` 확인을 통해, 에디터 본문 영역에서는 "Run", "Approve" 감지를 원천 제거 (오직 패널 내로 한정).
|
||||
2. 클릭(Trigger-click): `deepFindButtons()` 내에서 `findPanel()`(에이전트 패널) -> 알림 Toasts -> Document 본문 순으로 탐색 **우선순위(Priority)**를 강제 적용.
|
||||
- **주의**: 버튼 이벤트 후킹 시 텍스트 매칭에만 의존하지 말고, 반드시 DOM 탐색 우선순위와 컨텍스트 범위를 함께 필터링하여 False Positive를 차단할 것.
|
||||
## <20>윍<EFBFBD> Active/Recent Issues
|
||||
|
||||
|
||||
|
||||
### [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>쓬.
|
||||
|
||||
- **<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>븿.
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>**: `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>븿.
|
||||
|
||||
|
||||
|
||||
### [2026-03-24] DOM Observer — VS Code Native UI Blind Spot
|
||||
- **증상**: "Always Allow" 및 일반 "Allow Alt+↵" 권한 알림 버튼이 디스코드 권한 센싱에서 완전히 누락됨.
|
||||
- **원인**: VS Code 네이티브 알림 및 채팅 패널 내의 버튼은 `<button>` 태그 대신 `<a role="button">`, `<vscode-button>` 등을 사용하는데, 기존 DOM scan 로직이 `querySelectorAll('button')`으로 하드코딩되어 노드를 아예 찾지 못함. (추가로 Always Allow 정규식 누락)
|
||||
- **해결** (v0.5.9): DOM scan, 리슨 훅 등 모든 탐색 로직 셀렉터를 `button, [role="button"], vscode-button, .monaco-text-button` 으로 전면 개편. 정규식을 `/^(?:Always )?Allow/i`로 수정.
|
||||
### [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>쓬
|
||||
|
||||
- **<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>맖
|
||||
### [2026-03-24] Python Hub — 좀비 커넥션 및 UI 프리징
|
||||
- **증상**: `npm run` 명령이 `실행 정책` 관련 오류로 실패
|
||||
- **원인**: PowerShell 스크립트 실행 정책이 제한적
|
||||
- **해결**: `cmd /c npm run dev` 형식으로 cmd를 통해 실행
|
||||
- **주의**: npm 관련 명령은 항상 `cmd /c` 접두어 사용 권장
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>**: <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>꽣留곹븷 寃<>
|
||||
|
||||
|
||||
### [2026-03-08] PowerShell curl — Invoke-WebRequest 충돌
|
||||
- **증상**: `curl` 명령이 예상과 다른 응답 형식을 반환
|
||||
- **원인**: PowerShell에서 `curl`은 `Invoke-WebRequest`의 별칭
|
||||
- **해결**: **`curl.exe`**를 명시적으로 사용
|
||||
- **주의**: HTTP 관련 모든 명령에서 `curl.exe` 사용 필수
|
||||
|
||||
### [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>쓬)
|
||||
|
||||
- **<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>븿.
|
||||
## 미해결 이슈
|
||||
|
||||
- **二쇱쓽**: 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-23/24] 평생 지속되는 WebSocket 좀비 커넥션 및 False Positive 강제 연결 끊김 (v0.5.5 → 0.5.8)
|
||||
- **증상**:
|
||||
1. (v0.5.5) 절전 모드 복구 시 실연결이 끊어졌음에도 확장이 이를 인지하지 못하는 좀비(Half-open) 소켓 발생.
|
||||
2. (v0.5.6) 좀비 소켓을 잡기 위해 10초 타이머(`pongTimeoutTimer`)를 넣었으나, VS Code의 무거운 파일 검색 시 Event Loop가 블로킹되면 멀쩡한 연결인데도 허위 타임아웃(False Positive) 판정으로 연결을 강제 종료함. 이로 인해 누적된 재연결 딜레이(Exponential Backoff)가 60초까지 늘어나면서 확장이 심각하게 멈춤(Freeze).
|
||||
- **원인**: Node.js `ws` 라이브러리의 `ws.ping()`은 비동기 I/O 네트워크 큐를 타지만, `setTimeout(..., 10000)` 타임아웃은 Event Loop 블로킹 해제 직후 곧바로 만료되어 버림. 따라서 네트워크 I/O 응답(pong)보다 로컬 타이머가 먼저 터져서 정상적인 소켓을 죽임.
|
||||
- **해결** (v0.5.8 완성):
|
||||
- 위험한 `setTimeout` 방식 폐기.
|
||||
- 기존의 25초 주기 `setInterval` 하트비트 루프 내부에 `Date.now() - lastPongTime > 60000` (60초 초과 시 타임아웃) 검증 로직을 도입.
|
||||
- 만약 Event Loop가 수십 초 밀리더라도, 블로킹 해제 후 큐된 I/O 이벤트(`pong`)가 `setInterval` 타이머 콜백 이전에 먼저 처리되거나(Node.js Phase 규칙), 적어도 60초라는 버퍼 덕분에 **False Positive 가능성을 원천 차단**함과 동시에 좀비 소켓을 안정적으로 제거함.
|
||||
- **주의**: Node.js의 단일 스레드 Event Loop 환경(특히 무거운 동기 작업이 잦은 VS Code Extension)에서 네트워크 I/O를 로컬 `setTimeout`과 경주(Race)시키는 설계는 필연적으로 False Positive를 낳음. Timestamp(`Date.now()`) 기반 간격 검증(Interval check)이 훨씬 안전함.
|
||||
|
||||
|
||||
|
||||
### [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>.
|
||||
|
||||
- **<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).
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>** (v0.5.12):
|
||||
|
||||
### [2026-03-11] rejectAgentStep / !stop — AG 미등록 커맨드 + 렌더러 전용 함수 + 스테일 프리미티브
|
||||
- **증상**: `!stop` 명령이 AI를 멈추지 못함. 로그: "No active cascade" / "no session tracked yet"
|
||||
- **원인**: (1) `antigravity.agent.rejectAgentStep`은 AG 미등록 커맨드. (2) 대체한 `getActiveCascadeId()`는 **렌더러(DOM) 전용 함수** — Extension host에서 항상 `undefined` 반환. (3) **v0.4.5 수정도 실패**: `extension.ts`의 `getActiveSessionId: () => activeSessionId`가 module-level 스트링 프리미티브를 참조 — step-probe가 `ctx.activeSessionId`를 업데이트해도 extension.ts의 변수는 불변 (프리미티브 복사)
|
||||
- **해결** (2026-03-18 v0.4.6): `step-probe.ts`에서 `getActiveSessionId()` getter 함수 export → extension.ts closures에서 `getStepProbeSessionId()` 호출. 이제 step-probe의 live `ctx.activeSessionId`를 직접 읽음 (`ab0c116`)
|
||||
- **주의**: JS에서 **string/number는 프리미티브라 참조 전달 불가** — 객체 속성을 공유하려면 getter 함수나 객체 래퍼 사용 필수
|
||||
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`).
|
||||
|
||||
- **二쇱쓽**: 釉뚮씪<EFBFBD>슦<EFBFBD><EFBFBD><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-19] browser_subagent Allow — 잘못된 RPC payload
|
||||
- **증상**: 서브 에이전트 "execute JavaScript on localhost" Allow 버튼이 자동 승인되지 않음
|
||||
- **원인**: `step-probe.ts`에서 `browser_subagent` toolName이 step_type 분류 없이 raw toolName으로 전달 → `approval-handler.ts`에서 `runExtensionCode` 매핑에 포함되지 않아 default `runCommand` RPC payload 사용 → AG가 잘못된 interaction type으로 무시
|
||||
- **해결** (v0.5.1): `approval-handler.ts` L384에 `browser_subagent` 추가, `step-probe.ts` L481/L549에 `browser_subagent`/`open_browser_url` step_type 분류 추가 (`549af6d`)
|
||||
- **주의**: 새로운 AG 도구 추가 시 반드시 (1) step-probe step_type 매핑 (2) approval-handler RPC payload 매핑 양쪽 모두 업데이트
|
||||
|
||||
### [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>쓬.
|
||||
|
||||
- **<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>룄.
|
||||
### [2026-03-21] Idle→Resume 신호 소실 — 3중 버그
|
||||
- **증상**: AG 장시간 idle 후 작업 재개 시 Discord 승인 신호가 전달되지 않음
|
||||
- **원인**: (1) `ws-client.ts` `auth_fail` 시 `shouldReconnect=false` — JWT 24h 만료 시 WS 영구 종료. (2) `hub.py` `_disconnect`에서 유일 연결 시 `pending_owners` 삭제 — 재연결 후 Discord 버튼 무효. (3) `step-probe.ts` `stallProbed=true` + `lastPendingStepIndex=N`이 WS 재연결 시 리셋 안 됨 — WAITING step 재전송 영구 차단
|
||||
- **해결** (v0.5.2): (1) `auth_fail` → `registrationCode` 재시도. (2) `pending_owners` orphan 마커로 보존+재할당. (3) `resetPendingStateForReconnect()` + `onConnected`에서 호출
|
||||
- **주의**: WS `onConnected`에서 반드시 step-probe 상태 리셋 필수. `stallProbed`/`lastPendingStepIndex`는 TTL 없는 영구 값
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>** (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> 泥섎━.
|
||||
|
||||
|
||||
|
||||
### [2026-03-28] [approval-handler] stepIndex 誘명솗<EBAA85>젙 <20>떆 wrong-stepIndex RPC <20>궘鍮<EAB698>
|
||||
|
||||
> v0.4.5 수정 사항(Hub pending_owners, diff_review WS, auto_approve 이중쓰기, WS dual-write, ApprovalView fallback)은
|
||||
> 코드 수정 완료됨. E2E 통합 검증은 Vikunja #410에서 추적 중.
|
||||
- **利앹긽**: 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>넚.
|
||||
### [2026-03-21] stepIndex=-1 — AG proto uint32 에러
|
||||
- **증상**: DOM observer가 Allow 버튼 감지 → Discord 승인 → RPC `HandleCascadeUserInteraction` 400 에러
|
||||
- **원인**: DOM observer 경로는 step index를 모름 → `stepIndex=-1` 전달 → AG proto `uint32` 필드에 음수 불가
|
||||
- **해결**: `Math.max(0, ...)` 로 clamp. `permission` type → `runExtensionCode.confirm` 매핑 추가 (v0.5.4)
|
||||
- **주의**: DOM observer 경로의 step_type은 항상 `stepIndex=-1`일 수 있으므로 proto 전달 전 양수 보장 필수
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>** (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>슜.
|
||||
|
||||
|
||||
### [2026-03-21] reviewAbsoluteUris — latestNotifyUserStep 필드명 불일치
|
||||
- **증상**: `notify_user`의 PathsToReview 파일 릴레이가 한 번도 작동하지 않음
|
||||
- **원인**: AG 실제 필드명 `reviewAbsoluteUris` vs 코드 `pathsToReview`/`paths_to_review`/`filePaths`
|
||||
- **해결**: `reviewAbsoluteUris` 를 첫 번째 후보로 추가 (v0.5.3)
|
||||
- **주의**: AG RPC 필드명은 extension.log `[NOTIFY-STEP] keys=` 로 확인 가능. 추측 금지
|
||||
|
||||
### [2026-03-25] [Architecture] Discord Signal Drop & Extension Freezes
|
||||
|
||||
- **利앹긽**: <EFBFBD>옣<EFBFBD>떆媛<EFBFBD> <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>씤**:
|
||||
### [2026-03-21] 세션 전환 — 첫 WAITING 감지 20-25s 지연
|
||||
- **증상**: 새 대화 시작 후 첫 run_command 승인이 Discord에 안 오고 AG에서 직접 승인해야 함
|
||||
- **원인**: `lastModTime=''` 리셋 → `modTimeChanged=true` → THINKING 분기 반복 → probe 15-25s 지연
|
||||
- **해결**: `lastModTime=currentModTime` + `return` 제거 + 즉시 probe 강제 + 회귀 가드 추가 (v0.5.3)
|
||||
- **주의**: 세션 전환 시 `wasRunning`/`pendingModifiedFiles` 리셋 필수 (이전 세션 잔여물로 false diff_review 방지)
|
||||
|
||||
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>맖
|
||||
|
||||
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>봽由ъ쫰
|
||||
## 핵심 작업 규칙 (과거 이슈에서 반복된 패턴)
|
||||
|
||||
- **<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>엫.
|
||||
| # | 규칙 | 관련 이슈 (archive 참조) |
|
||||
|
||||
### [2026-03-24] DOM Observer /trigger-click <20>젋<EFBFBD>뜑留<EB9C91> <20>닚<EFBFBD>꽌 <20>삤<EFBFBD>옉<EFBFBD>룞 諛<> False Positive <20>봽由ъ쭠
|
||||
| 1 | **Hub WS와 file bridge는 상호 배타적** — `if hub: ws + return` / `else: file` | WS dual-write, _auto_approve 이중 쓰기 |
|
||||
| 2 | **WS 경로 추가 시 file-bridge의 모든 분기를 포팅** | diff_review WS regression |
|
||||
| 3 | **AG RPC `{}` 응답은 실패로 간주** — 메서드명 틀려도 에러 없이 `{}` 반환 | AcknowledgeCascadeCodeEdit |
|
||||
| 4 | **ResolveOutstandingSteps는 CANCEL 동작** — 승인에 절대 사용 금지 | Step probe reject |
|
||||
| 5 | **Extension 코드 수정 후 반드시 VSIX 빌드 + AG 풀 재시작** | Extension 버전 미배포 |
|
||||
| 6 | **HTML 패치 변경 시 V8 CachedData 삭제 필수** | V8 CachedData, CSP |
|
||||
| 7 | **`bridge/pending/` 조작 시 반드시 `project_name` + `conversation_id` 필터** | 크로스 프로젝트 DEDUP MERGE |
|
||||
| 8 | **`processResponseFile` 상태 리셋은 `sawRunningAfterPending=true`만** | processResponseFile 무한 루프 |
|
||||
| 9 | **fs.watch Windows 불안정 — 반드시 polling fallback 병행** | fs.watch silent fail |
|
||||
| 10 | **diff_review는 VS Code 커맨드만 유효** — RPC 3개 전략 모두 실패 확정 | diff_review RPC dead-end |
|
||||
| 11 | **HttpBridgeContext에 프리미티브 by-value 복사 금지** — 별도 객체 생성 시 getter 사용 | HttpBridgeContext stale primitive |
|
||||
| 12 | **새 AG 도구 추가 시 step-probe step_type 매핑 + approval-handler RPC payload 매핑 양쪽 필수** | browser_subagent Allow |
|
||||
| 13 | **WS `onConnected`에서 step-probe 상태 리셋 필수** — `stallProbed`/`lastPendingStepIndex`는 TTL 없는 영구 값 | Idle→Resume 신호 소실 |
|
||||
| 14 | **AG proto `uint32` 필드에 음수 전달 금지** — `stepIndex` 등은 `Math.max(0, ...)` 필수 | stepIndex=-1 RPC 400 |
|
||||
| 15 | **RPC "input not registered" = wrong-LS 연결** — `fixLSConnection()` 자동 재시도 필수, `lines.length<=1` 조기종료 금지 | Deriva wrong-LS (v0.5.5) |
|
||||
| 16 | **익스텐션(Bridge)은 자의적 비즈니스 판단 절대 금지** — `SafeToAutoRun` 등의 조건 브랜치 분기는 모두 봇으로 위임 (Agnostic Bridge) | SafeToAutoRun Deadlock (v0.5.15) |
|
||||
| 17 | **package.json 빌드 스크립트 강제** — `vscode:prepublish` 추가로 낡은 소스 배포 원천 차단 | VSIX v0.5.15 빌드 누락 |
|
||||
| 18 | **동기식 `cp.execSync` 사용 금지** — Windows 환경에서 메인 이벤트루프 프리징 및 WS heartbeat 단절 유발 | detectProjectName 프리징 |
|
||||
|
||||
- **利앹긽**: 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>빐寃<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>젙).
|
||||
|
||||
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>븷 寃<>.
|
||||
|
||||
|
||||
|
||||
### [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>맖.
|
||||
|
||||
- **<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>씫)
|
||||
- **증상**: 디스코드 봇은 '자동 승인됨'을 띄우지만 실제 코드 본문이 표시되지 않고, 채널에 진짜 채팅 메시지나 알림이 스팸 큐 뒤에 밀려 전송되지 않음.
|
||||
- **원인**: 1) observer-script.ts에서 버튼 텍스트 매칭 시 Run 단어의 경계(\b) 처리를 하지 않아 VS Code 하단의 'Running 1 command'를 가로채어 PENDING 스팸 무한 생성. 2) bot.py에서 자동 승인 Embed 생성 시 req.description을 그리지 않고 버튼 텍스트(req.command)만 표시. 3) step-probe.ts에서 세션 교체 시 최근 알림 인덱스 초기화를 잘못하여 세션의 첫 메시지를 무조건 드롭.
|
||||
- **해결**: DOM 감지 정규식에 \b 강제 부여 (/Run\b/), bot.py의 Auto-Approve 쪽 Embed 본문에 req.description 렌더링 추가, step-probe.ts에서 session init 시 index를 -1로 리셋.
|
||||
- **주의**: Native UI 텍스트 감지 시 단어 경계(\b)까지 검증해야 False Positive를 막을 수 있으며, Auto-Approve는 반드시 본문을 노출해야 함.
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>** (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>젙.
|
||||
|
||||
|
||||
|
||||
- **증상**: 디스코드 채팅방에 Agent의 텍스트 응답(AI 응답)이 아예 누락되어 전송되지 않음.
|
||||
- **원인**: GetCascadeTrajectorySteps가 반환하는 plannerResponse가 프로토콜 방식에 따라 최상단(s.plannerResponse)이 아닌 s.step.plannerResponse에 중첩되어 들어올 수 있음. 기존 파서는 하드코딩된 필드 및 플랫 구조만 조회하여 응답을 버림.
|
||||
- **주의**: AG RPC 필드명 구조 추측 금지. 필요 시 샌드박스로 두 가지 구조(Flat, Nested) 모두 모킹하여 직접 파싱 확인.
|
||||
### [2026-03-24] Python Hub <20><><EFBFBD> 醫<>鍮<EFBFBD> 而ㅻ꽖<E385BB>뀡 諛<> UI <20>봽由ъ쭠
|
||||
|
||||
- **利앹긽**: `npm run` 紐낅졊<EB8285>씠 `<EFBFBD>떎<EFBFBD>뻾 <20>젙梨<ECA099>` 愿<><E684BF>젴 <20>삤瑜섎줈 <20>떎<EFBFBD>뙣
|
||||
|
||||
- **<2A>썝<EFBFBD>씤**: PowerShell <20>뒪<EFBFBD>겕由쏀듃 <20>떎<EFBFBD>뻾 <20>젙梨낆씠 <20>젣<EFBFBD>븳<EFBFBD>쟻
|
||||
- **증상**: 디스코드로 내용이 아예 전달되지 않음. `[RT-CAPTURE]`, `[RESPONSE-CAPTURE]` 로그 모두 전혀 남지 않음.
|
||||
- **원인**: AI 응답이나 코딩 작업이 5초(폴링 주기) 미만으로 매우 빠르게 끝나면, 확장이 `IDLE -> IDLE` 상태만 관찰하며 `wasRunning` 플래그가 `false`로 유지됨. 기존 `[RESPONSE-CAPTURE]` 조건식(`wasRunning && !isRunning && currentCount > ...`)이 `wasRunning=false`로 인해 블록되어 캡처 자체를 완전히 건너뛰게 됨.
|
||||
- **해결**: `wasRunning` 검증을 삭제하고 `!isRunning && currentCount > lastResponseCaptureStep` 조건으로 완화하여 누락된 step이 있을 때 무조건 캡처하도록 변경. 추가로 오래된 `[RESPONSE-CAPTURE]` 내 하드코딩 파서를 `extractPlannerText`로 일원화 적용.
|
||||
- **주의**: 폴링 방식에서는 상태(RUNNING->IDLE) 전이를 확신할 수 없으므로, Step Count(인덱스 전진)라는 100% 신뢰 가능한 마커를 통해 새 응답 여부를 감지해야 함.
|
||||
### [2026-04-10] [Bot] chat_snapshot_scanner 무한 Abort 및 파일 적체 (Exception 누락)
|
||||
- **증상**: 봇이 디스코드로 AI 답변(채팅 스냅샷)을 전혀 전송하지 못하고 렉이 걸림. ridge/chat_snapshots/에 처리되지 않은 JSON 파일이 수십 개 적체됨.
|
||||
- **원인**: ot.py의 chat_snapshot_scanner에서 파일을 순회 파싱할 때 내부의 .unlink() 과정에서 발생하는 예외나 discord.Embed 생성 예외 등을 루프 안에서 잡아주지 못함. 첫 에러 파일(poison pill)을 만나는 순간 루프 전체가 폭파되어 뒤쪽의 정상 파일들도 영원히 처리되지 않고 다음 폴 스케줄에서 다시 첫 파일에 막힘.
|
||||
- **해결**: 루프 내부에 except Exception을 추가하여 전역 예외를 잡아 방어. 실패한 파일은 glob에서 반복 시도되지 않게 .json.failed로 우회(rename)시켜 큐를 비워줌.
|
||||
- **주의**: 폴링/스캐너 or 루프 내부에서는 개별 아이템 파싱 단계에서 발생 가능한 모든 예외 상태에 대한 Defensive Catch 및 Continue(우회) 로직이 필수임.
|
||||
|
||||
- **<EFBFBD>빐寃<EFBFBD>**: `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>슜 沅뚯옣
|
||||
|
||||
|
||||
|
||||
### [2026-03-08] PowerShell curl <20><><EFBFBD> Invoke-WebRequest 異⑸룎
|
||||
|
||||
- **利앹긽**: `curl` 紐낅졊<EB8285>씠 <20>삁<EFBFBD>긽怨<EAB8BD> <20>떎瑜<EB968E> <20>쓳<EFBFBD>떟 <20>삎<EFBFBD>떇<EFBFBD>쓣 諛섑솚
|
||||
- **<2A><><EFBFBD><EFBFBD>**: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ۼ<EFBFBD><DBBC>ߴ<EFBFBD> { limit: 30 } <20>Ķ<EFBFBD><C4B6><EFBFBD><EFBFBD>Ͱ<EFBFBD> LS <20>鿣<EFBFBD>忡<EFBFBD><E5BFA1> <20><><EFBFBD>õǾ<C3B5> <20>ֽ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 10<31><30> <20><><EFBFBD>ѿ<EFBFBD> <20>ɷ<EFBFBD> <20>߷<EFBFBD><DFB7><EFBFBD><EFBFBD><EFBFBD>. (Discord<72><64> <20><EFBFBD><DEBD><EFBFBD> <20><> <20><> <20><><EFBFBD>ڵ<EFBFBD> <20><> <20>Ѿ<EFBFBD><D1BE><EFBFBD>).
|
||||
- **<2A><EFBFBD><EFBFBD><EFBFBD>**: GetAllCascadeTrajectories<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> pagination <20>ɼ<EFBFBD><C9BC><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰų<CFB0> <20><><EFBFBD><EFBFBD> 10 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ɸ<EFBFBD>.
|
||||
- **<2A>ذ<EFBFBD>**: step-probe.ts<74><73><EFBFBD><EFBFBD> <20>⺻ GetAllCascadeTrajectories<65><73> <20><><EFBFBD>Ҿ<EFBFBD> <20><><EFBFBD><EFBFBD> Ʈ<><C6AE><EFBFBD><EFBFBD><EFBFBD>丮<EFBFBD><E4B8AE> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD> GetDiagnostics API<50><49> <20><><EFBFBD><EFBFBD> ȣ<><C8A3><EFBFBD>ϰ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> <20>ֽ<EFBFBD> Session ID<49><44> <20><>ġ<EFBFBD><C4A1> <20>ʰ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰ<EFBFBD> <20><>.
|
||||
- **<2A><EFBFBD><EFBFBD><EFBFBD>**: LS Backend<6E><64><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> RPC<50><43> <20>Ѱ<EFBFBD><D1B0><EFBFBD> Argument <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ȸ<><C8B8><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD>, <20><><EFBFBD><EFBFBD> GetDiagnostics <20><> <20>鵵<EFBFBD><E9B5B5> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Ȱ<><C8B0><EFBFBD><EFBFBD> <20><>.
|
||||
|
||||
- **<2A>썝<EFBFBD>씤**: PowerShell<EFBFBD>뿉<EFBFBD>꽌 `curl`<60><><EFBFBD> `Invoke-WebRequest`<60>쓽 蹂꾩묶
|
||||
|
||||
- **<2A>빐寃<EFBFBD>**: **`curl.exe`**瑜<> 紐낆떆<EB8286>쟻<EFBFBD>쑝濡<EC919D> <20>궗<EFBFBD>슜
|
||||
|
||||
### [2026-04-10] [Probe Logging] — AI응답 텍스트 & WAITING 스텝 동시 누락 버그
|
||||
- **증상**: 굉장히 빠른 AI 응답(또는 즉각적인 툴 호출) 시 `step-probe.ts`가 메시지와 승인 다이얼로그를 모두 Discord로 릴레이하지 못함.
|
||||
- **원인**: 실시간 텍스트 캡처(`delta > 0`) 조건에 `isRunning &&`이 걸려있어, 상태가 `WAITING`이나 `IDLE`로 즉시 넘어가면 텍스트를 캡처하는 루틴이 전부 스킵됨. 또한 이 순간 `isStall` 조건도 타지 않아 `WAITING` 디텍션도 증발함.
|
||||
- **해결**: 실시간 캡처 로직에서 `isRunning &&` 조건을 제거하고, `delta > 0`일 때 추가된 최신 스텝을 스캔하면서 `PLANNER_RESPONSE`와 `WAITING` 스텝을 모두 처리하도록 수정함.
|
||||
- **주의**: LS Backend 10개 Session 제한 버그가 있어, 다른 창에서 수동 채팅(`1fbca84c`)이 IDLE로 남아있으면 자동화 에이전트의 워크스페이스 세션과 헷갈릴 수 있으나, 이 버그는 polling 타이밍 문제였음.
|
||||
- **二쇱쓽**: HTTP 愿<><E684BF>젴 紐⑤뱺 紐낅졊<EB8285>뿉<EFBFBD>꽌 `curl.exe` <20>궗<EFBFBD>슜 <20>븘<EFBFBD>닔
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
- **증상**: 새로운 대화(Session) 시작 시 첫 AI 응답 텍스트가 디스코드에 전혀 전송되지 않는 현상.
|
||||
- **원인**: 백엔드의 `GetAllCascadeTrajectories`가 10개 세션만 반환하여 새 세션이 누락됨. 이를 보완하기 위해 `brain/` 디렉토리를 스캔하는 Fallback 로직이 동작했으나, 신규 세션의 첫 단계에서 `GetCascadeTrajectorySteps`(stepOffset: 0) 호출 시 내부 응답(UTF-8 파싱 등) 에러로 인해 Exception이 발생, `trajectorySummaries`에 세션이 아예 등록되지 않음. 세션이 추적되지 않으니 `delta > 0` 기반의 응답 캡처가 발생하지 않음.
|
||||
- **해결**: `step-probe.ts`의 Fallback 2 `catch` 블록에서 에러가 발생하더라도 강제로 `stepCount: 1`로 세션을 등록하도록 패치하여 세션 인식 유실 방지.
|
||||
- **주의**: API 호출 실패를 조용히 `catch`로 넘기면 전체 파이프라인(여기서는 상태 폴링)이 해당 데이터를 영원히 무시하게 되는 치명적 버그가 발생함. 장애 허용 설계 시 기본값 복원(Fallback State) 설정 필수.
|
||||
|
||||
## 誘명빐寃<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)
|
||||
|
||||
- **증상**: 디스코드에서 승인(Approve)을 누르면, 에이전트 확장 프로그램이 알맞은 버튼(예: `Always run`)을 누르지 못하거나, 엉뚱한 버튼(예: 상단의 `Running1 command`)을 눌러버려 실제 승인 처리가 누락되는 현상.
|
||||
- **원인**: 1) UI 버튼 텍스트에 `keyboard_arrow_up` 등 머티리얼 아이콘 텍스트가 접착(`Always runkeyboard_arrow_up`)되어 정규식이 실패할 것을 우려해 단어 경계(`\b`)를 제거한 패치가 원인. 단어 경계가 사라지면서 `/Run/i` 패턴이 `Running1 command` 같은 다른 상태 텍스트 버튼에 오탐(False Positive)됨. 2) DOM 순서상 상태 텍스트 버튼이 앞서 있으므로 오탐된 버튼이 우선 클릭됨.
|
||||
- **해결**: `trigger-click` 로직 실행 전 버튼의 `textContent`에서 `keyboard_arrow_up` 등 알려진 꼬리 아이콘 문자열을 명시적으로 제거(strip)하고, 모든 트리거 정규식에 다시 단어 경계(`\b`)를 강제 삽입하여 오탐을 원천 차단함.
|
||||
- **주의**: UI 요소를 DOM에서 긁어올 때는 텍스트에 숨겨진 아이콘/웹폰트 리거쳐(ligatures)가 없는지 검토해야 함. 패턴 매칭 시 꼬리표를 먼저 제거하고 명확한 경계를 부여할 것.
|
||||
- **利앹긽**:
|
||||
|
||||
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).
|
||||
|
||||
- **증상**: 신규 작업 시 '신호안들어와' (Discord로 릴레이 안 됨). 로그에 500 error trajectory not found 무한 반복.\n- **원인**: Antigravity가 작업하면서 brain/에 36글자 폴더를 생성하는데, Cascade가 아니므로 GetCascadeTrajectorySteps에서 500 에러를 냅니다. 하지만 이전 신규 세션 유실 방지 패치가 이 Ghost 세션을 RUNNING으로 강제 등록하면서, 활성 세션(activeSessionId)을 탈취하고 무한 에러 루프에 빠지게 만들었습니다.\n- **해결**: step-probe.ts에서 폴백 등록 시 error message에 'trajectory not found'가 포함되면 Ghost 세션으로 간주해 강제 등록(continue)을 건너뛰게 하고, Stall Probe 에러 catch에서도 UTF-8 에러가 아니면 stallProbed=true를 주어 재시도 무한 루프를 완전히 끊어냈습니다.\n- **주의**: uuid 길이(36자)만으로 디렉토리를 식별할 때 Antigravity와 Google Agent가 모호해질 수 있으므로, 반드시 Backend 응답의 확실한 에러(trajectory not found) 메시지로 예외 판별을 해야 합니다.\n
|
||||
- **<EFBFBD>썝<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>냼耳볦쓣 二쎌엫.
|
||||
@@ -2,4 +2,5 @@
|
||||
|
||||
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
|
||||
|-------|-------|----------|-----------|-----------|
|
||||
| 001 | 13:04 | Pure 웹소켓 게이트웨이 완전 전환 및 레거시 파일 브릿지 통신코드 삭제 | `TBD` | ✅ |
|
||||
| 001 | 13:04 | Pure 웹소켓 게이트웨이 완전 전환 및 레거시 파일 브릿지 통신코드 삭제 | `072f83b` | ✅ |
|
||||
| 002 | 17:25 | Antigravity Observer 컨텍스트 추출 범위 제한 및 노이즈(UI/TypeScript 코드) 필터링, Discord 임베드 개선 | `70dc301` | ✅ |
|
||||
|
||||
5
docs/devlog/2026-04-12.md
Normal file
5
docs/devlog/2026-04-12.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 2026-04-12
|
||||
|
||||
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완/미 |
|
||||
|-------|-------|----------|-----------|-----------|
|
||||
| 001 | 06:12 | AG Native DOM 파싱 v7 전면 재설계 — data-testid/data-step-index 기반 step-aware 파서, UI 노이즈 차단 | `a4d7286` | 🔧 |
|
||||
22
docs/devlog/entries/20260412-001.md
Normal file
22
docs/devlog/entries/20260412-001.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# AG Native DOM 파싱 v7 전면 재설계
|
||||
|
||||
- **시간**: 2026-04-12 05:49~06:12
|
||||
- **Commit**: `a4d7286`
|
||||
|
||||
## 배경
|
||||
- AG Native 세션에서 Discord 릴레이가 전혀 동작하지 않음
|
||||
- SDK `GetCascadeTrajectorySteps`가 `trajectory not found` 반환 — AG Native는 Cascade API에 등록 안 됨
|
||||
- DOM observer가 UI 노이즈(content_copy, keyboard_arrow_up, Always run, Cancel)를 AI 응답으로 오인
|
||||
|
||||
## 결정 사항
|
||||
- SDK 경로 대신 **DOM이 유일한 데이터 소스**로 확정
|
||||
- `jetskiAgent/main.js` (11MB) 번들 분석으로 AG Native DOM 구조 역공학:
|
||||
- `data-testid="conversation-view"` — 대화 최상위 컨테이너
|
||||
- `data-step-index` — 각 step 식별 속성
|
||||
- `text-ide-message-block-bot-color` — 봇 메시지 클래스 (확인됨)
|
||||
- observer-script v6→v7 전면 재설계: step-aware 파싱
|
||||
|
||||
## 미완료
|
||||
- AG 재시작 후 실제 동작 검증 필요 (DOM 덤프 → 셀렉터 미세조정)
|
||||
- deep-inspect 정상 동작 확인
|
||||
- Discord에 실제 AI 응답 전달 확인
|
||||
@@ -5,8 +5,13 @@
|
||||
* Handles:
|
||||
* - Response file watching (file-based bridge fallback)
|
||||
* - Response processing (diff_review, DOM observer, step_probe paths)
|
||||
* - Multi-strategy approval execution (RPC, VS Code commands, DOM click)
|
||||
* - Multi-strategy approval execution (VS Code commands, RPC, DOM click)
|
||||
* - Diff review Accept/Reject via VS Code commands
|
||||
*
|
||||
* STRATEGY ORDER (most reliable first):
|
||||
* 0. antigravity.acceptAgentStep / rejectAgentStep — AG's own commands, always works
|
||||
* 1. HandleCascadeUserInteraction RPC — cross-platform, needs stepIndex
|
||||
* 2. DOM click trigger via HTTP bridge — fallback
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
@@ -256,7 +261,7 @@ async function processResponseFile(filePath: string) {
|
||||
} catch { }
|
||||
}
|
||||
|
||||
// ═══ MULTI-STRATEGY APPROVAL (v2.1) ═══
|
||||
// ═══ MULTI-STRATEGY APPROVAL (v3.0) ═══
|
||||
const approved = resp.approved;
|
||||
|
||||
// ── diff_review: Accept all / Reject all ──
|
||||
@@ -268,16 +273,10 @@ async function processResponseFile(filePath: string) {
|
||||
button_index: resp.button_index,
|
||||
step_type: pendingStepType,
|
||||
});
|
||||
} else if (isDomObserver) {
|
||||
// DOM observer path: ALSO try RPC strategies (renderer click is unreliable)
|
||||
const targetSession = sessionId || ctx.activeSessionId;
|
||||
ctx.logToFile(`[RESPONSE] dom_observer → tryApprovalStrategies(${approved}, ${targetSession.substring(0, 8)}, type=${pendingStepType}, step=${pendingStepIndex})`);
|
||||
const strategyResult = await tryApprovalStrategies(approved, targetSession, pendingStepType, pendingStepIndex);
|
||||
ctx.logToFile(`[RESPONSE] dom strategy result: ${strategyResult}`);
|
||||
} else {
|
||||
// Step probe path: run ALL approval strategies
|
||||
// ALL paths (dom_observer + step_probe) use same strategy pipeline
|
||||
const targetSession = sessionId || ctx.activeSessionId;
|
||||
ctx.logToFile(`[RESPONSE] step_probe → tryApprovalStrategies(${approved}, ${targetSession.substring(0, 8)}, type=${pendingStepType}, step=${pendingStepIndex})`);
|
||||
ctx.logToFile(`[RESPONSE] → tryApprovalStrategies(${approved}, ${targetSession.substring(0, 8)}, type=${pendingStepType}, step=${pendingStepIndex})`);
|
||||
const strategyResult = await tryApprovalStrategies(approved, targetSession, pendingStepType, pendingStepIndex);
|
||||
ctx.logToFile(`[RESPONSE] strategy result: ${strategyResult}`);
|
||||
}
|
||||
@@ -307,9 +306,9 @@ async function processResponseFile(filePath: string) {
|
||||
* Returns a string describing which method succeeded (or all failed).
|
||||
*
|
||||
* Strategy order (most reliable first):
|
||||
* 1. HandleCascadeUserInteraction RPC (cross-platform, no focus)
|
||||
* 2. VS Code accept/reject commands (focus-dependent)
|
||||
* 3. Log failure for manual intervention
|
||||
* 0. antigravity.acceptAgentStep / rejectAgentStep (AG VS Code commands — always works)
|
||||
* 1. HandleCascadeUserInteraction RPC (cross-platform, needs stepIndex)
|
||||
* 2. Renderer DOM Click via HTTP Bridge (fallback)
|
||||
*/
|
||||
export async function tryApprovalStrategies(approved: boolean, sessionId: string, stepType: string = '', stepIndex: number = -1): Promise<string> {
|
||||
const action = approved ? 'APPROVE' : 'REJECT';
|
||||
@@ -317,90 +316,153 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string
|
||||
: (ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : -1);
|
||||
ctx.logToFile(`[APPROVAL] Starting ${action} strategies for session ${sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${effectiveStepIndex}`);
|
||||
|
||||
// ── Dynamic Command Discovery (log what's available during WAITING state) ──
|
||||
let approvalCmdList: string[] = [];
|
||||
try {
|
||||
const allCmds = await vscode.commands.getCommands(true);
|
||||
const agCmds = allCmds.filter((c: string) => c.startsWith('antigravity.'));
|
||||
approvalCmdList = agCmds.filter((c: string) => {
|
||||
const lower = c.toLowerCase();
|
||||
return lower.includes('accept') || lower.includes('reject') || lower.includes('approve')
|
||||
|| lower.includes('terminal') || lower.includes('run') || lower.includes('step')
|
||||
|| lower.includes('cascade') || lower.includes('action');
|
||||
});
|
||||
ctx.logToFile(`[APPROVAL-CMD-CHECK] ${agCmds.length} total, ${approvalCmdList.length} approval-related:`);
|
||||
for (const c of approvalCmdList) {
|
||||
ctx.logToFile(`[APPROVAL-CMD-CHECK] → ${c}`);
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 0: SDK-verified AG commands (step_type-aware dispatch)
|
||||
//
|
||||
// From SDK index.js (verified command mapping):
|
||||
// antigravity.agent.acceptAgentStep — code edits, file writes
|
||||
// antigravity.agent.rejectAgentStep — reject code edits
|
||||
// antigravity.command.accept — non-terminal commands (Run, Allow, etc.)
|
||||
// antigravity.command.reject — reject non-terminal commands
|
||||
// antigravity.terminalCommand.accept — terminal commands
|
||||
// antigravity.terminalCommand.reject — reject terminal commands
|
||||
// antigravity.terminalCommand.run — run terminal commands
|
||||
//
|
||||
// These operate on the currently focused/active step — no stepIndex needed!
|
||||
// ══════════════════════════════════════════════════════════
|
||||
{
|
||||
const typeLower = stepType.toLowerCase().replace('cortex_step_type_', '');
|
||||
|
||||
// Determine which SDK command pair to use based on step_type
|
||||
let acceptCmd: string;
|
||||
let rejectCmd: string;
|
||||
|
||||
if (typeLower.includes('code_edit') || typeLower.includes('write_to_file')
|
||||
|| typeLower.includes('propose_code') || typeLower.includes('write_cascade_edit')
|
||||
|| typeLower === 'diff_review') {
|
||||
// Code edits → agent step commands
|
||||
acceptCmd = 'antigravity.agent.acceptAgentStep';
|
||||
rejectCmd = 'antigravity.agent.rejectAgentStep';
|
||||
} else if (typeLower.includes('run_command') || typeLower.includes('shell_exec')
|
||||
|| typeLower.includes('send_command_input')) {
|
||||
// Terminal commands → terminal command pair
|
||||
acceptCmd = 'antigravity.terminalCommand.accept';
|
||||
rejectCmd = 'antigravity.terminalCommand.reject';
|
||||
} else if (typeLower === 'command' || typeLower.includes('permission')
|
||||
|| typeLower.includes('browser') || typeLower.includes('mcp')
|
||||
|| typeLower.includes('extension_code') || typeLower.includes('subagent')
|
||||
|| typeLower.includes('open_browser') || typeLower.includes('read_url')
|
||||
|| typeLower.includes('invoke_subagent')) {
|
||||
// Non-terminal commands (Run, Allow, etc.) → command pair
|
||||
acceptCmd = 'antigravity.command.accept';
|
||||
rejectCmd = 'antigravity.command.reject';
|
||||
} else {
|
||||
// Unknown type — try all three in order
|
||||
acceptCmd = 'antigravity.command.accept';
|
||||
rejectCmd = 'antigravity.command.reject';
|
||||
}
|
||||
|
||||
const primaryCmd = approved ? acceptCmd : rejectCmd;
|
||||
ctx.logToFile(`[APPROVAL-0] stepType="${stepType}" → ${primaryCmd}`);
|
||||
|
||||
try {
|
||||
await vscode.commands.executeCommand(primaryCmd);
|
||||
ctx.logToFile(`[APPROVAL-0] ✅ ${primaryCmd} SUCCESS`);
|
||||
return `SDK:${primaryCmd}`;
|
||||
} catch (e: any) {
|
||||
ctx.logToFile(`[APPROVAL-CMD-CHECK] error: ${e.message}`);
|
||||
ctx.logToFile(`[APPROVAL-0] ❌ ${primaryCmd} failed: ${e.message?.substring(0, 200)}`);
|
||||
}
|
||||
|
||||
// Fallback: if the primary type-specific command failed, try the other pairs
|
||||
const fallbackPairs = [
|
||||
approved ? 'antigravity.command.accept' : 'antigravity.command.reject',
|
||||
approved ? 'antigravity.agent.acceptAgentStep' : 'antigravity.agent.rejectAgentStep',
|
||||
approved ? 'antigravity.terminalCommand.accept' : 'antigravity.terminalCommand.reject',
|
||||
].filter(cmd => cmd !== primaryCmd); // skip already-tried
|
||||
|
||||
for (const fallbackCmd of fallbackPairs) {
|
||||
try {
|
||||
ctx.logToFile(`[APPROVAL-0-FB] Trying ${fallbackCmd}...`);
|
||||
await vscode.commands.executeCommand(fallbackCmd);
|
||||
ctx.logToFile(`[APPROVAL-0-FB] ✅ ${fallbackCmd} SUCCESS`);
|
||||
return `SDK-FB:${fallbackCmd}`;
|
||||
} catch (e: any) {
|
||||
ctx.logToFile(`[APPROVAL-0-FB] ❌ ${fallbackCmd}: ${e.message?.substring(0, 100)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 0-PROTO: Correct proto-based RPC (decoded from AG source)
|
||||
// STRATEGY 1: HandleCascadeUserInteraction RPC
|
||||
// Now supports BOTH approve AND reject.
|
||||
// Requires valid stepIndex for most step types.
|
||||
// ══════════════════════════════════════════════════════════
|
||||
if (ctx.sdk && approved && effectiveStepIndex >= 0) {
|
||||
// Build interaction sub-message based on step_type
|
||||
if (ctx.sdk && effectiveStepIndex >= 0) {
|
||||
const typeLower = stepType.toLowerCase().replace('cortex_step_type_', '');
|
||||
let interactionPayload: Record<string, any> = {};
|
||||
|
||||
// Code edit steps — use dedicated RPC
|
||||
if (typeLower.includes('code_edit') || typeLower.includes('write_to_file') || typeLower.includes('propose_code') || typeLower.includes('write_cascade_edit')) {
|
||||
// CODE EDIT: Uses acknowledgeCodeActionStep RPC (correct AG LS method)
|
||||
try {
|
||||
ctx.logToFile(`[APPROVAL-CODE-EDIT] trying submitCodeAcknowledgement command`);
|
||||
ctx.logToFile(`[APPROVAL-1-CODE] trying submitCodeAcknowledgement command`);
|
||||
await vscode.commands.executeCommand('antigravity.prioritized.submitCodeAcknowledgement');
|
||||
ctx.logToFile(`[APPROVAL-CODE-EDIT] ✅ submitCodeAcknowledgement OK`);
|
||||
ctx.logToFile(`[APPROVAL-1-CODE] ✅ submitCodeAcknowledgement OK`);
|
||||
return `CMD:submitCodeAcknowledgement(accept=${approved})`;
|
||||
} catch {
|
||||
ctx.logToFile(`[APPROVAL-CODE-EDIT] submitCodeAcknowledgement not available, trying RPC`);
|
||||
ctx.logToFile(`[APPROVAL-1-CODE] submitCodeAcknowledgement not available, trying RPC`);
|
||||
}
|
||||
// Direct LS RPC with correct method name
|
||||
try {
|
||||
ctx.logToFile(`[APPROVAL-CODE-EDIT] acknowledgeCodeActionStep(cascadeId=${sessionId.substring(0, 8)}, accept=${approved}, stepIndices=[${effectiveStepIndex}])`);
|
||||
ctx.logToFile(`[APPROVAL-1-CODE] acknowledgeCodeActionStep(cascadeId=${sessionId.substring(0, 8)}, accept=${approved}, stepIndices=[${effectiveStepIndex}])`);
|
||||
const ackResult = await ctx.sdk.ls.rawRPC('acknowledgeCodeActionStep', {
|
||||
cascadeId: sessionId,
|
||||
accept: approved,
|
||||
stepIndices: [effectiveStepIndex],
|
||||
});
|
||||
ctx.logToFile(`[APPROVAL-CODE-EDIT] ✅ SUCCESS: ${JSON.stringify(ackResult).substring(0, 200)}`);
|
||||
ctx.logToFile(`[APPROVAL-1-CODE] ✅ SUCCESS: ${JSON.stringify(ackResult).substring(0, 200)}`);
|
||||
return `RPC:acknowledgeCodeActionStep(accept=${approved})`;
|
||||
} catch (e: any) {
|
||||
ctx.logToFile(`[APPROVAL-CODE-EDIT] ❌ ${e.message.substring(0, 200)}`);
|
||||
ctx.logToFile(`[APPROVAL-CODE-EDIT] falling back to HandleCascadeUserInteraction`);
|
||||
interactionPayload = { runCommand: { confirm: true } };
|
||||
ctx.logToFile(`[APPROVAL-1-CODE] ❌ ${e.message.substring(0, 200)}`);
|
||||
// Fall through to generic HandleCascadeUserInteraction
|
||||
interactionPayload = { runCommand: { confirm: approved } };
|
||||
}
|
||||
}
|
||||
|
||||
// Map step_type to interaction sub-message field
|
||||
// CRITICAL FIX: Use `confirm: approved` (not always true) to support REJECT
|
||||
if (typeLower.includes('run_command') || typeLower.includes('shell_exec')) {
|
||||
interactionPayload = { runCommand: { confirm: true } };
|
||||
interactionPayload = { runCommand: { confirm: approved } };
|
||||
} else if (typeLower.includes('open_browser')) {
|
||||
interactionPayload = { openBrowserUrl: { confirm: true } };
|
||||
interactionPayload = { openBrowserUrl: { confirm: approved } };
|
||||
} else if (typeLower.includes('send_command_input')) {
|
||||
interactionPayload = { sendCommandInput: { confirm: true } };
|
||||
interactionPayload = { sendCommandInput: { confirm: approved } };
|
||||
} else if (typeLower.includes('read_url')) {
|
||||
interactionPayload = { readUrlContent: { confirm: true } };
|
||||
interactionPayload = { readUrlContent: { confirm: approved } };
|
||||
} else if (typeLower.includes('mcp')) {
|
||||
interactionPayload = { mcpTool: { confirm: true } };
|
||||
interactionPayload = { mcpTool: { confirm: approved } };
|
||||
} else if (typeLower.includes('invoke_subagent') || typeLower.includes('extension_code') || typeLower.includes('browser_subagent')) {
|
||||
interactionPayload = { runExtensionCode: { confirm: true } };
|
||||
interactionPayload = { runExtensionCode: { confirm: approved } };
|
||||
} else if (typeLower.includes('file_permission')) {
|
||||
if (typeLower.includes('deny')) {
|
||||
interactionPayload = { filePermission: { allow: false, scope: 1 } };
|
||||
} else {
|
||||
const scope = typeLower.includes('conversation') ? 2 : 1;
|
||||
interactionPayload = { filePermission: { allow: true, scope } };
|
||||
interactionPayload = { filePermission: { allow: approved, scope } };
|
||||
}
|
||||
} else if (typeLower.includes('elicitation')) {
|
||||
interactionPayload = { elicitation: {} };
|
||||
} else if (typeLower === 'permission' || typeLower.includes('permission')) {
|
||||
// DOM observer 'permission' type: browser_subagent Allow/Deny dialog
|
||||
// Try runExtensionCode first (most common for JS execution permission)
|
||||
interactionPayload = { runExtensionCode: { confirm: true } };
|
||||
interactionPayload = { runExtensionCode: { confirm: approved } };
|
||||
} else if (typeLower === 'command' || typeLower === '') {
|
||||
// Generic command — most common case from DOM observer
|
||||
interactionPayload = { runCommand: { confirm: approved } };
|
||||
} else {
|
||||
// Default: try run_command (most common)
|
||||
interactionPayload = { runCommand: { confirm: true } };
|
||||
// Default: try run_command
|
||||
interactionPayload = { runCommand: { confirm: approved } };
|
||||
}
|
||||
|
||||
const activeTrajectoryId = getTrajectoryId();
|
||||
const protoVariants = [
|
||||
// Variant A: camelCase with trajectoryId (proven working for run_command)
|
||||
// Variant A: camelCase with trajectoryId
|
||||
{
|
||||
cascadeId: sessionId,
|
||||
interaction: {
|
||||
@@ -431,20 +493,17 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string
|
||||
for (let i = 0; i < protoVariants.length; i++) {
|
||||
try {
|
||||
const payload = protoVariants[i];
|
||||
ctx.logToFile(`[APPROVAL-PROTO-${i}] HandleCascadeUserInteraction(${JSON.stringify(payload).substring(0, 250)})`);
|
||||
ctx.logToFile(`[APPROVAL-1-${i}] HandleCascadeUserInteraction(${JSON.stringify(payload).substring(0, 250)})`);
|
||||
const rpcResult = await ctx.sdk.ls.rawRPC('HandleCascadeUserInteraction', payload);
|
||||
ctx.logToFile(`[APPROVAL-PROTO-${i}] ✅ SUCCESS: ${JSON.stringify(rpcResult).substring(0, 200)}`);
|
||||
return `RPC-PROTO-${i}:HandleCascadeUserInteraction(${typeLower})`;
|
||||
ctx.logToFile(`[APPROVAL-1-${i}] ✅ SUCCESS: ${JSON.stringify(rpcResult).substring(0, 200)}`);
|
||||
return `RPC-${i}:HandleCascadeUserInteraction(${typeLower},${action})`;
|
||||
} catch (e: any) {
|
||||
lastRpcError = e.message || '';
|
||||
ctx.logToFile(`[APPROVAL-PROTO-${i}] ❌ ${lastRpcError.substring(0, 300)}`);
|
||||
ctx.logToFile(`[APPROVAL-1-${i}] ❌ ${lastRpcError.substring(0, 300)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Auto-recovery: wrong-LS detection ──────────────────────
|
||||
// All 3 proto variants failed. If the error is "input not registered",
|
||||
// SDK is likely connected to wrong LS process. Attempt fixLSConnection
|
||||
// and retry ONE time to avoid permanent failure.
|
||||
if (ctx.fixLSConnection && lastRpcError.includes('input not registered')) {
|
||||
ctx.logToFile('[APPROVAL] ⚠️ wrong-LS detected ("input not registered"), attempting LS fix...');
|
||||
try {
|
||||
@@ -453,10 +512,9 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string
|
||||
ctx.logToFile('[APPROVAL] LS reconnected — retrying first proto variant...');
|
||||
try {
|
||||
const retryPayload = protoVariants[0];
|
||||
ctx.logToFile(`[APPROVAL-RETRY] HandleCascadeUserInteraction(${JSON.stringify(retryPayload).substring(0, 250)})`);
|
||||
const retryResult = await ctx.sdk.ls.rawRPC('HandleCascadeUserInteraction', retryPayload);
|
||||
ctx.logToFile(`[APPROVAL-RETRY] ✅ SUCCESS: ${JSON.stringify(retryResult).substring(0, 200)}`);
|
||||
return `RPC-RETRY:HandleCascadeUserInteraction(${typeLower})`;
|
||||
return `RPC-RETRY:HandleCascadeUserInteraction(${typeLower},${action})`;
|
||||
} catch (retryErr: any) {
|
||||
ctx.logToFile(`[APPROVAL-RETRY] ❌ ${retryErr.message?.substring(0, 200)}`);
|
||||
}
|
||||
@@ -467,9 +525,14 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string
|
||||
ctx.logToFile(`[APPROVAL] fixLSConnection error: ${fixErr.message?.substring(0, 200)}`);
|
||||
}
|
||||
}
|
||||
} else if (ctx.sdk && effectiveStepIndex < 0) {
|
||||
ctx.logToFile(`[APPROVAL-1] SKIPPED RPC: stepIndex=${effectiveStepIndex} (unknown) — Strategy 0 (VS Code command) was the primary attempt`);
|
||||
}
|
||||
|
||||
// ── Strategy 2: Renderer DOM Click via HTTP Bridge (primary fallback) ──
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 2: Renderer DOM Click via HTTP Bridge (fallback)
|
||||
// Sets a click trigger that the observer script polls and executes.
|
||||
// ══════════════════════════════════════════════════════════
|
||||
try {
|
||||
const triggerAction = approved ? 'approve' : 'reject';
|
||||
ctx.logToFile(`[APPROVAL-2] Setting clickTrigger=${triggerAction} for renderer DOM click`);
|
||||
@@ -479,6 +542,6 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string
|
||||
ctx.logToFile(`[APPROVAL-2] ❌ FAIL: ${e.message}`);
|
||||
}
|
||||
|
||||
ctx.logToFile(`[APPROVAL] strategies complete — check logs for results`);
|
||||
ctx.logToFile(`[APPROVAL] All strategies complete for ${action}`);
|
||||
return `STRATEGIES_DONE:${action}`;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,25 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise<numbe
|
||||
return;
|
||||
}
|
||||
|
||||
// GET /status — diagnostic endpoint
|
||||
if (req.method === 'GET' && url.pathname === '/status') {
|
||||
const { getStepProbeContext } = require('./step-probe');
|
||||
const probeCtx = getStepProbeContext();
|
||||
const status = {
|
||||
projectName: ctx.projectName,
|
||||
activeSessionId: probeCtx.activeSessionId || ctx.activeSessionId,
|
||||
lastPendingStepIndex: probeCtx.lastPendingStepIndex,
|
||||
sessionStalled: probeCtx.sessionStalled,
|
||||
wsConnected: ctx.wsBridge?.isConnected() ?? false,
|
||||
clickTrigger: clickTrigger ? { ...clickTrigger, ageMs: Date.now() - clickTrigger.timestamp } : null,
|
||||
uptime: Math.round(process.uptime()),
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(status, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
// GET /ping — health check
|
||||
if (url.pathname === '/ping') {
|
||||
res.writeHead(200); res.end('pong');
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
export function generateApprovalObserverScript(_port: number): string {
|
||||
return `
|
||||
// ── Gravity Bridge v6: Clean Context Extraction ──
|
||||
// ── Gravity Bridge v7: Step-Aware AG Native DOM Parser ──
|
||||
// Uses data-testid="conversation-view" + data-step-index for reliable parsing
|
||||
(function(){
|
||||
'use strict';
|
||||
var BASE='',_obs=false,_sent={},_ready=false;
|
||||
var _scanScheduled=false,_lastScanTs=0;
|
||||
var THROTTLE_MS=500;
|
||||
var CLEANUP_MS=300000;
|
||||
var _dumpSent=false; // one-time DOM dump
|
||||
|
||||
function log(m){console.log('[GB Observer] '+m);}
|
||||
log('v6 Script loaded — Clean Context Extraction');
|
||||
log('v7 Script loaded — Step-Aware AG Native DOM Parser');
|
||||
|
||||
// React-Compatible Synthetic Clicker
|
||||
function dispatchReactClick(el){
|
||||
@@ -26,57 +28,50 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
}
|
||||
|
||||
// ── Noise filter: lines that are UI artifacts, not real content ──
|
||||
var NOISE_PATTERNS = [
|
||||
/^chevron_right$/i,
|
||||
/^chevron_left$/i,
|
||||
/^arrow_/i,
|
||||
/^Thought for \\\\d+/i,
|
||||
/^expand_/i,
|
||||
/^close$/i,
|
||||
/^more_/i,
|
||||
/^content_copy$/i,
|
||||
/^check$/i,
|
||||
/^\\\\d+ lines?$/i,
|
||||
/^Show more$/i,
|
||||
/^Show less$/i,
|
||||
/^Copy$/i,
|
||||
/^Edit$/i,
|
||||
/^Copied!$/i,
|
||||
/^\\\\s*$/,
|
||||
/^declare\\\\s+(class|function|interface|type|enum|const|var|let)\\\\s/, // TypeScript declarations
|
||||
/^(import|export|from)\\\\s/, // JS imports
|
||||
/^\\\\s*[{}()\\\\[\\\\];]\\\\s*$/, // lone brackets
|
||||
/\\\\.ts:\\\\d+:/, // file:line references
|
||||
/extension.*src.*sdk/i, // SDK file paths
|
||||
];
|
||||
var NOISE_RE = new RegExp(
|
||||
'^(' +
|
||||
'chevron_right|chevron_left|arrow_drop_down|arrow_drop_up|arrow_right|arrow_left|' +
|
||||
'arrow_forward|arrow_back|expand_more|expand_less|close|more_horiz|more_vert|' +
|
||||
'content_copy|content_paste|check|check_circle|error|warning|info|' +
|
||||
'keyboard_arrow_up|keyboard_arrow_down|keyboard_arrow_left|keyboard_arrow_right|' +
|
||||
'Thought for \\\\d+|Show more|Show less|Copy|Copied!|Edit|Cancel|' +
|
||||
'Always run|Always allow|Running command|Running \\\\d+ commands?|' +
|
||||
'Deny|Allow|Allow Once|Allow This Conversation|' +
|
||||
'Run|Send|Stop|Review Changes|Accept all|Reject all|Accept|Reject' +
|
||||
')$', '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) {
|
||||
if (!line || line.trim().length < 2) return true;
|
||||
var trimmed = line.trim();
|
||||
for (var i = 0; i < NOISE_PATTERNS.length; i++) {
|
||||
if (NOISE_PATTERNS[i].test(trimmed)) return true;
|
||||
}
|
||||
if (NOISE_RE.test(trimmed)) return true;
|
||||
if (NOISE_CODE_RE.test(trimmed)) return true;
|
||||
// Single-word Material icon names (all lowercase, no spaces)
|
||||
if (/^[a-z_]+$/.test(trimmed) && trimmed.length < 30) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function cleanLines(text) {
|
||||
if (!text) return '';
|
||||
var lines = text.split('\\\\n');
|
||||
var lines = text.split('\\n');
|
||||
var clean = [];
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (!isNoiseLine(lines[i])) clean.push(lines[i].trim());
|
||||
}
|
||||
return clean.join('\\\\n').trim();
|
||||
return clean.join('\\n').trim();
|
||||
}
|
||||
|
||||
function cleanButtonText(btn) {
|
||||
if (!btn) return '';
|
||||
var clone = btn.cloneNode(true);
|
||||
var icons = clone.querySelectorAll('.google-symbols, .codicon, [class*="icon"], svg');
|
||||
var icons = clone.querySelectorAll('.google-symbols, .codicon, [class*="icon"], svg, .material-symbols-outlined, .material-icons');
|
||||
for(var i=0; i<icons.length; i++) {
|
||||
if(icons[i].parentNode) icons[i].parentNode.removeChild(icons[i]);
|
||||
}
|
||||
var tr = clone.querySelector('.truncate');
|
||||
var txt = (tr ? tr.textContent : clone.textContent) || '';
|
||||
return txt.trim().replace(/^[\\\\s\\\\u200B-\\\\u200D\\\\uFEFF\\\\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\\\+.*/i,'').trim();
|
||||
return txt.trim().replace(/^[\\s\\u200B-\\u200D\\uFEFF\\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\+.*/i,'').trim();
|
||||
}
|
||||
|
||||
function btnId(b,type){
|
||||
@@ -90,81 +85,73 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
return type+'|'+txt+'|'+idx;
|
||||
}
|
||||
|
||||
// ── Context extraction: TIGHT scope — only button's immediate context ──
|
||||
// v6 FIX: Never climb more than 4 parents. Never grab editor/sidebar content.
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// v7: STEP-AWARE CONTEXT EXTRACTION
|
||||
// Find the closest [data-step-index] ancestor, extract step info
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
function extractCommandContext(b) {
|
||||
// Strategy 1: aria-label or title on button itself
|
||||
var ariaLabel = b.getAttribute('aria-label') || b.getAttribute('title') || '';
|
||||
if (ariaLabel && ariaLabel.length > 5 && ariaLabel.length < 500) {
|
||||
return ariaLabel;
|
||||
function getStepContainer(el) {
|
||||
var node = el;
|
||||
for (var depth = 0; depth < 10 && node; depth++) {
|
||||
if (node.hasAttribute && node.hasAttribute('data-step-index')) return node;
|
||||
node = node.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Strategy 2: Look for command text in button's DIRECT parent chain (max 3 levels)
|
||||
var el = b.parentElement;
|
||||
for (var depth = 0; depth < 3 && el; depth++) {
|
||||
// Check for code/pre elements (command text)
|
||||
var pres = el.querySelectorAll('pre, code');
|
||||
for (var pi = 0; pi < pres.length; pi++) {
|
||||
var preText = (pres[pi].textContent || '').trim();
|
||||
if (preText.length > 2 && preText.length < 500 && !isNoiseLine(preText)) {
|
||||
return preText.substring(0, 400);
|
||||
function extractStepContext(btn) {
|
||||
var stepEl = getStepContainer(btn);
|
||||
if (!stepEl) return cleanButtonText(btn);
|
||||
|
||||
var stepIdx = stepEl.getAttribute('data-step-index') || '?';
|
||||
|
||||
// Get step header text (first line, usually has the tool name/command)
|
||||
var header = stepEl.querySelector('[class*="cursor-pointer"]');
|
||||
var headerText = '';
|
||||
if (header) {
|
||||
// Clone and strip icons/buttons
|
||||
var hClone = header.cloneNode(true);
|
||||
var hRemove = hClone.querySelectorAll('button, svg, [class*="icon"], .google-symbols, .material-symbols-outlined');
|
||||
for (var i = 0; i < hRemove.length; i++) {
|
||||
if (hRemove[i].parentNode) hRemove[i].parentNode.removeChild(hRemove[i]);
|
||||
}
|
||||
}
|
||||
// Check for span with title attribute containing command info
|
||||
var titleSpans = el.querySelectorAll('span[title]');
|
||||
for (var ti = 0; ti < titleSpans.length; ti++) {
|
||||
var spanTitle = titleSpans[ti].getAttribute('title') || '';
|
||||
if (spanTitle.length > 5 && spanTitle.length < 500) {
|
||||
return spanTitle.substring(0, 400);
|
||||
}
|
||||
}
|
||||
el = el.parentElement;
|
||||
headerText = (hClone.textContent || '').trim().substring(0, 300);
|
||||
// Clean noise
|
||||
headerText = cleanLines(headerText);
|
||||
}
|
||||
|
||||
// Strategy 3: Immediate parent's text only (NOT full page)
|
||||
var immediateParent = b.parentElement;
|
||||
if (immediateParent) {
|
||||
var parentText = '';
|
||||
var children = immediateParent.childNodes;
|
||||
for (var ci = 0; ci < children.length; ci++) {
|
||||
var child = children[ci];
|
||||
if (child.nodeType === 3 && child.nodeValue && child.nodeValue.trim()) {
|
||||
parentText += child.nodeValue.trim() + ' ';
|
||||
} else if (child.nodeType === 1 && child.tagName !== 'BUTTON' && child.tagName !== 'SVG') {
|
||||
var childText = child.textContent || '';
|
||||
if (childText.trim().length > 2 && childText.trim().length < 200) {
|
||||
parentText += childText.trim() + ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
parentText = parentText.trim();
|
||||
if (parentText.length > 3 && parentText.length < 300) {
|
||||
return cleanLines(parentText).substring(0, 300);
|
||||
}
|
||||
// Try to get code/pre content (command detail)
|
||||
var codeEl = stepEl.querySelector('pre, code');
|
||||
var codeText = '';
|
||||
if (codeEl) {
|
||||
codeText = (codeEl.textContent || '').trim().substring(0, 400);
|
||||
}
|
||||
|
||||
return '';
|
||||
// Try aria-label on button
|
||||
var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || '';
|
||||
|
||||
var parts = [];
|
||||
if (headerText) parts.push(headerText);
|
||||
if (codeText && !headerText.includes(codeText.substring(0, 20))) parts.push(codeText);
|
||||
if (ariaLabel && ariaLabel.length > 5 && !headerText.includes(ariaLabel)) parts.push(ariaLabel);
|
||||
|
||||
var result = parts.join(' — ');
|
||||
if (!result) result = cleanButtonText(btn);
|
||||
return 'Step #' + stepIdx + ': ' + result;
|
||||
}
|
||||
|
||||
function extractContext(b) {
|
||||
var cmd = cleanButtonText(b);
|
||||
var detail = extractCommandContext(b);
|
||||
if (!detail) return cmd;
|
||||
// Deduplicate: if detail contains button text, just show detail
|
||||
if (detail.includes(cmd)) return cleanLines(detail);
|
||||
return cmd + ': ' + cleanLines(detail);
|
||||
return extractStepContext(b);
|
||||
}
|
||||
|
||||
var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', 'Always allow', '결행', '수락', '반영', '허용', '승인'];
|
||||
var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss', '거절', '거부', '취소', '차단', '정지', '무시'];
|
||||
var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', 'Always allow', 'Always run'];
|
||||
var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss'];
|
||||
|
||||
function isActionBtn(txt) {
|
||||
for(var i=0; i<ACTION_WORDS.length; i++) {
|
||||
if(txt.indexOf(ACTION_WORDS[i]) !== -1) return true;
|
||||
}
|
||||
// "Running N command(s)" pattern
|
||||
if (/Running\\\\d*\\\\s*command/i.test(txt)) return true;
|
||||
if (/Running\\s*\\d*\\s*command/i.test(txt)) return true;
|
||||
return false;
|
||||
}
|
||||
function isRejectBtn(txt) {
|
||||
@@ -202,7 +189,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
var items = document.querySelectorAll('[aria-label^="Gravity Bridge Control"], [title^="Gravity Bridge Control"]');
|
||||
if (items.length > 0) {
|
||||
var text = items[0].getAttribute('aria-label') || items[0].getAttribute('title') || '';
|
||||
var m = text.match(/port:(\\\\d+)/);
|
||||
var m = text.match(/port:(\\d+)/);
|
||||
if (m && m[1]) {
|
||||
clearInterval(timer);
|
||||
tryPingAsync(parseInt(m[1], 10)).then(function(ok){ cb(ok ? parseInt(m[1],10) : HARDCODED_PORT); });
|
||||
@@ -222,67 +209,218 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
startObserver();
|
||||
});
|
||||
|
||||
// ── Chat body scanning (for Discord relay of AI responses) ──
|
||||
var _lastText = "";
|
||||
var _lastTextTime = 0;
|
||||
var _lastTextSent = false;
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// v7: DOM STRUCTURE DUMP (one-time, on first conversation-view detection)
|
||||
// Posts the DOM tree structure to /dump-html for debugging
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
function extractCleanChatText(container) {
|
||||
if (!container) return '';
|
||||
// Try markdown body first
|
||||
var md = container.querySelector('.markdown-body') || container.querySelector('.prose');
|
||||
var rawText = '';
|
||||
if (md && md.innerText && md.innerText.trim().length > 10) {
|
||||
rawText = md.innerText.trim();
|
||||
} else {
|
||||
rawText = (container.innerText || container.textContent || '').trim();
|
||||
function dumpDOMStructure() {
|
||||
if (_dumpSent || !_ready) return;
|
||||
var cv = document.querySelector('[data-testid="conversation-view"]');
|
||||
if (!cv) return;
|
||||
_dumpSent = true;
|
||||
|
||||
// Walk the DOM tree and capture structure (classes, data-attrs, tag names)
|
||||
function walkNode(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, 200) : '',
|
||||
attrs: {},
|
||||
text: '',
|
||||
children: []
|
||||
};
|
||||
// Capture data-* and role attributes
|
||||
if (el.attributes) {
|
||||
for (var ai = 0; ai < el.attributes.length; ai++) {
|
||||
var attr = el.attributes[ai];
|
||||
if (attr.name.startsWith('data-') || attr.name === 'role' || attr.name === 'aria-label') {
|
||||
info.attrs[attr.name] = (attr.value || '').substring(0, 100);
|
||||
}
|
||||
// Clean the text
|
||||
}
|
||||
}
|
||||
// For leaf text nodes, capture short text
|
||||
if (!el.children || el.children.length === 0) {
|
||||
var t = (el.textContent || '').trim();
|
||||
if (t.length > 0 && t.length < 100) info.text = t;
|
||||
}
|
||||
// Recurse children (limit to first 10 per level)
|
||||
if (el.children) {
|
||||
for (var ci = 0; ci < Math.min(el.children.length, 10); ci++) {
|
||||
var childInfo = walkNode(el.children[ci], depth + 1);
|
||||
if (childInfo) info.children.push(childInfo);
|
||||
}
|
||||
if (el.children.length > 10) {
|
||||
info.children.push({tag: '...', text: '+' + (el.children.length - 10) + ' more'});
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
var structure = walkNode(cv, 0);
|
||||
var payload = JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
source: 'v7_dom_dump',
|
||||
conversationView: structure
|
||||
});
|
||||
log('DOM dump: conversation-view found, sending ' + payload.length + ' bytes');
|
||||
fetch(BASE + '/dump-html', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: payload
|
||||
}).catch(function(e) { log('DOM dump failed: ' + e.message); });
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// v7: STEP-AWARE CHAT BODY SCANNING
|
||||
// Scans [data-step-index] elements inside [data-testid="conversation-view"]
|
||||
// Extracts AI response text while filtering UI noise
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
var _lastScrapedStepIndex = -1;
|
||||
var _lastStepText = '';
|
||||
var _lastStepTextTime = 0;
|
||||
var _lastStepTextSent = false;
|
||||
|
||||
function extractCleanStepText(stepEl) {
|
||||
if (!stepEl) return '';
|
||||
|
||||
// Clone the step element so we can strip UI elements without affecting the DOM
|
||||
var clone = stepEl.cloneNode(true);
|
||||
|
||||
// Remove all buttons (Run, Allow, Cancel, etc.)
|
||||
var buttons = clone.querySelectorAll('button');
|
||||
for (var bi = 0; bi < buttons.length; bi++) {
|
||||
if (buttons[bi].parentNode) buttons[bi].parentNode.removeChild(buttons[bi]);
|
||||
}
|
||||
|
||||
// Remove all SVGs and icon elements
|
||||
var icons = clone.querySelectorAll('svg, .google-symbols, .material-symbols-outlined, .material-icons, [class*="codicon"], [class*="icon"]');
|
||||
for (var ii = 0; ii < icons.length; ii++) {
|
||||
if (icons[ii].parentNode) icons[ii].parentNode.removeChild(icons[ii]);
|
||||
}
|
||||
|
||||
// Remove copy buttons and their containers
|
||||
var copyBtns = clone.querySelectorAll('[class*="copy"], [aria-label*="copy"], [title*="Copy"]');
|
||||
for (var ci = 0; ci < copyBtns.length; ci++) {
|
||||
if (copyBtns[ci].parentNode) copyBtns[ci].parentNode.removeChild(copyBtns[ci]);
|
||||
}
|
||||
|
||||
// Try to get text from markdown rendering area first
|
||||
// Look for known markdown container patterns
|
||||
var mdEl = clone.querySelector('.markdown-body, .prose, [class*="markdown"], [class*="rendered"]');
|
||||
var rawText = '';
|
||||
if (mdEl && mdEl.innerText && mdEl.innerText.trim().length > 10) {
|
||||
rawText = mdEl.innerText.trim();
|
||||
} else {
|
||||
// Fallback: get all text but filter aggressively
|
||||
rawText = (clone.innerText || clone.textContent || '').trim();
|
||||
}
|
||||
|
||||
// Apply line-by-line noise filter
|
||||
return cleanLines(rawText).substring(0, 3500);
|
||||
}
|
||||
|
||||
function scanChatBodies() {
|
||||
if(!_ready)return;
|
||||
// Find bot response containers — try multiple selectors for compatibility
|
||||
var botTurns = document.querySelectorAll(
|
||||
'.text-ide-message-block-bot-color, ' +
|
||||
'[data-testid*="bot"], [data-testid*="assistant"], ' +
|
||||
'[class*="agent-convo"], [class*="bot-message"]'
|
||||
);
|
||||
if (botTurns.length === 0) return;
|
||||
if (!_ready) return;
|
||||
|
||||
var lastTurn = botTurns[botTurns.length - 1];
|
||||
if (lastTurn.dataset.agChatScraped === "true" || lastTurn.dataset.agChatScraped === "pending") return;
|
||||
// One-time DOM dump
|
||||
dumpDOMStructure();
|
||||
|
||||
var currentText = lastTurn.textContent || '';
|
||||
if (currentText.length < 5) return;
|
||||
// PRIMARY: Find conversation-view container
|
||||
var cv = document.querySelector('[data-testid="conversation-view"]');
|
||||
if (!cv) {
|
||||
// FALLBACK: Try older selectors
|
||||
cv = document.querySelector('[class*="conversation"], [class*="chat-container"]');
|
||||
if (!cv) return;
|
||||
}
|
||||
|
||||
if (_lastText !== currentText) {
|
||||
_lastText = currentText;
|
||||
_lastTextTime = Date.now();
|
||||
_lastTextSent = false;
|
||||
} else if (!_lastTextSent) {
|
||||
if (Date.now() - _lastTextTime > 3000) {
|
||||
_lastTextSent = true;
|
||||
lastTurn.dataset.agChatScraped = "pending";
|
||||
var finalTxt = extractCleanChatText(lastTurn);
|
||||
if (finalTxt && finalTxt.length > 5 && finalTxt !== "Review Changes") {
|
||||
fetch(BASE+'/chat', {
|
||||
// Find ALL step elements within the conversation
|
||||
var stepEls = cv.querySelectorAll('[data-step-index]');
|
||||
if (stepEls.length === 0) {
|
||||
// FALLBACK: Try text-ide-message-block-bot-color directly
|
||||
var botMsgs = cv.querySelectorAll('.text-ide-message-block-bot-color');
|
||||
if (botMsgs.length === 0) return;
|
||||
// Use the last bot message as a pseudo-step
|
||||
var lastBot = botMsgs[botMsgs.length - 1];
|
||||
if (lastBot.dataset.agChatScraped === 'true' || lastBot.dataset.agChatScraped === 'pending') return;
|
||||
var botText = extractCleanStepText(lastBot);
|
||||
if (botText && botText.length > 20) {
|
||||
if (_lastStepText !== botText) {
|
||||
_lastStepText = botText;
|
||||
_lastStepTextTime = Date.now();
|
||||
_lastStepTextSent = false;
|
||||
} else if (!_lastStepTextSent && (Date.now() - _lastStepTextTime > 3000)) {
|
||||
_lastStepTextSent = true;
|
||||
lastBot.dataset.agChatScraped = 'pending';
|
||||
fetch(BASE + '/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: finalTxt })
|
||||
}).then(function(){
|
||||
lastTurn.dataset.agChatScraped = "true";
|
||||
}).catch(function(){
|
||||
lastTurn.dataset.agChatScraped = "false";
|
||||
});
|
||||
} else {
|
||||
lastTurn.dataset.agChatScraped = "true";
|
||||
body: JSON.stringify({ text: botText, source: 'fallback_bot_msg' })
|
||||
}).then(function() { lastBot.dataset.agChatScraped = 'true'; })
|
||||
.catch(function() { lastBot.dataset.agChatScraped = 'false'; });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the LATEST step that has meaningful text content (AI response)
|
||||
// Iterate backwards to find the most recent unscraped step
|
||||
for (var si = stepEls.length - 1; si >= Math.max(0, stepEls.length - 5); si--) {
|
||||
var stepEl = stepEls[si];
|
||||
var stepIdx = parseInt(stepEl.getAttribute('data-step-index') || '-1', 10);
|
||||
|
||||
// Skip already scraped
|
||||
if (stepEl.dataset.agChatScraped === 'true' || stepEl.dataset.agChatScraped === 'pending') continue;
|
||||
|
||||
// Skip steps we've already processed
|
||||
if (stepIdx >= 0 && stepIdx <= _lastScrapedStepIndex) continue;
|
||||
|
||||
// Check if this step has substantial text content (not just a tool call header)
|
||||
var stepText = extractCleanStepText(stepEl);
|
||||
if (!stepText || stepText.length < 30) continue;
|
||||
|
||||
// QUALITY CHECK: Skip if the text is mostly short lines (UI artifacts)
|
||||
var lines = stepText.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('Step ' + stepIdx + ': skipped (no long lines, likely UI noise)');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait for content to stabilize (3s no change)
|
||||
if (_lastStepText !== stepText) {
|
||||
_lastStepText = stepText;
|
||||
_lastStepTextTime = Date.now();
|
||||
_lastStepTextSent = false;
|
||||
break; // Wait for next scan cycle
|
||||
}
|
||||
|
||||
if (_lastStepTextSent) continue;
|
||||
if (Date.now() - _lastStepTextTime < 3000) break; // Still waiting
|
||||
|
||||
// Content is stable — send it
|
||||
_lastStepTextSent = true;
|
||||
_lastScrapedStepIndex = stepIdx;
|
||||
stepEl.dataset.agChatScraped = 'pending';
|
||||
|
||||
log('Chat relay: step=' + stepIdx + ' text=' + stepText.length + ' chars');
|
||||
(function(el, txt, idx) {
|
||||
fetch(BASE + '/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: txt, source: 'step_' + idx, step_index: idx })
|
||||
}).then(function() { el.dataset.agChatScraped = 'true'; })
|
||||
.catch(function() { el.dataset.agChatScraped = 'false'; });
|
||||
})(stepEl, stepText, stepIdx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// BUTTON SCANNING (approval detection)
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
function scan(){
|
||||
if(!_ready)return;
|
||||
scanChatBodies();
|
||||
@@ -299,15 +437,19 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
|
||||
if(!isActionBtn(txt)) continue;
|
||||
// Skip inline code lens buttons
|
||||
if (b.closest('.codelens-decoration') && !txt.includes('Accept') && !txt.includes('수락') && !txt.includes('반영')) {
|
||||
if (b.closest('.codelens-decoration') && !txt.includes('Accept')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchedType = txt.includes('Accept') ? 'diff_review' : (txt.includes('Run') || /Running\\\\d/.test(txt) ? 'command' : 'permission');
|
||||
var container=b.parentElement;
|
||||
var groupKey=matchedType+'|'+btnId(b,matchedType);
|
||||
var matchedType = txt.includes('Accept') ? 'diff_review' : (txt.includes('Run') || /Running\\d/.test(txt) ? 'command' : 'permission');
|
||||
|
||||
// v7: Use step-index for more unique group key
|
||||
var stepContainer = getStepContainer(b);
|
||||
var stepIdx = stepContainer ? stepContainer.getAttribute('data-step-index') : 'none';
|
||||
var groupKey = matchedType + '|' + stepIdx + '|' + btnId(b, matchedType);
|
||||
if(_sent[groupKey])continue;
|
||||
|
||||
var container=b.parentElement;
|
||||
var siblings=collectSiblingButtons(container,b);
|
||||
if(siblings.length===0)siblings=[{btn:b,text:txt,isPrimary:true}];
|
||||
|
||||
@@ -329,7 +471,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
_sent[groupKey]={rid:rid,ts:now};
|
||||
for(var mk=0;mk<bidList.length;mk++)_sent[bidList[mk]]={rid:rid,ts:now};
|
||||
|
||||
log('DETECTED '+matchedType+': '+txt+' ['+desc.substring(0,80)+']');
|
||||
log('DETECTED '+matchedType+': '+txt+' ['+desc.substring(0,80)+'] step='+stepIdx);
|
||||
|
||||
(function(rid2,btnRefs2,bidList2,groupKey2,txt2,desc2,type2,buttonsArr2){
|
||||
var payload={
|
||||
@@ -379,13 +521,13 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
clearInterval(timer);
|
||||
var btnIdx=(typeof d.button_index==='number'&&d.button_index>=0)?d.button_index:-1;
|
||||
if(btnIdx>=0&&btnIdx<btnRefs.length){
|
||||
log((d.approved?'✅':'❌')+' CHOICE '+rid+' → clicking button['+btnIdx+']');
|
||||
log((d.approved?'OK':'NO')+' CHOICE '+rid+' -> clicking button['+btnIdx+']');
|
||||
dispatchReactClick(btnRefs[btnIdx]);
|
||||
} else if(d.approved){
|
||||
log('✅ APPROVED '+rid+' → clicking primary');
|
||||
log('OK APPROVED '+rid+' -> clicking primary');
|
||||
dispatchReactClick(btnRefs[0]);
|
||||
} else {
|
||||
log('❌ REJECTED '+rid+' → finding reject button');
|
||||
log('NO REJECTED '+rid+' -> finding reject button');
|
||||
clickRejectButton(btnRefs[0]);
|
||||
}
|
||||
delete _sent[groupKey];
|
||||
@@ -471,6 +613,57 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
setTimeout(pollTriggerClick, 2000);
|
||||
})();
|
||||
|
||||
// ── DEEP-INSPECT POLLING ──
|
||||
(function pollDeepInspect(){
|
||||
if(_ready&&BASE){
|
||||
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||
if(!d.inspect)return;
|
||||
log('Deep inspect triggered');
|
||||
var cv = document.querySelector('[data-testid="conversation-view"]');
|
||||
var result = {
|
||||
timestamp: new Date().toISOString(),
|
||||
windowURL: window.location.href,
|
||||
conversationViewFound: !!cv,
|
||||
stepElements: [],
|
||||
buttons: [],
|
||||
totalElements: document.body ? document.querySelectorAll('*').length : 0,
|
||||
};
|
||||
if (cv) {
|
||||
var steps = cv.querySelectorAll('[data-step-index]');
|
||||
for (var si = 0; si < steps.length; si++) {
|
||||
var s = steps[si];
|
||||
var text = (s.textContent || '').trim().substring(0, 200);
|
||||
result.stepElements.push({
|
||||
stepIndex: s.getAttribute('data-step-index'),
|
||||
classes: (s.className || '').substring(0, 200),
|
||||
textPreview: text,
|
||||
childCount: s.children ? s.children.length : 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
var allBtns = document.querySelectorAll('button');
|
||||
for (var bi = 0; bi < Math.min(allBtns.length, 30); bi++) {
|
||||
var btn = allBtns[bi];
|
||||
var btxt = cleanButtonText(btn);
|
||||
if (btxt.length > 0) {
|
||||
var stepC = getStepContainer(btn);
|
||||
result.buttons.push({
|
||||
text: btxt,
|
||||
stepIndex: stepC ? stepC.getAttribute('data-step-index') : null,
|
||||
visible: !!(btn.offsetParent || btn.style.display === 'fixed'),
|
||||
});
|
||||
}
|
||||
}
|
||||
fetch(BASE+'/deep-inspect-result', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(result)
|
||||
}).catch(function(){});
|
||||
}).catch(function(){});
|
||||
}
|
||||
setTimeout(pollDeepInspect, 3000);
|
||||
})();
|
||||
|
||||
_obs=true;
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -39,7 +39,7 @@ let responseWatcher: fs.FSWatcher | null = null;
|
||||
let brainWatcher: BrainWatcher | null = null;
|
||||
let activeTrajectoryId = '';
|
||||
const recentPendingSteps = new Map<string, number>();
|
||||
const PENDING_MEMORY_TTL_MS = 60_000;
|
||||
const PENDING_MEMORY_TTL_MS = 30_000;
|
||||
|
||||
// generateApprovalObserverScript → extracted to ./observer-script.ts
|
||||
const lastSnapshotText = new Map<string, string>();
|
||||
@@ -589,6 +589,7 @@ function setupMonitor() {
|
||||
// lastModifiedTime is still changing = AI is thinking, NOT approval
|
||||
consecutiveIdleCount = 0; // Reset!
|
||||
ctx.stallProbed = false;
|
||||
ctx.sessionStalled = false; // FIX: also reset stalled flag on modTime change
|
||||
if (pollCount <= 10 || pollCount % 12 === 0) {
|
||||
ctx.logToFile(`[THINK] step=${currentCount} modTime changing → not stall`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user