Compare commits

...

272 Commits

Author SHA1 Message Date
Variet Worker
32cf69469c docs: session end — known-issues 2건 추가 + observer-dev-guide 3.3 업데이트 + devlog #019
[2026-04-19] Accept all span 렌더링 감지 실패 (v0.5.101)
[2026-04-19] auto-approve _from_ws 마커 누락 (v0.5.103)
observer-dev-guide 3.3: Accept all Observer 접근 가능으로 변경
Vikunja: #638 done, #639 done
2026-04-20 04:58:13 +09:00
Variet Worker
7c8891b99c fix(bridge): v38 auto-approve response에 _from_ws 마커 추가 — Observer polling 실패 수정 (v0.5.103)
근본 원인: auto-approve response 파일에 _from_ws 마커 없음.
processResponseFile(response watcher)이 Observer보다 먼저 파일을 읽고 삭제.
Observer의 GET /response/{rid} polling이 항상 {waiting:true} 반환.
known-issues [2026-04-18] WS response 파일 삭제 버그와 동일 패턴.
2026-04-20 04:43:56 +09:00
Variet Worker
3cc3442fda fix(bridge): v37 openReviewChanges 선호출 — agentAcceptAllInFile 실효성 보장 (v0.5.102)
agentAcceptAllInFile은 diff review 패널이 포커스되어야 동작.
v34에서 직접 호출만 했더니 SUCCESS 반환하지만 실제 효과 없음.
openReviewChanges → 500ms 대기 → agentAcceptAllInFile 순서로 수정.
2026-04-20 04:34:44 +09:00
Variet Worker
e95e7791f9 fix(observer): v36 Accept all 감지 — span.cursor-pointer 선택자 추가 (v0.5.101)
근본 원인: AG Native에서 Accept all 버튼이 <button>이 아닌 <span> 태그.
Observer의 allBtns 선택자가 button만 스캔하여 Accept all 미감지.
ACCEPT-SCAN 로그에서 tag=SPAN cls=cursor-pointer 확인.
span.cursor-pointer 추가로 diff review 버튼 감지 복구.
2026-04-20 04:26:58 +09:00
Variet Worker
2bf1eb41d1 feat(probe): v35 code_edit 자동 Accept — agentAcceptAllInFile 직접 호출 (v0.5.100)
Accept all 버튼이 Observer DOM에 없음 확인 (에디터 레이어).
step_probe에서 WAITING code_edit 감지 시 500ms 후 agentAcceptAllInFile 직접 실행.
Observer relay 필터에 ACCEPT 추가.
2026-04-20 04:05:47 +09:00
Variet Worker
cf1352eefa fix(bridge): v34 Accept all 이중 보장 — agentAcceptAllInFile 직접 호출 + ACCEPT 로그 relay (v0.5.99)
v34: Accept all/diff_review 감지 시 Observer DOM 클릭 + extension host agentAcceptAllInFile 이중 실행.
Observer relay 필터에 ACCEPT 키워드 추가로 ACCEPT-SCAN 진단 로그 활성화.
2026-04-20 00:11:07 +09:00
Variet Worker
6aea48e2e9 feat(bridge): v33 Accept all 자동승인 — diff review 버튼도 auto-approve (v0.5.98)
Always run과 동일하게 Accept all / Accept 버튼도 즉시 자동 승인.
Observer가 step_type=diff_review로 전송 → bridge에서 AUTO_APPROVE_RE 매칭 → 즉시 응답.
2026-04-19 21:27:18 +09:00
Variet Worker
bd5a7ca8b9 fix(observer): v32 터미널 프롬프트 조기감지 — JUNK/PROMPT 필터 전에 명령어 추출 (v0.5.97)
근본원인: code 요소의 textContent '❯ project > command'가 PowerShell 키워드
(return, function, const)를 포함할 수 있어 JUNK_CODE_RE에 잘못 걸림.
v32: ❯ 마커로 시작하는 code 텍스트는 JUNK/PROMPT 전에 명령어 직접 추출.
14/14 E2E 테스트 통과.
2026-04-19 19:55:44 +09:00
Variet Worker
8ada5f7daf fix(observer): v31 — content_copy 아이콘 필터 + 후보 길이순 정렬 + trailing icon strip (v0.5.96) 2026-04-19 15:18:11 +09:00
Variet Worker
4f2be831a1 diag(observer): v30 SCAN 진단 로그 — div textContent 덤프 (v0.5.95) 2026-04-19 15:08:56 +09:00
Variet Worker
cbfd137dcb fix(observer): v30 Running command 추출 — includes 매칭 + raw fallback + 디버그 로그 (v0.5.94) 2026-04-19 15:03:09 +09:00
Variet Worker
a99a1e3f54 docs: 배포 전 체크리스트에 log relay 필터/regex E2E/가정 검증 항목 추가 2026-04-19 14:56:40 +09:00
Variet Worker
ad4ed623bd docs: observer-dev-guide DOM 구조 BTN-DOM-DUMP 기반 갱신 + log relay 필터 규칙 추가 2026-04-19 14:48:42 +09:00
Variet Worker
64800d3c20 fix(observer): 'Running command' div에서 명령어 추출 — pre/code 대신 plain div 탐색 (v0.5.93) 2026-04-19 14:40:05 +09:00
Variet Worker
70c83b4226 fix(observer): log 필터에 CONTEXT/DEFERRED/DETECTED/BTN-DOM 추가 — 진단 로그 relay 누락 수정 (v0.5.92) 2026-04-19 14:10:59 +09:00
Variet Worker
bb54802c06 feat(enrichment): Discord 알림 지연 + Step Probe 폴링 — generic Always run 커맨드 100% 보강 (v0.5.91) 2026-04-19 10:25:55 +09:00
Variet Worker
bf53072f3c feat(enrichment): Step Probe API 메모리 기반 명령어 보강 — Always run 표시 개선 (v0.5.90) 2026-04-19 09:59:20 +09:00
Variet Worker
02b4b03699 diag(observer): Always run 버튼 주변 DOM 구조 원샷 덤프 추가 (v0.5.89) 2026-04-19 09:19:31 +09:00
Variet Worker
db805c6fde fix(observer): matchedType 대소문자 무시 — Always run이 permission으로 잘못 분류되는 문제 수정 (v0.5.88) 2026-04-19 08:01:47 +09:00
Variet Worker
7f33a20e43 docs: 배포 전 자기검증 체크리스트 추가 — 재시작 요구 최소화 정책 2026-04-19 07:58:14 +09:00
Variet Worker
ef788e6ecc docs: Observer 개발 가이드 SSOT 문서 생성 — 제약사항/배포/DOM구조/교훈 종합 2026-04-19 07:52:53 +09:00
Variet Worker
cd00986274 fix(observer): 깨진 문자열 리터럴 2건 수정 — walkNode 크래시 해결 (v0.5.87) 2026-04-19 07:51:29 +09:00
Variet Worker
12095f36a4 fix(observer): function declaration → var expression — strict mode 크래시 수정 (v0.5.86) 2026-04-19 07:41:04 +09:00
Variet Worker
498683c977 fix(approval): _from_ws 파일 60초 TTL 자동 삭제 — stale SKIP 스팸 방지 (v0.5.85) 2026-04-19 07:32:49 +09:00
Variet Worker
1662ac4f6b fix(observer): regex → 문자열 비교로 isGenericDesc 수정 — template literal escaping 회피 (v0.5.84) 2026-04-19 07:02:04 +09:00
Variet Worker
d027562f17 fix(observer): 500ms 딜레이드 컨텍스트 추출 + 버튼 셀렉터 확장 (v0.5.83)
- Always run 감지 시 desc가 generic이면 500ms 딜레이 후 재추출
- 버튼 셀렉터에 role=button, monaco-button, vscode-button 추가
- ACCEPT-SCAN 디버그 로그 (30초 간격)
2026-04-19 06:53:54 +09:00
Variet Worker
cc261011d6 fix(observer): 구 visibility 체크 제거 — Accept all 버튼 감지 차단 수정 (v0.5.81) 2026-04-19 04:18:35 +09:00
Variet Worker
37fbb9657e fix(observer): diff_review Accept all 버튼 감지 — offsetParent 체크 완화 (v0.5.80) 2026-04-19 03:53:03 +09:00
Variet Worker
965f619664 docs: relay-architecture + known-issues 업데이트 (v0.5.78-79 변경사항 반영) 2026-04-19 03:48:31 +09:00
Variet Worker
139ad3ee93 fix(extension): Retry auto-approve 흐름 복구 + Observer 형제 탐색 + thinking 필터링 (v0.5.79)
- WS response 파일에 _from_ws 마커 추가하여 processResponseFile 삭제 방지
- extractContextFromNearby에 sibling 탐색 추가 (AG Native DOM 구조 대응)
- thinking 블록 (max-h-[200px]) 필터링으로 내부 사고 릴레이 차단
- DOM 탐색 depth 5→10 확대 + pre.font-mono 우선 탐색
- 사용자 메시지 셀렉터 (.select-text.rounded-lg) 추가
2026-04-19 03:46:39 +09:00
Variet Worker
08fd08b9a6 feat(observer): diagnostic log relay via HTTP + auto-approve enrichment fallback (v0.5.63) #task-634 2026-04-18 08:18:35 +09:00
Variet Worker
326454be40 fix(bridge): move Always run auto-approve BEFORE filter chain — no more silent drops (v0.5.60) #task-634 2026-04-18 06:54:15 +09:00
Variet Worker
98b7585e3c fix(observer): text-level markdown table wrapping for Discord — AG Native uses divs not HTML tables (v0.5.59) #task-634 2026-04-18 06:46:21 +09:00
Variet Worker
c7f939ce85 fix(bridge): Always run auto-approve now checks buttons array, not just rawCmd (v0.5.58) #task-634 2026-04-18 06:35:01 +09:00
Variet Worker
7a1675fd5d feat(observer): table-to-codeblock conversion for Discord compatibility (v0.5.57) #task-634 2026-04-18 06:25:55 +09:00
Variet Worker
6b9f1188c3 feat(bridge): DOM Markdown parser restoration (v0.5.56) + code noise filter fix + user msg relay #task-634 2026-04-17 08:06:53 +09:00
Variet Worker
13e569f426 chore(bridge): update known-issues and prep for DOM Observer MD restoration (#634) 2026-04-17 06:25:22 +09:00
Variet Worker
b2f17a086b docs: devlog 20260416-005 v0.5.53 Always run auto-approve + index update 2026-04-16 22:08:16 +09:00
Variet Worker
7dbf73aa89 feat(bridge): v17 Always run auto-approve + Retry button relay (v0.5.53) #task-632
- Observer v17: ACTION_WORDS에 'Retry' 추가 — Agent terminated 대화상자의 Retry 버튼 감지/릴레이
- http-bridge: 'Always run' 버튼 자동승인 — response 파일 즉시 생성하여 observer가 바로 클릭
- bot.py: auto_approved 상태 처리 — Discord에 비대화형 '자동 승인' 알림 표시
- Observer matchedType에 'retry' step_type 매핑
2026-04-16 22:03:09 +09:00
Variet Worker
62ee081ffe fix(observer): v16 style/script strip in extractCleanStepText — CSS가 AI 응답으로 Discord 전달되는 버그 수정 (v0.5.52) #task-632 2026-04-16 21:08:52 +09:00
Variet Worker
60a2a97868 docs: E2E 코드 검증 결과 기록 — known-issues #632 업데이트 + devlog 003 2026-04-16 17:17:18 +09:00
Variet Worker
7ae43088e6 docs: devlog 20260416-002 AG Native chat relay + index update 2026-04-16 05:30:20 +09:00
Variet Worker
729875f3a6 feat(observer): v15 AG Native chat relay — scanChatBodies dual strategy (#632)
- Add AG Native DOM path: #conversation + .leading-relaxed.select-text
- Keep Cascade path: [data-testid=conversation-view] + [data-step-index]
- Register #632 in known-issues.md (SDK+DOM both blocked for AG Native)
- Bump version 0.5.50 → 0.5.51
- Add DOM analysis helper scripts
2026-04-16 05:28:44 +09:00
Variet Worker
a00d561e28 docs: devlog 2026-04-16 + known-issues v0.5.50 terminal output filter 2026-04-16 04:59:27 +09:00
Variet Worker
7ade31e4cf fix(bridge): v16 terminal output filter + v15 stale LS auto-fix + heartbeat probe (v0.5.50) #task-619
- http-bridge v16: Block terminal OUTPUT as enriched cmd — if description has no prompt marker (> » $ #), it's stdout from code block, not an actual command. Prevents 'No extension.log found' etc. from reaching Discord.
- step-probe v15: Stale LS auto-detection — if all sessions are >5min old, periodically retry fixLSConnection(). Heartbeat probe every 10 polls to detect step changes when summary API returns frozen stepCount.
- extension.ts v15: fixLSConnection() fallback — match LS processes without --workspace_id (common after AG restart)
2026-04-16 04:58:05 +09:00
Variet Worker
66233bd9cb fix(bridge): strengthen JUNK_CONTENT_RE — add CSS block, .code-block/.code-line, integration.build patterns
Unit tested: 12 legit commands pass, all junk patterns now filtered.
HTML manually patched to v14 (bypasses extension host cache).
2026-04-15 15:37:09 +09:00
Variet Worker
ed90cbf874 fix(observer/bridge): v14 — strict 5-level DOM scope, CSS/code/icon junk filter, auto-version sync (v0.5.47) #task-619
Root causes fixed:
1. extractContextFromNearby depth 20→5 — stops grabbing unrelated UI/editor code
2. JUNK_CODE_RE — rejects CSS rules, JS source code, extension internals
3. ICON_GLUE_RE — rejects Material icon text glued with content
4. Fallback span/div/p collection REMOVED entirely (always grabbed chat text)
5. html-patcher strips old observer from integration.build() cache
6. http-bridge server-side JUNK_CONTENT_RE as last line of defense
2026-04-15 14:55:58 +09:00
Variet Worker
2e32be96fe fix(observer/bridge): v13 _promptOnlySkipped fallback guard + generic button no-context filter (v0.5.46) #task-619 2026-04-15 13:27:09 +09:00
Variet Worker
87c99c7243 docs: devlog 2026-04-15 + scratch cleanup
- devlog entry: PROMPT_ONLY_RE 근본원인 분석 및 수정 (v0.5.45)
- scratch files: 이전 세션 디버깅 스크립트 전체 정리
2026-04-15 09:59:22 +09:00
Variet Worker
01539e9bfb fix(observer/bridge): PROMPT_ONLY_RE — regex escaping + ellipsis prefix handling (v0.5.45) #task-619
Observer: \\\\s (4-backslash) → \\s (2-backslash) in template literal regex
  - Per known-issues rule: regex literals need 2-backslash for \s, \d etc.
  - Old: literal \\s matched, new: whitespace class matched correctly

http-bridge: /^[\s\\\/]*[\w_.-]+\s*[>»$#]\s*$/ → /^.*[>»$#]\s*$/
  - Old pattern failed on ellipsis '…' prefix (U+2026 not in charset)
  - Terminal prompt '…\\gravity_control >' was passing through as cmd
  - New pattern: any text ending with prompt marker is skipped
2026-04-15 09:53:32 +09:00
Variet Worker
4684376c78 fix(http-bridge): remove incorrect extracted===rawDesc skip in enrichment (v0.5.44) #task-619 2026-04-15 07:47:56 +09:00
Variet Worker
7ee5947b32 fix(observer): v12 — skip prompt-only code text + enrichment validation (v0.5.44) #task-619
- extractContextFromNearby: PROMPT_ONLY_RE skips code elements containing only terminal prompts (e.g. '\\gravity_control >')
- Multi-codeEl traversal: tries all code elements at each depth, picks longest non-prompt text
- http-bridge enrichment: validates extracted command is not just a prompt fragment before enriching
- Fixes: cmd='\\gravity_control >' (prompt-only) no longer sent to Discord as the command text
2026-04-15 07:43:15 +09:00
Variet Worker
59ddcbb612 fix(http-bridge): move command enrichment before Run filter — fixes context loss (v0.5.43) #task-619 2026-04-15 06:54:06 +09:00
Variet Worker
ed63f65975 feat(observer): v11 — enhanced context extraction + 5-level sibling search + command enrichment (v0.5.42) #task-619
- extractContextFromNearby: span/div/p text fallback when pre/code not found (depth 0-8)
- collectSiblingButtons: extended to 5 parent levels, stop only when both action+reject found
- http-bridge: command enrichment — swap generic button text with actual command from description
- Fixes: cmd='Always run' now shows actual command text, Cancel button detection improved
2026-04-15 06:20:47 +09:00
Variet Worker
02d9799f20 diag(observer): v10-diag — extractContextFromNearby trail logging for context extraction failure analysis #task-619 2026-04-14 07:38:27 +09:00
Variet Worker
a9c64e6716 docs: known-issues — template literal regex escaping bug (v0.5.41) 2026-04-14 06:06:23 +09:00
Variet Worker
8e89209a27 fix(observer): template literal regex escaping — \s/\d had wrong backslash count (v0.5.41)
Root cause: regex literals inside template literal (\...\) need exactly 2
backslashes (\\s, \\d) to produce \s, \d in the output. The v9 code had 4
backslashes (\\\\s) which produced literal \\s in the browser (matching
backslash+s instead of whitespace class). This broke:
- 'Running N commands' skip pattern (never matched)
- NOISE_CODE_RE (declare/import line filters)
- cleanLines() pre-strip word boundary \\b and split/join \\n
- cleanButtonText() whitespace strip \\s

The fix restores all regex-literal patterns to exactly 2 backslashes.
new RegExp() string patterns (like NOISE_RE) already used 4 backslashes
correctly (string→regex has one extra escaping layer).

Verified: node eval of generated code confirms /^Running\s*\d+\s*commands?$/i
correctly matches 'Running2 commands' and 'Running 4 commands'.

#task-619 #task-620
2026-04-14 06:05:43 +09:00
Variet Worker
a8d875167d fix(observer): v9 - stop treating Running N commands as approval button, add DOM-climbing context extraction 2026-04-13 19:37:18 +09:00
Variet Worker
2a1ebf1020 fix(extension): UTF-8 encoding + noise filter enhancement (v0.5.39)
- http-bridge.ts: add req.setEncoding('utf8') to all POST handlers
  to fix Korean text corruption in pending/chat/dump payloads
- observer-script.ts: add inline pre-strip in cleanLines() for
  Material icon names concatenated by textContent without newlines
- observer-script.ts: apply cleanLines() to codeText extraction
- known-issues: document UTF-8 encoding and noise filter issues
2026-04-13 12:56:25 +09:00
Variet Worker
5a76e30993 docs: known-issues — html-patcher String.replace dollar-pattern corruption bug 2026-04-13 12:22:41 +09:00
Variet Worker
d6ed8764b8 fix(html-patcher): escape $ in inline script replacement to prevent String.replace() special pattern corruption — root cause of observer SyntaxError #task-619 2026-04-13 12:22:02 +09:00
Variet Worker
a214ab029f docs: devlog — observer v8 verification session, V8 cache re-cleared 2026-04-13 11:02:26 +09:00
Variet Worker
f45d2d970d fix(observer): ensure inline script injection before </body> for Electron execution + add BEACON diagnostic ping
- html-patcher: relocate inline script from after </html> to before </body>
- html-patcher: clean up duplicate </html> tags from previous bad insertions
- observer-script: add immediate BEACON fetch to /ping on script load
- http-bridge: add diagnostic request logging for non-polling endpoints
- devlog 003: crash recovery session notes
2026-04-12 21:30:58 +09:00
Variet Worker
6dc0854c47 docs: devlog 003 — observer v8 full-DOM dump 2026-04-12 07:37:52 +09:00
Variet Worker
0e03b3a300 feat(observer): v8 full-DOM unconditional dump — body tree capture at 5s/15s/60s with depth 15, indexed dump files, deep-inspect overhaul #task-619 2026-04-12 07:37:25 +09:00
Variet Worker
353265bed8 docs: AG Native bundle reverse engineering analysis — plannerResponse/Whi renderer structure, V8 cache fix, known-issues update 2026-04-12 07:05:57 +09:00
Variet Worker
eef59e6bb2 docs: devlog entry for AG Native DOM parser v7 rewrite session 2026-04-12 06:15:47 +09:00
Variet Worker
a4d7286bce refactor(observer): v7 step-aware AG Native DOM parser with data-testid/data-step-index based content extraction
- Replace CSS class-based scanning with [data-testid='conversation-view'] + [data-step-index] traversal
- New extractCleanStepText(): clone-and-strip buttons/SVG/icons before text extraction
- New extractStepContext(): step-container-aware context with header + code block
- NOISE_RE: block Material icon names, button labels, UI artifacts
- Auto DOM structure dump on first conversation-view detection
- Enhanced deep-inspect with step element + button inventory
- known-issues: document AG Native SDK API incompatibility
2026-04-12 06:14:46 +09:00
Variet Worker
70dc301dca fix(bridge): isolate DOM observation scope and strip UI noise (TypeScript declarations, metrics) from Discord pending approval embeds 2026-04-11 17:25:33 +09:00
Variet Worker
7630bf1f8c chore: Add jump_url logging and plaintext fallback to approval messages to trace silent Discord drops 2026-04-11 15:45:21 +09:00
Variet Worker
ec7883755a fix(bot): remove lingering bridge dependency triggering AttributeError on pending relay 2026-04-11 13:21:02 +09:00
Variet Worker
072f83bf25 refactor(bridge): migrate gravity bridge to pure websocket gateway architecture, deleting legacy local file scanners and dependencies 2026-04-11 13:06:38 +09:00
Variet Worker
5e697cd919 Fix DOM observer regex/container bugs and add continuous AI Chat Body scraper via HTTP Bridge 2026-04-11 00:08:55 +09:00
Variet Worker
b3825e1c8a fix(extension): skip Antigravity ghost sessions in Fallback 2 to prevent trajectory not found infinite loop 2026-04-10 22:19:09 +09:00
Variet Worker
a99c283656 fix(extension): restore AI Response Content capture by patching DOM extraction, CSP connect-src, and TS regex literal serialization 2026-04-10 21:10:33 +09:00
Variet Worker
58887f6933 chore: bump version to 0.5.23 for vsix 2026-04-10 17:39:07 +09:00
Variet Worker
488b36f192 fix(step-probe): ensure fast AI responses and tool calls are captured by real-time block 2026-04-10 17:12:21 +09:00
Variet Worker
300338d5d3 fix(extension): bypass 10-item limit of GetAllCascadeTrajectories by utilizing GetDiagnostics 2026-04-10 16:52:12 +09:00
Variet Worker
e745744636 fix(Backend): Gravity Bridge response extraction & bot exception crash loop
* Restore step.content.parts traversal missing in prior bugfix
* Catch wide exceptions in bot.py chat_snapshot_scanner and move broken files to .json.failed to prevent loop aborts blocking the pending queue
2026-04-10 16:33:02 +09:00
Variet Worker
b88e75b075 fix(bridge): eliminate discord empty embed spam by disabling DOM observer proactive pending
- removed PATS in observer-script.ts
- step-probe.ts now handles 100% of pending detection with full RPC payload context
- DOM observer restricted to trigger-click polling only
2026-04-10 16:12:15 +09:00
Variet Worker
2ece05fc6f fix(extension): resolve AI response dropping for sub-5s executions by relaxing IDLE capture condition #task-607 2026-04-10 16:05:30 +09:00
Variet Worker
6bbc9ddd00 fix(extension): resolve AI response dropping by adding nested payload extraction in step-utils 2026-04-10 15:52:25 +09:00
Variet Worker
89c95de18c fix(bridge): resolve missing Discord embed bodies by extracting detailed RPC payload from step_probe 2026-04-10 08:02:41 +09:00
Variet Worker
fadd39424b fix(bridge): fix Discord signal relay false-positives and empty body logic 2026-04-10 00:12:01 +09:00
Variet Worker
22e1799d66 fix(extension): resolve Native UI icon text gluing causing DOM observer signal drop #task-603 2026-04-09 23:13:49 +09:00
Variet Worker
e4f674ec9f docs: restore unintentionally deleted known issues (mend destructive commit) 2026-04-09 22:36:50 +09:00
Variet Worker
47c0602427 fix(extension): pin point CodeLens exclusion filter to prevent native Agent UI freezing (v0.5.22) #task-602 2026-04-09 22:32:40 +09:00
Variet Worker
75762964e3 fix(extension): adapt DOM observer to Native Agent panel and Tailwind migration (v0.5.21) 2026-04-09 21:56:29 +09:00
Variet Worker
d2023321bd fix(extension): remove redundant SafeToAutoRun chat snapshot for Discord relay
* Removed writeChatSnapshot calls in step-probe.ts to prevent duplicate ' 자동 실행됨' notifications since bot.py already broadcasts '🤖 자동 승인됨'.
* Docs: Update devlog and known-issues with Discord Bot cache deletion bugs and multi-workspace LS connection conflicts.
* Fixes Vikunja #593
2026-04-08 17:59:40 +09:00
Variet Worker
2eb1fbb6b7 fix(pipeline): resolve SafeToAutoRun deadlock and sync freezing (v0.5.20) (#589) 2026-04-08 07:30:33 +09:00
Variet Worker
13f13ee243 fix(extension): resolve 10-item limit truncation & WS zombie disconnection (v0.5.14) 2026-04-01 18:21:51 +09:00
Variet Worker
2d5059d2d5 chore(ext): version bump 0.5.11 2026-03-28 09:21:10 +09:00
Variet Worker
7bbd8749d7 fix(extension): guitar_score step-probe UTF-8 loop + approval stepIndex guard (v0.5.11) 2026-03-28 09:15:11 +09:00
Variet Worker
d5fdc41f35 fix(extension): Discord signal drop and UI freeze (async IO, regex filters, WS rate-limits) (v0.5.10) 2026-03-25 07:14:34 +09:00
Variet Worker
3ec45ac6b7 docs(devlog): record hash and Vikunja ID for session 001 and 003 2026-03-24 18:19:30 +09:00
Variet Worker
101ec20b21 fix(extension): restructure DOM observer to prevent false positive triggers (v0.5.10) 2026-03-24 18:15:05 +09:00
Variet Worker
86e5a24a75 docs(devlog): record hash and Vikunja ID for session 002 2026-03-24 14:04:04 +09:00
Variet Worker
7b6cd59801 fix(extension): support vscode native notification UI and Always Allow buttons for DOM observer (#514) 2026-03-24 13:58:21 +09:00
Variet Worker
f13bcc871c fix(ext): v0.5.8 false positive zombie socket disconnect bug resolve (timestamp replace setTimeout) 2026-03-24 07:00:43 +09:00
Variet Worker
ecebec3906 fix(bridge): resolve websocket zombie connection and bounding memory leaks 2026-03-23 21:11:52 +09:00
Variet Worker
e21f71baf8 docs: devlog 2026-03-22 VSIX v0.5.5 빌드 2026-03-22 09:15:27 +09:00
Variet Worker
b81135d855 chore(ext): version bump 0.5.5 2026-03-22 01:23:09 +09:00
Variet Worker
a6aa643be9 docs: devlog #2 wrong-LS auto-recovery (v0.5.5) 2026-03-21 21:16:49 +09:00
Variet Worker
6234301a47 fix(ext): v0.5.5 wrong-LS 자동 복구 — fixLSConnection export + 'input not registered' 감지 시 LS 재연결 + 1회 retry 2026-03-21 21:15:18 +09:00
Variet Worker
a72c522ab5 fix(extension): v0.5.4 신호 감지 3중 버그 수정 — 세션 전환 즉시 probe, reviewAbsoluteUris 필드, stepIndex uint32 clamp + permission 매핑 2026-03-21 17:51:10 +09:00
Variet Worker
f4ded343c7 docs: devlog commit hash update + known-issues idle-resume 3-bug (v0.5.2) 2026-03-21 10:58:09 +09:00
Variet Worker
5aad82c727 fix(ext+hub): v0.5.2 Idle→Resume 신호 소실 3중 버그 수정 — auth_fail 재연결 + pending_owners 보존 + step-probe 리셋 2026-03-21 10:51:02 +09:00
Variet Worker
94cbda6f3d docs: devlog #1 2026-03-19 + known-issues browser_subagent Allow 추가 + rule #12 2026-03-21 10:16:35 +09:00
Variet Worker
549af6dae2 fix(ext): browser_subagent Allow 버튼 RPC 매핑 수정 — runExtensionCode payload 적용 (v0.5.1) 2026-03-20 18:13:07 +09:00
Variet Worker
e306fae130 docs: devlog #4 — codebase health + communication audit (no changes needed) 2026-03-18 15:52:19 +09:00
Variet Worker
bc9d0f2fbb docs: devlog #3 — step-probe module split + Vikunja #414 done 2026-03-18 14:35:22 +09:00
Variet Worker
17978a750c refactor(ext): split step-probe.ts → approval-handler.ts (1597→1017+411 lines) #task-414 2026-03-18 14:34:32 +09:00
Variet Worker
0f057c0c95 docs: devlog #2 — bot unit tests + Vikunja #410 done 2026-03-18 14:06:25 +09:00
Variet Worker
a41062b6ff test(bot): bot.py unit tests — 27 cases for _write_command, _hub_on_pending, ApprovalView #task-410 2026-03-18 14:05:39 +09:00
Variet Worker
029a246658 docs: devlog 2026-03-18 커밋 해시 업데이트 2026-03-18 13:24:21 +09:00
Variet Worker
e7631177f8 refactor(cleanup): v0.5.0 Collector 제거 + dead code 정리 + HttpBridgeContext 버그 수정
- DELETE collector.py (523줄)
- main.py: BOT_MODE=remote 분기 제거
- gateway.py: Collector REST 6개 endpoint 제거 (311→168줄)
- bridge.py: RemoteTransport 제거 (480→270줄)
- config.py: REMOTE_BRIDGE_URL 제거
- extension.ts: dead code 4개 + stale module vars 제거
- step-probe.ts: getStepProbeContext() 추가, autoApproveEnabled 제거
- FIX: HttpBridgeContext stale primitive (getter 패턴으로 수정)
- ADD: extension.log rotation (10MB→2MB tail)
- docs: architecture.md, tech-stack.md, known-issues.md 업데이트
2026-03-18 11:08:59 +09:00
Variet Worker
4a5521dcc3 docs: devlog #006 + known-issues !stop stale primitive update #task-410 2026-03-18 09:20:07 +09:00
Variet Worker
ab0c116c9e fix(ext): !stop getActiveSessionId stale primitive — use step-probe getter #task-410 2026-03-18 08:34:58 +09:00
Variet Worker
07bbb626a6 docs: devlog #005 + known-issues !stop root cause update + Vikunja #411 done 2026-03-18 08:22:10 +09:00
Variet Worker
d55b6b97ad fix: stop command uses activeSessionId instead of renderer-only getActiveCascadeId #task-411 2026-03-18 08:09:29 +09:00
Variet Worker
d8eac80b2f fix(ext): !stop CancelCascadeInvocation RPC — AG 빨간■ 동일 메커니즘 적용 #task-411 2026-03-18 07:16:57 +09:00
Variet Worker
759dab55b6 fix(ext): !stop 핸들러 SDK cancelCurrentTask() 교체 — rejectAgentStep 미등록 이슈 해결 #task-411 2026-03-18 06:49:17 +09:00
Variet Worker
bbfafdc5e4 docs: rejectAgentStep 조사 결과 — CancelCascadeInvocation RPC 대안 발견 #task-411 2026-03-18 06:46:26 +09:00
Variet Worker
ac803d436f test(hub): 45개 단위 테스트 추가 — 연결 관리, pending_owners, 라우팅, 인증 #task-412 2026-03-18 06:42:51 +09:00
Variet Worker
ebf2228aa8 docs: known-issues 정리 + Vikunja #410~#414 태스크 등록 반영 2026-03-18 06:38:05 +09:00
Variet Worker
881a424b23 docs: known-issues 아카이빙 + Collector 폐기 마킹 + 레퍼런스 문서 보강 #task-409 2026-03-18 06:28:40 +09:00
Variet Worker
d06b1ea0db docs: usage-guide WS Hub 아키텍처 업데이트 + start_bot.bat Collector 경고 추가 2026-03-17 22:06:52 +09:00
Variet Worker
48ae19b3e1 docs: known-issues pending_owners lifecycle + devlog #017 2026-03-17 21:56:43 +09:00
Variet Worker
9ccfa83439 fix(hub): reassign pending_owners on WS reconnect — prevents approval response loss 2026-03-17 21:52:50 +09:00
Variet Worker
0fae7e32aa fix(ext,bot): 통신 아키텍처 감사 — writeRegistration 이중쓰기 + ApprovalView fallback + scanner 최적화
- step-probe.ts: writeRegistration WS 후 return 추가 (파일 이중쓰기 방지)
- bot.py: ApprovalView approve/reject/choice — send_response_to_pending_owner 반환값 확인 + file bridge fallback (5곳)
- bot.py: scanner 주기 3s/5s → 30s (Hub 모드 불필요 I/O 감소)
2026-03-17 21:30:05 +09:00
Variet Worker
47cc838d9d fix(ext,bot): Accept All WS regression + auto_approve dual-write — VSIX v0.4.5 2026-03-17 21:01:24 +09:00
Variet Worker
4e8ac8d6b7 docs: known-issues dual-delivery + devlog #013-014 2026-03-17 20:39:21 +09:00
Variet Worker
0da6291d98 chore(extension): bump to v0.4.4 - dual delivery fix + echo dedup 2026-03-17 20:36:27 +09:00
Variet Worker
4bb400820c fix(command-handler): add echo-dedup for WS commands to prevent Discord relay 2026-03-17 20:34:55 +09:00
Variet Worker
302d21d35c fix(bot,extension): prevent dual delivery of commands and responses via WS+file 2026-03-17 20:30:37 +09:00
Variet Worker
6640d42449 refactor(extension): split extension.ts into 3 modules - http-bridge, html-patcher, command-handler (#398) 2026-03-17 18:50:12 +09:00
Variet Worker
1ce8b7c707 docs: devlog #012 + known-issues WS 응답 라우팅 2026-03-17 17:47:21 +09:00
Variet Worker
2eea5fa638 fix(ext): WS response → tryApprovalStrategies 직접 호출 (파일 경유 제거) 2026-03-17 17:43:45 +09:00
Variet Worker
adbed69237 docs: devlog #012 final + known-issues ApprovalView WS 2026-03-17 17:08:15 +09:00
Variet Worker
442221e6a3 fix(bot): ApprovalView Hub WS 응답 라우팅 — Discord 승인이 Extension에 전달 안 되는 근본 원인 2026-03-17 14:53:22 +09:00
Variet Worker
50efd52f41 docs: devlog #012 update + known-issues ApprovalRequest 누락 필드 2026-03-17 14:30:35 +09:00
Variet Worker
f6181e552d fix(bot): ApprovalRequest missing conversation_id + timestamp in Hub path 2026-03-17 13:20:00 +09:00
Variet Worker
1bb54eb820 docs: devlog #012 + known-issues 3건 + VSIX v0.4.3 빌드 아티팩트 2026-03-17 10:48:09 +09:00
Variet Worker
9523d1328e fix(ext): workspaceUri 누락 + WS-only 전송 + user msg dedup 2026-03-17 10:38:45 +09:00
Variet Worker
96e9b8adce fix(bot): Hub WS auto-approve Discord 알림 누락 + !auto 이중발송 dedup 2026-03-17 10:37:55 +09:00
Variet Worker
edd4943e2e chore(extension): ws 모듈 번들 + E2E 사전 검증 #task-396
- extension/package.json: ws dependency 추가
- extension/.vscodeignore: !node_modules/ws/** 추가 (VSIX 번들)
- known-issues: NPM WS 프록시 + ws 모듈 미번들 이슈 추가
- devlog: #010 완료, #011 E2E 사전 검증 (미완료)
2026-03-17 08:21:43 +09:00
Variet Worker
6ea3211a58 docs: devlog #010 - 문서 재작성 + 서버 배포 + WS 호환 2026-03-17 07:42:55 +09:00
Variet Worker
b9b240de0b fix(extension): ws-client browser WebSocket API compat (.onopen/.onmessage) 2026-03-17 07:41:56 +09:00
Variet Worker
36b70505d7 docs: .env.example Hub 인증 변수 추가 2026-03-17 07:20:19 +09:00
Variet Worker
5bdaba01bd fix(infra): docker-compose.yml 서버 실제 구성 반영 + Caddyfile 제거 2026-03-17 07:18:57 +09:00
Variet Worker
28d399ba91 infra: Caddyfile ag.variet.net + docker-compose Hub env vars + Extension hubUrl 설정 2026-03-17 07:09:46 +09:00
Variet Worker
fadfd88f51 docs: architecture/tech-stack/conventions 전면 재작성 + Wiki 동기화 2026-03-17 06:48:46 +09:00
Variet Worker
61bd4b1ffb docs: devlog 009 hash update 2026-03-17 06:42:45 +09:00
Variet Worker
5f795b9a91 refactor(extension): 모듈 분리 + Hub 통합 테스트 #task-395
- extension.ts 3,446→1,289줄 (-63%)
- step-probe.ts (1,435줄): setupMonitor, processResponseFile, tryApprovalStrategies
- observer-script.ts (687줄): DOM observer script
- ws-client.ts (390줄): WSBridgeClient
- step-utils.ts (114줄): step 파싱 유틸
- auth.py (115줄): JWT + registration code
- hub.py (581줄): WSHub + per-client queue
- Hub WS 연동 테스트 통과 (auth, chat, register)
- VSIX v0.4.0 빌드
2026-03-17 06:41:42 +09:00
Variet Worker
a372bd8b2d docs: session end — known-issues 3건 (cross-project flooding, pending 누적, diff_review brain/) + devlog #008 2026-03-16 23:08:31 +09:00
Variet Worker
e3f8fb93f7 fix: cross-project event flooding + pending accumulation + diff_review brain exclusion
Phase 1: Collector auto-cleanup of auto_resolved/expired pending files after Gateway forwarding
Phase 2: Watcher project filter (only MY sessions emit events) + Collector event forward filter
Phase 3: Extension diff_review excludes brain/ artifact files (task.md, implementation_plan.md)
2026-03-16 23:05:27 +09:00
Variet Worker
7ca0bc0f1f docs: session end — known-issues 2건 (병렬 step 누락, snapshot 로깅) + devlog #007 2026-03-16 20:42:04 +09:00
Variet Worker
7f079a56a0 fix: process ALL parallel WAITING steps instead of only first one
step_probe break statement caused only one WAITING step to get
a pending file when AG runs multiple parallel tool calls.
Now iterates all WAITING steps and creates pending for each.
2026-03-16 20:36:41 +09:00
Variet Worker
fdc0084813 fix: add chat snapshot delivery success/failure logging 2026-03-16 20:22:49 +09:00
Variet Worker
f309518e78 fix: add channel failure logging to diagnose Discord notification delivery issue 2026-03-16 19:47:06 +09:00
Variet Worker
412c212c6e fix(extension): v0.3.16 — diff_review duplicate approval filter + IDLE notification + !auto echo removal 2026-03-16 19:14:43 +09:00
Variet Worker
0035394b9c docs: session end — known-issues update + devlog #005 (v0.3.15 diff_review fix) 2026-03-16 18:46:07 +09:00
Variet Worker
0fdf668abc fix(extension): diff_review use agentAcceptAllInFile instead of dead RPC strategies (v0.3.15) 2026-03-16 18:43:04 +09:00
Variet Worker
5a1d4f0b0c fix(extension): acknowledgeCodeActionStep RPC discovery + v0.3.14 3-tier strategy 2026-03-16 18:11:20 +09:00
Variet Worker
82461bc3fc fix(extension): diff_review RPC parameter experiment — 4 format variants (A/B/C/D) + known-issues update 2026-03-16 16:59:58 +09:00
Variet Worker
9ef2c3f07c fix(extension): diff_review steps=[] race condition — in-memory metadata cache (v0.3.13)
Root cause: Collector deletes pending file before Extension reads edit_step_indices.
Fix: diffReviewMetadata Map caches step indices in Extension memory.
Known issue added. Devlog entry 003.
2026-03-16 16:09:42 +09:00
Variet Worker
12a1cf8692 docs: update devlog hash 2026-03-16 14:23:38 +09:00
Variet Worker
f302984721 fix(extension): diff_review 2-strategy deploy + 8s pending delay
- Deploy AcknowledgeCascadeCodeEdit RPC strategy (was in source but never compiled)
- Add 8s setTimeout delay for diff_review pending (AI response arrives on Discord first)
- Capture closure variables for delayed pending creation safety
- known-issues: diff_review pending ordering fix
2026-03-16 14:22:41 +09:00
Variet Worker
15f6a743a4 docs: update devlog hash 2026-03-16 13:53:23 +09:00
Variet Worker
d521dd5fa3 fix(extension): step_type mapping bug + diff_review handler refactor
- Separate read tools (file_permission) from write tools (code_edit)
- write_to_file/replace_file_content now use AcknowledgeCascadeCodeEdit RPC
- diff_review: 2-strategy approach (RPC first, openReviewChanges fallback)
- Track modified_files and edit_step_indices in diff_review pending
- known-issues: 3 new entries (pending accumulation, step_type bug, isDirty failure)
2026-03-16 13:52:02 +09:00
Variet Worker
078f721187 docs(devlog): update 2026-03-16 commit hash 2026-03-16 11:12:44 +09:00
Variet Worker
2d9fe964f6 fix(bridge): v0.3.12 approval state management — sawRunningAfterPending gate + approval-flow.md system doc
- processResponseFile: set sawRunningAfterPending=true instead of removing resets
  (prevents infinite pending loop AND known-issues L479 auto_resolve regression)
- Hoist sawRunningAfterPending to module level for cross-function access
- Add recentPendingSteps memory dedup Map (60s TTL) for file-deletion resilience
- Create docs/approval-flow.md: complete system flow guide with state diagram
- Update known-issues.md: 2 new entries (state reset fix, memory dedup)
2026-03-16 11:11:50 +09:00
Variet Worker
37c0aae41c fix(bot): multi-project signal freeze — cache-only _get_channel + per-tick scanner cap
Root cause: When 3+ projects generated pending simultaneously, Bot's
pending_approval_scanner made 20-40 Discord API calls in one tick
(sequential await), triggering Discord 429 rate limits which blocked
the entire scanner for 10-30s, freezing ALL signal delivery.

Two fixes:
1. _get_channel(): Replace guild.fetch_channels() (API call) with
   discord.utils.get(guild.channels) (in-memory cache). Eliminates
   redundant API calls + Lock contention when multiple projects arrive.
2. pending_approval_scanner: Per-tick caps (5 new + 5 status) prevent
   one tick from monopolizing Discord API quota. Excess items are
   naturally processed in subsequent 3-second ticks.
2026-03-16 07:06:51 +09:00
CD
64f80212c3 docs(devlog): add entries 007-008 for system audit and agent rules 2026-03-15 23:28:28 +09:00
CD
9b93ee9776 docs(agent): add anti-local-thinking rules — NEVER #10 strengthen (disprove before report), NEVER #11 (no mechanical apply), ALWAYS #9 (project history cross-ref), Bug Report Protocol 2026-03-15 23:26:54 +09:00
CD
c9f44afcf1 fix(bridge): system audit + 5-file bug fix — PATS Deny trigger removal, auto_resolved chat dedup, UUID filenames, IP rate limit leak, bot.py deque 2026-03-15 23:01:20 +09:00
CD
429cae47b7 docs: session end — known-issues + devlog + VSIX v0.3.11 deployed 2026-03-15 18:53:00 +09:00
CD
5e5f515db4 fix(bridge): auto-approve crash — DOM observer Deny filter + bot reject-word guard + AGENT rule 2026-03-15 18:49:58 +09:00
CD
6739f8f30c fix(bridge): v0.3.11 approval flow architecture fix — eliminate double-fire auto-approve, strip 30+ failed RPC strategies, add project_name DEDUP guard
- Remove Extension-side auto-approve (was double-firing with Bot auto-approve)
- Strip failed strategies 0A-1 from tryApprovalStrategies (~150 lines)
- Keep only Strategy 0-PROTO (proto RPC) + Strategy 2 (clickTrigger)
- Add bot.py AUTO-RESOLVED logging for diagnostics
- Update known-issues with 3 new entries
- Clean deployment: v0.3.8→v0.3.10→v0.3.11
2026-03-15 17:11:38 +09:00
CD
75289b3ec5 docs: update devlog with perf optimization entry + compiled output 2026-03-15 10:54:24 +09:00
CD
ae0509fbb5 perf(bridge): 3 optimizations — pollResponseGroup 1500ms, renderer adaptive idle, Bot single-pass scanner 2026-03-15 10:51:22 +09:00
CD
f96203646e fix(bridge): 4 race condition fixes for approval lifecycle 2026-03-15 10:44:31 +09:00
CD
c910c7c386 docs: add v0.3.10 entry to devlog 2026-03-15 08:31:47 +09:00
CD
10caae1506 chore(extension): bump version to 0.3.10 2026-03-15 08:27:38 +09:00
CD
28975f9c4b docs: update devlog commit hash 2026-03-15 08:19:26 +09:00
CD
40e3cd550f fix(bridge): 5 bug fixes for approval signal drop and Discord relay
- DEDUP: add conversation_id guard to prevent cross-session step_index collision
- step_probe: suppress pending when projectName=default (empty window)
- watchCommandsDir: add 3s polling fallback (fs.watch silent fail on Windows)
- auto toggle: write chat_snapshot confirmation back to Discord
- bot on_message: add message ID dedup for Gateway event replay
2026-03-15 08:18:26 +09:00
Variet Worker
1f96997831 docs: devlog 2026-03-13 — Collector 성능 최적화 2026-03-13 23:21:58 +09:00
Variet Worker
d4a2016d06 perf(collector/watcher): 로컬 데이터 전송 성능 최적화 — mtime 프리체크, 프로젝트 캐시, re-forward 수정, 폴링 간격 조정 2026-03-13 23:13:18 +09:00
Variet Worker
1835166c5a docs: devlog 2026-03-13 — Discord 아티팩트 알림 개선 2026-03-13 09:47:50 +09:00
Variet Worker
e5a05e3ac4 feat(bot/extension/watcher): Discord 아티팩트 알림 개선 — 파일 첨부 전송, truncation 확대, 동적 .md 감시 2026-03-13 09:46:56 +09:00
Variet Worker
9036f1cefc docs: devlog #005 + known-issues — rate limit 구조적 수정 기록 2026-03-12 23:06:15 +09:00
Variet Worker
56de71470d fix(collector/bridge/gateway): rate limit 구조적 수정 — 점진적 백오프 + adaptive 폴링 + burst-friendly 윈도우 2026-03-12 22:33:49 +09:00
Variet Worker
5cdf7777a5 docs: devlog #004 업데이트 — throttle fix 커밋 추가 2026-03-12 21:15:01 +09:00
Variet Worker
bcc29f9331 fix(collector): add 0.3s throttle between multi-project command polls to prevent rate limit bursts 2026-03-12 21:11:05 +09:00
Variet Worker
5a4ac1bf9b docs: devlog #004 + known-issues — Collector multi-project polling bug 2026-03-12 20:54:23 +09:00
Variet Worker
ae51d28857 fix(collector): multi-project command polling via bridge/register/ discovery 2026-03-12 20:37:23 +09:00
Variet Worker
71f2a269f0 docs: devlog #003 추가 — workbench.html 크로스 복원 CSS 수정 (직전 세션 기록 복구) 2026-03-12 18:09:52 +09:00
Variet Worker
6d8c6f182c fix(extension): HTML 패치 안전성 강화 — pre-patch backup + 구조 검증 + 자동 복원 2026-03-12 17:55:42 +09:00
Variet Worker
bb6c03e957 docs: devlog 002 커밋 해시 업데이트 2026-03-12 17:08:00 +09:00
Variet Worker
a9feee6faa fix(extension): workbench.html 0-byte 파괴 방지 — pre-read/pre-write 안전 가드 추가 2026-03-12 17:05:29 +09:00
Variet Worker
52c9526fdb fix(bridge): 429 Rate Limit 무한 루프 방지 — 지수 백오프 + Collector 폴링 보호 + rate limit 완화 2026-03-12 00:50:29 +09:00
Variet Worker
feb8c05a73 chore: 테스트 파일 삭제 2026-03-12 00:02:13 +09:00
Variet Worker
3fcf4f7037 docs: 세션 종료 — AGENT.md 검증 규칙 + known-issues 3건 + devlog
AGENT.md:
- NEVER #8-9: 실제 E2E 테스트 없이 '구현 완료' 금지
- ALWAYS #7-8: 실행 검증 필수, '구현'과 '검증' 구분 보고

known-issues 3건:
- Collector 동기→aiohttp 전환 기록
- Extension fs.watch response 감지 누락 (미해결)
- rejectAgentStep 미등록 (미해결)
2026-03-12 00:00:30 +09:00
Variet Worker
d7ed454332 fix: 나노 검증 — health URL/response polling/startup 상태변경 3건 수정
1. health_check: /../health → /health (URL 해석 오류)
2. response polling: startup pending 제외 (불필요한 HTTP 요청 방지)
3. startup pending 상태변경: pending→skip, auto_resolved/expired→forward
2026-03-11 23:01:24 +09:00
Variet Worker
1bf41ceee3 refactor: 아키텍처 수정 — 동기HTTP→aiohttp + 연결 모니터링 + 재시도큐
#1 동기 HTTP → async aiohttp (Critical)
  - RemoteTransport: urllib.request → aiohttp.ClientSession
  - 모든 HTTP 요청이 non-blocking으로 전환
  - 이벤트 루프 블로킹 문제 해결

#2 연결 상태 모니터링
  - RemoteTransport: connected 플래그 + 연속 실패 카운터
  - Collector: 30초마다 health check → 실패 시 경고 로그
  - 연결 복구 시 ' Gateway connected' 메시지

#3 실패 재시도 큐
  - RemoteTransport: _retry_queue (최대 100건)
  - POST 실패 시 큐에 저장, 연결 복구 후 자동 재전송
  - Collector: 10초마다 retry flush
2026-03-11 22:55:54 +09:00
Variet Worker
d2a477e12e fix(collector): MERGE + auto_resolved/expired 상태 변경 감지
- pending 파일 콘텐츠 해시 추적 (_pending_hashes)
- 내용 변경 시 Gateway에 재전달 (MERGE: command 업데이트, status 변경)
- _startup_pending으로 시작 시 기존 파일과 신규 파일 분리
2026-03-11 22:48:47 +09:00
Variet Worker
58a421f5a6 fix: 전체 시스템 감사 — 6건 수정 (보안 + 안정성)
Bug 1 (만료됨 스팸): Collector 시작 시 기존 pending skip
Bug 2 (pending 미삭제): Gateway에서 response 소비 시 pending도 삭제
Bug 3 (재시작 중복): Bug 1로 해결

Security 1: API 요청 1MB 크기 제한 (client_max_size)
Security 2: IP별 rate limiting (10 req/s)
Security 3: _commands 메모리 누수 방지 (TTL 30분)
2026-03-11 22:42:05 +09:00
Variet Worker
7eca0763c9 fix(collector): 기능 누락 3건 수정 — Discord 명령어/채팅/등록 중계
Gap 1: Discord→Extension 명령어 깨짐
  - bot.py: _write_command() 래퍼 — gateway.push_command()도 호출
  - main.py: bot.gateway 연결
  - 슬래시 명령어 + on_message 모두 _write_command 사용

Gap 2: Chat snapshot 미전달
  - collector.py: _forward_chat_snapshots_loop 추가

Gap 3: Session registration 미전달
  - collector.py: _forward_registrations_loop 추가
2026-03-11 22:31:08 +09:00
Variet Worker
3d75825bba feat(collector): brain event 중계 추가 — Watcher 이벤트를 Gateway로 전달
- collector.py: _forward_events_loop — BrainEvent를 JSON으로 serialize하여 /api/event POST
- gateway.py: /api/event 엔드포인트 — 수신한 이벤트를 bot event_queue에 주입
- main.py: event_queue를 CollectorBridge에 전달

이제 task.md, implementation_plan, walkthrough 변경사항이 Collector→Gateway→Discord 경로로 전달됨
2026-03-11 22:24:48 +09:00
Variet Worker
7e36db5191 fix(docker): Dockerfile에 parser.py 등 누락 — COPY *.py로 수정 2026-03-11 21:24:09 +09:00
Variet Worker
c60d14e408 docs: devlog 2026-03-11 항목 006~009 커밋 해시 및 추가 기록 2026-03-11 20:18:36 +09:00
Variet Worker
95c2905e14 feat(collector): RemoteTransport + CollectorBridge 구현 — Collector↔Gateway HTTP 통신 완성
- bridge.py RemoteTransport: HTTP 클라이언트, API Key auth, Gateway API 매핑
- collector.py CollectorBridge: 3개 async loop (pending 전달, response 폴링, commands 폴링)
- main.py: BOT_MODE=remote → CollectorBridge 실행 (Discord bot 없이)
- config.py: GATEWAY_API_KEY 설정
- .env.example: 모든 설정 항목 업데이트
2026-03-11 20:10:45 +09:00
Variet Worker
95da3e9307 feat(gateway): API Key 인증 + HTTPS (Caddy) 보안 강화
- gateway.py: auth middleware — /api/* 엔드포인트에 Bearer token 필수
- Caddyfile: Let's Encrypt 자동 HTTPS 리버스 프록시
- docker-compose.yml: Caddy 추가, Gateway 포트 내부 전용
- config.py: GATEWAY_API_KEY 설정 추가
- .env: 키 생성 명령어 가이드 포함
2026-03-11 19:49:24 +09:00
Variet Worker
6dbbb57fa7 feat(gateway): Docker Gateway 봇 + HTTP API 구현 #task-311
- gateway.py: Collector↔Gateway HTTP API (pending, response, chat, register, commands)
- Dockerfile + docker-compose.yml: BOT_MODE=gateway, port 8585
- main.py: gateway 모드 (watcher 비활성, GatewayAPI 시작)
- config.py: gateway 모드 BRAIN_PATH 검증 스킵
- requirements.txt: aiohttp 추가
- docs/usage-guide.md: Docker 배포 섹션 추가
- Extension VSIX v0.3.9 빌드 (auto-approve 포함)
2026-03-11 19:38:26 +09:00
Variet Worker
c1303999cf feat(bot,bridge): P1 !auto 토글 자동승인 + P2 BridgeTransport 추상화 #task-304 #task-305
P1: !auto 토글 (bot.py + extension.ts)
- auto_approve_projects set으로 프로젝트별 상태 관리
- !auto → on/off 토글, pending 자동 승인 + 🤖 자동 승인됨 embed
- Extension step_probe에서 autoApproveEnabled 시 직접 tryApprovalStrategies

P2: BridgeTransport 추상화 (bridge.py)
- BridgeTransport ABC + LocalTransport (기존 동작 100% 호환)
- RemoteTransport 스켈레톤 (multi-PC 대비)
- config.py BOT_MODE/REMOTE_BRIDGE_URL, main.py transport 주입

docs: usage-guide.md + tech-stack.md Python 경로 기록
2026-03-11 19:25:40 +09:00
Variet Worker
1696a2976b fix(config,extension): BRAIN_PATH 빈문자열 버그 + 크로스프로젝트 DEDUP MERGE 수정
- config.py: os.getenv BRAIN_PATH 빈값 시 CWD 해석 → or 패턴으로 수정
- extension.ts: writePendingApproval DEDUP에 project_name 가드 3곳 추가
- extension.ts: HTTP /pending file_permission dedup에도 project_name 가드
- known-issues: 2건 추가 (BRAIN_PATH, DEDUP MERGE)
- devlog: 2026-03-11 생성
2026-03-11 09:36:55 +09:00
Variet Worker
71aa80d144 fix(extension): v0.3.9 — SDK JS 파일 VSIX 포함 수정 + start_bot.bat conda Python 우선 2026-03-11 00:01:26 +09:00
CD
ff559bc6ee chore: .agents 워크플로우/레퍼런스/가이드 전체 추가 (.gitignore 규칙 제거) 2026-03-10 23:29:28 +09:00
CD
a0d46f1ff3 fix(extension): SDK LS 대소문자 매칭 버그 수정 — fixLSConnection() 추가 (멀티프로젝트 신호 누락 해결) 2026-03-10 22:51:02 +09:00
CD
4d780ec5e7 docs: devlog 013 + known-issues (Reload Window stale session, RUNNING 우선 선택, IDLE 채널) 2026-03-10 22:21:32 +09:00
CD
6179c4d242 fix(bridge): RUNNING 세션 우선 선택 + IDLE 채널 자동 생성 제거
- extension: bestSession 선택에 2단계 비교 (RUNNING > IDLE, then modTime)
- extension: [SESSION-FILTER] 진단 로그 + [projectName] 로그 접두사
- bot: pending_approval_scanner의 IDLE 프로젝트 자동 채널 생성 제거
- known-issues: 2개 항목 추가 (IDLE 고착, 채널 증식)
2026-03-10 21:56:46 +09:00
CD
5a3217d31a fix(extension): 크로스 프로젝트 response watcher 우회 수정 + file_permission write 도구 3-button 매핑
- response watcher: pending 삭제 후 response data의 project_name으로 fallback 필터
- processResponseFile: sessionId를 pending에서 우선 사용 (activeSessionId 대신)
- logToFile: [projectName] 접두사 추가 (공유 로그 구분)
- file_permission 리스트에 replace_file_content, write_to_file, multi_replace_file_content 추가
- UserResponse에 project_name 필드 추가 + bot.py 4곳 전파
- known-issues: 2건 추가, devlog 012
2026-03-10 21:02:06 +09:00
CD
08c5cb461b docs: devlog 011 + known-issues (workspace URI 세션 격리) 2026-03-10 19:33:39 +09:00
CD
ae91134ff2 fix(extension): v0.3.8 — workspace URI 기반 세션 필터링 (멀티프로젝트 격리) 2026-03-10 19:28:32 +09:00
CD
c9524fc8a8 fix(extension): v0.3.7 — file_permission 3-button 주입 + active_project.lock 제거
- writePendingApproval()에서 step_type=file_permission일 때 자동 3-button 주입
- active_project.lock 메커니즘 제거 (멀티 프로젝트 동시 사용 지원)
- step_probe auto-resolve에 project_name 필터 추가
- known-issues 2건 추가
2026-03-10 18:48:51 +09:00
CD
11a4730873 docs: devlog 008-009 (project lock, stale reject, v0.3.6 release) 2026-03-10 17:50:00 +09:00
CD
bd46beabb1 release: v0.3.6 — deployment package (VSIX + bot launcher + stale response filter + project lock) 2026-03-10 17:44:24 +09:00
CD
95d4f854f5 fix: skip stale timeout responses (>2min old reject) to prevent phantom REJECT duplicates 2026-03-10 17:23:47 +09:00
CD
186875ad0b feat: single active project lock — warns if another project already connected to Discord 2026-03-10 17:13:20 +09:00
CD
99f3f264ed docs: devlog entries 006-007 (diff review relay, step_type passthrough, file_permission auto-detect) 2026-03-10 15:56:14 +09:00
CD
d1586c5e97 fix: auto-detect file_permission for file-related tools in step_probe + always check cmd for allow 2026-03-10 15:50:01 +09:00
CD
4dcb78c1ce fix: focus dirty files before executing agentAcceptAllInFile command 2026-03-10 15:34:37 +09:00
CD
0470c03ab3 fix: add step_type to default approve/reject/timeout callbacks (not just multi-choice) 2026-03-10 15:29:55 +09:00
CD
26c19fb6be fix: add step_type to ApprovalRequest (was being filtered out by known-fields logic) 2026-03-10 15:16:18 +09:00
CD
c4dfbcad67 fix: increase pending timeout to 30min, pass step_type through response 2026-03-10 15:07:36 +09:00
CD
7982263fcd fix: pass step_type through response file for diff_review routing 2026-03-10 15:02:24 +09:00
CD
8fbf6bf6b7 fix: diff review uses cumulative file tracking instead of IDLE-time step scan 2026-03-10 14:44:16 +09:00
CD
f8f9ce8f5f fix: init lastUserInputStepIdx + lastResponseCaptureStep on session change (prevents stale replay) 2026-03-10 14:35:56 +09:00
CD
82b727a1e6 fix: skip echo relay for Discord-origin user messages 2026-03-10 14:31:47 +09:00
CD
c15b0f676f feat: diff review Discord relay — Accept/Reject all via VS Code commands 2026-03-10 14:28:01 +09:00
CD
8a6428efa8 docs: devlog 004-005 entries (auto_resolved sync + #253 relay) 2026-03-10 14:10:23 +09:00
CD
b50012075e feat: full conversation relay #253 — user messages + error notifications to Discord 2026-03-10 14:08:14 +09:00
CD
514c0f2738 fix: extract user message from userInput.userResponse field (discovered via step dump) 2026-03-10 14:05:22 +09:00
CD
17dd6654f1 feat: relay AG-side user messages to Discord via chat_snapshots 2026-03-10 13:58:19 +09:00
CD
048ffd90a3 feat: auto_resolved sync + expired card update + DOM step_index 2026-03-10 13:52:27 +09:00
CD
93439d2f1c docs: devlog index 002+003, known-issues update (verbosity + file_permission), Vikunja #276 #277 done 2026-03-10 13:46:43 +09:00
CD
a440868101 docs: devlog 20260310-003 — approval flow improvements summary 2026-03-10 13:42:52 +09:00
CD
47dbd38c7c fix: show actual arg values (paths, queries) instead of parameter names in approval 2026-03-10 13:30:01 +09:00
CD
e107b70510 fix: dedup file_permission pendings (10s window) + clean description text 2026-03-10 13:21:18 +09:00
CD
bec38f9a6a fix: filter DOM Observer Run-only pendings when step_probe already has pending 2026-03-10 13:08:50 +09:00
CD
14d2acf6c4 feat: 3-button file permission UX (Allow Once / Allow This Conversation / Deny) 2026-03-10 12:45:12 +09:00
CD
c9b4fd4722 fix: route file_permission scope by cmd (once=1, conversation=2) 2026-03-10 11:20:55 +09:00
CD
c612c37105 fix: module-scope stallProbed + reset after approval for consecutive detection 2026-03-10 11:16:23 +09:00
CD
857e10126d fix: add verbosity=DEBUG to all step_probe calls for full command text 2026-03-10 11:11:10 +09:00
CD
75a3482a9c fix: command length 150->1500, filter EPHEMERAL_MESSAGE, widen approval gate 2026-03-10 11:01:45 +09:00
CD
df592723b7 feat: file_permission interaction + DOM Observer RPC passthrough 2026-03-10 10:54:28 +09:00
CD
563fbadd5a docs: devlog 20260310-002 session summary 2026-03-10 10:42:44 +09:00
CD
2958bdc950 feat: real-time PLANNER_RESPONSE capture on every delta>0 during RUNNING 2026-03-10 09:54:30 +09:00
CD
9b047c0c7d fix: extract text from plannerResponse.modifiedResponse field 2026-03-10 09:38:24 +09:00
CD
7ed2db90df fix: add verbosity=DEBUG to GetCascadeTrajectorySteps for response text 2026-03-10 09:13:13 +09:00
CD
1089c6ce61 fix: extract text from plannerResponse field for Discord relay 2026-03-10 09:02:16 +09:00
CD
e586bb6d41 feat: capture AI text responses on RUNNING->IDLE for Discord relay 2026-03-10 08:43:57 +09:00
CD
8c6d25c6d4 fix: add snapshot diagnostics + lower content filter for Discord messages 2026-03-10 08:18:36 +09:00
CD
628b5ae2fa fix: use stepOffset to bypass 775-step API limit with full details 2026-03-10 08:08:36 +09:00
CD
2361aa7558 fix: disable ResolveOutstandingSteps + add 775-limit stall fallback 2026-03-10 08:03:57 +09:00
CD
0e3a896c86 feat: step_type routing for all approval interaction types 2026-03-10 07:56:36 +09:00
CD
1f63f60280 feat: proto-based RPC approval for Run commands via Discord
Decoded HandleCascadeUserInteractionRequest protobuf schema from AG's
extension.js (message #162, base64 FileDescriptor 78KB).

Working payload (variant PROTO-0):
  cascadeId + interaction.{trajectoryId, stepIndex, runCommand.confirm}

Changes:
- extension.ts: Added Strategy 0-PROTO with decoded proto RPC call
- extension.ts: Fixed processResponseFile to call tryApprovalStrategies()
  instead of direct clickTrigger (was bypassing all strategies)
- extension.ts: Fixed false positive Run detection (sessionStalled reset
  when step_probe confirms no WAITING)
- extension.ts: Moved lastPendingStepIndex to module scope
- extension.ts: Added activeTrajectoryId tracking from session init
- bot.py: Added MERGE detection + Discord message edit for command updates
- bot.py: Added _sent_commands tracking for merge detection

Proto RE methodology:
1. Found schema exports in AG extension.js
2. Located fileDesc() with base64 protobuf descriptor
3. Decoded 58KB raw proto, found message names
4. Extracted CascadeRunCommandInteraction.confirm field
5. Tested camelCase JSON via ConnectRPC = SUCCESS
2026-03-10 07:45:10 +09:00
CD
98646fed27 docs: update devlog index with commit hash aab1cfb 2026-03-10 06:34:38 +09:00
CD
aab1cfba27 fix(bridge): approval ENOENT race condition + multi-choice button grouping #task-276 #task-277 2026-03-10 06:32:20 +09:00
CD
373c0f7ddc fix(bridge): approval flow robustness — pending cleanup, MERGE dedup, false positive filter, auto_resolve, 30min timeout 2026-03-10 00:41:39 +09:00
CD
7fdefb0c63 docs: update devlog index with commit hash 4ba65f9 2026-03-09 23:26:39 +09:00
CD
4ba65f9fc7 feat(bridge): Retry/Dismiss/Reject-all button detection + agent_guide workflow integration #task-274 2026-03-09 23:26:04 +09:00
164 changed files with 16413 additions and 4455 deletions

70
.agents/AGENT.md Normal file
View File

@@ -0,0 +1,70 @@
---
description: 모든 작업에 자동 적용되는 에이전트 행동 규칙. 새 대화 시작 시 반드시 이 파일을 먼저 읽습니다.
---
# Agent Rules
## Identity
당신은 이 프로젝트의 시니어 개발자입니다. 지시를 정확히 따르고, 추측보다 근거를 우선합니다.
## NEVER (절대 금지)
1. NEVER start coding without reading relevant reference documents in `.agents/references/`
2. NEVER guess when documentation exists — always check `.agents/references/` first
3. NEVER repeat a failed approach — check `.agents/references/known-issues.md` first
4. NEVER call APIs directly when helper scripts exist in `.agents/workflows/helpers/`
5. NEVER skip the pre-task checklist defined in `.agents/workflows/pre-task.md`
6. NEVER attempt the same failed approach more than 2 times
7. NEVER truncate error messages — always show the full error output
8. NEVER say "구현 완료" or "동작 확인" without ACTUAL end-to-end test — import/문법 통과는 검증이 아님
9. NEVER confuse "코드가 논리적으로 맞음" with "실제로 동작함" — 실행 로그가 없으면 미검증
10. NEVER fix or audit code by looking at only the immediate file:
(a) Open the PRODUCER (who creates the data?) and CONSUMER (who reads/deletes?)
(b) Search for defense mechanisms (try-catch, dedup, idempotency guards)
(c) DISPROVE the bug before reporting — if a defense exists, it may be a false positive
(d) Report only bugs with a proven end-to-end triggering path
"I traced the flow" without opening actual files = violation.
11. NEVER apply changes mechanically across files — every import, variable, function must have at least one callsite in the SAME file
## ALWAYS (필수)
1. ALWAYS run `.agents/workflows/pre-task.md` before any implementation task
2. ALWAYS check `.agents/references/known-issues.md` before debugging
3. ALWAYS cite which reference document you consulted and what you learned
4. ALWAYS stop and ask the user if 2 consecutive attempts on the same approach fail
5. ALWAYS use existing helper scripts instead of raw API calls
6. ALWAYS read related existing code (minimum 3 files) before writing new code
7. ALWAYS verify with real execution after implementation — trigger the actual flow, check logs (e.g. extension.log), confirm the expected result appeared
8. ALWAYS distinguish "구현했다" vs "검증했다" when reporting to user — 테스트 안 했으면 명시
9. ALWAYS cross-reference with project history (devlog, git log -5, Vikunja) when evaluating system state — code absence may mean "intentionally removed" or "deployed externally", not "unimplemented"
## Failure Protocol
```
1st failure → Re-read reference docs → Try DIFFERENT approach
2nd failure (same issue) → STOP → Report diagnosis to user with:
- What was tried
- What failed
- Root cause hypothesis
- Suggested next steps
3rd attempt on same approach → FORBIDDEN
```
## Reference Loading Order
1. `.agents/AGENT.md` (this file — behavior rules)
2. `.agents/references/known-issues.md` (past failure patterns)
3. `.agents/references/` (project-specific knowledge)
4. `.agents/workflows/services.md` (service credentials & protocols)
5. `.agents/workflows/` (action procedures)
## Bug Report Protocol
→ See `.agents/references/bug-report-protocol.md`
## PowerShell Notes
- `curl` → PowerShell에서 `Invoke-WebRequest` 별칭. **반드시 `curl.exe`** 사용
- `npm` → 실행 정책 문제 시 `cmd /c npm` 사용
- JSON 처리 시 `.py` 스크립트 권장 (PowerShell 이스케이핑 이슈 방지)

163
.agents/GUIDE.md Normal file
View File

@@ -0,0 +1,163 @@
# AI 에이전트 워크플로우 시스템 가이드
> 이 가이드는 AI 코딩 에이전트가 더 똑똑하게 동작하도록 설계된 범용 워크플로우 시스템의 사용법을 설명합니다.
---
## 왜 이 시스템이 필요한가?
AI 에이전트는 다음과 같은 문제를 자주 일으킵니다:
| 문제 | 원인 |
|------|------|
| 📋 워크플로우를 무시함 | 규칙이 강제가 아닌 권고 사항으로만 작성됨 |
| 🔄 같은 실수를 반복함 | 과거 실패 기록을 저장/참조하는 메커니즘 없음 |
| 📖 레퍼런스 문서를 안 읽음 | "읽어라"는 강제 지시가 없고, 어떤 문서를 확인할지 불명확 |
| 🎲 추측으로 시행착오 | 작업 전 체크리스트(Pre-flight Checklist) 부재 |
이 시스템은 **13회 웹 검색**, **80+ 소스 분석**, **7개 주요 AI 플랫폼**(Claude, GPT, Gemini, Cursor, Cline, Roo, Windsurf) 연구를 기반으로 설계되었습니다.
---
## 파일 구조 개요
```
.agents/
├── AGENT.md ← 🧠 에이전트 헌법 (NEVER/ALWAYS 규칙)
├── GUIDE.md ← 📖 이 가이드
├── references/ ← 📚 프로젝트 지식 베이스
│ ├── architecture.md ← 아키텍처 설명
│ ├── tech-stack.md ← 기술 스택 & 버전
│ ├── conventions.md ← 코딩 컨벤션
│ └── known-issues.md ← 🔴 과거 실패 기록 (핵심!)
└── workflows/ ← ⚙️ 행동 절차
├── start.md ← 세션 시작 (룰 로딩 + devlog 복구)
├── end.md ← 세션 종료 (devlog + known-issues + Vikunja + Git)
├── pre-task.md ← 작업 전 필수 체크리스트
├── debug.md ← 디버깅 전용 절차
├── services.md ← 서비스 연동 정보 + AI 작업 프로토콜
├── check-gitea.md ← Gitea 현황 조회
├── check-vikunja.md ← Vikunja 태스크 조회
└── helpers/
├── vikunja_helper.py ← Vikunja API 안전 래퍼
└── wiki_helper.py ← Gitea Wiki 래퍼
```
**프로젝트 루트에 자동 생성되는 디렉토리:**
```
docs/devlog/ ← 📓 세션별 작업 기록
├── YYYY-MM-DD.md ← Index (매일 1줄씩 누적)
└── entries/
└── YYYYMMDD-NNN.md ← Entry (설계 결정/미완료 시만)
```
---
## 각 파일의 역할
### 🧠 `AGENT.md` — 에이전트 헌법
에이전트가 **모든 대화에서 따라야 하는 글로벌 규칙**입니다.
**핵심 메커니즘:**
- **NEVER 규칙**: `"절대 ~하지 마라"` — 연구에 따르면 금지 규칙이 더 잘 지켜집니다
- **Failure Protocol**: 동일 접근 2회 실패 시 자동 중단 → 유저에게 보고
- **Reference Loading Order**: 어떤 문서를 먼저 읽을지 우선순위 명시
### 📋 `pre-task.md` — 사전 점검 체크리스트
모든 구현 작업 전에 실행하는 **4단계 체크리스트**:
1. 요구사항 정리
2. 레퍼런스 확인 (추측 금지)
3. 계획 수립
4. 유저 확인
### 🔴 `known-issues.md` — 과거 실패 기록
**가장 중요한 파일.** 에이전트가 같은 실수를 반복하는 근본 원인은 **실패를 기억하지 못하기 때문**입니다. 이 파일은:
- 세션 종료 시 에이전트가 자동으로 새 이슈를 추가
- 디버깅/구현 전에 에이전트가 반드시 확인
- 시간이 지날수록 **축적 학습** 효과
### 🔧 `debug.md` — 디버깅 전용 워크플로우
**추측 기반 디버깅을 금지**하는 5단계 절차:
1. 정보 수집 (에러 전문 확인)
2. known-issues 확인
3. 근본 원인 분석 (가설 → 검증)
4. 수정 및 검증
5. 기록 (known-issues에 추가)
### 📓 Devlog — 세션별 작업 기록 (start.md / end.md에서 관리)
known-issues가 **실패만** 기록한다면, devlog는 **전체 세션 이력**을 기록합니다:
- **Index** (`docs/devlog/YYYY-MM-DD.md`): 매 작업마다 1줄 (필수)
- **Entry** (`docs/devlog/entries/YYYYMMDD-NNN.md`): 설계 결정/미완료/삽질 시만 (선택)
- **start.md**에서 자동으로 오늘/어제 devlog를 읽어 맥락 복구
### ▶️ `start.md` / ⏹️ `end.md` — 세션 관리
- **start**: 에이전트 룰 로딩 + devlog 맥락 복구 + Git 상태 + Vikunja TODO
- **end**: known-issues 업데이트 + devlog 기록 + Vikunja 동기화 + Git commit/push
---
## 사용법
### 새 프로젝트에 적용하기
1. `.agents/` 디렉토리를 프로젝트에 복사
2. `references/` 파일들을 프로젝트에 맞게 채우기:
- `architecture.md` — 프로젝트 구조 설명
- `tech-stack.md` — 사용 기술 및 버전
- `conventions.md` — 코딩 스타일 규칙
3. 프로젝트별 워크플로우가 있다면 `workflows/`에 추가
### 프로젝트별 워크플로우와 함께 사용하기
이 범용 워크플로우와 프로젝트별 워크플로우(예: Vikunja 동기화, Gitea 연동)는 **함께 사용**합니다:
```
.agents/
├── AGENT.md ← 범용 (공통)
├── references/ ← 범용 + 프로젝트 특화
│ ├── known-issues.md ← 범용 (공통)
│ └── ... ← 프로젝트에 맞게 작성
└── workflows/
├── pre-task.md ← 범용 (공통)
├── debug.md ← 범용 (공통)
├── start.md ← 범용 기반 + 프로젝트 단계 추가
├── end.md ← 범용 기반 + 프로젝트 단계 추가
├── services.md ← ⭐ 프로젝트별
├── check-vikunja.md ← ⭐ 프로젝트별
├── check-gitea.md ← ⭐ 프로젝트별
└── helpers/
├── vikunja_helper.py ← ⭐ 프로젝트별
└── wiki_helper.py ← ⭐ 프로젝트별
```
### 다른 AI IDE에서도 사용하기
| 대상 플랫폼 | 방법 |
|------------|------|
| **Cursor** | `AGENT.md``.cursor/rules/agent.mdc` (alwaysApply) |
| **Claude Code** | `AGENT.md``CLAUDE.md`, references를 `@import` |
| **Windsurf** | `AGENT.md``.windsurfrules` 또는 `.windsurf/rules/agent.md` |
| **Cline/Roo** | 루트에 `AGENTS.md`로 복사 |
| **Gemini** | `AGENT.md``.gemini/GEMINI.md` |
---
## 연구 근거 요약
이 시스템의 각 설계 결정은 학술 연구와 실무 사례에 근거합니다:
| 설계 결정 | 근거 |
|----------|------|
| NEVER > ALWAYS (금지 규칙 우선) | Community 검증 — "NEVER use X" ≫ "always prefer Y" |
| 2회 실패 시 자동 중단 | Streak Breaker / Sentinel Check 연구 |
| 실패 기록 누적 | Reflexion Framework (텍스트 피드백 기반 자기 교정) |
| 사전 체크리스트 강제 | Claude Skills 체크리스트 + GPT Chain-of-Thought |
| Progressive Disclosure | Anthropic Context Engineering (2025) |
| 300줄 이하 규칙 | Claude `CLAUDE.md` 공식 권장 (토큰 효율성) |
| 코드 예시 > 설명 | GitHub Copilot Agents, AGENTS.md 공통 Best Practice |

View File

@@ -0,0 +1,322 @@
# Architecture
> AI 에이전트는 구현 전 이 문서를 반드시 확인합니다.
## 1. 프로젝트 개요
**Gravity Control**은 Antigravity AI 코딩 에이전트와 Discord를 실시간으로 연결하는 브릿지 시스템이다.
### 핵심 목적
- AI 에이전트의 **승인 요청**(코드 실행, 파일 수정 등)을 Discord로 전달하고 사용자 응답을 반환
- AI 에이전트의 **작업 스냅샷**(대화 요약, 진행 상황)을 Discord에 실시간 표시
- **코드 리뷰**(diff review) accept/reject을 Discord에서 처리
- 사용자의 Discord **명령어**(!approve, !reject, !auto 등)를 AG Extension으로 전달
- **Auto-approve 모드**로 무인 작업 지원
### 시스템 구성
```
┌────────────────┐ WebSocket ┌──────────────┐ Discord API ┌─────────┐
│ VS Code │◄──────────────────►│ Hub Server │◄───────────────────►│ Discord │
│ AG Extension │ type:auth/chat │ (hub.py + │ discord.py bot │ 서버 │
│ (TypeScript) │ /pending/resp │ gateway.py)│ │ │
└────────────────┘ └──────────────┘ └─────────┘
↕ AG SDK (RPC) ↕
┌────────────────┐ ┌──────────────┐
│ Antigravity │ │ 파일 bridge │ ← 레거시 fallback
│ AI Engine │ │ (bridge.py) │ (WS 미사용 시)
└────────────────┘ └──────────────┘
```
---
## 2. 디렉토리 구조
```
gravity_control/
├── main.py # 진입점: Bot + Hub + Watcher 통합 시작
├── config.py # 환경변수 + .env 로드 (66줄)
├── ── 서버 측 (Python) ──
├── bot.py # Discord 봇: 승인 UI, 채널 관리, Hub 핸들러 (1,286줄)
├── hub.py # WebSocket Hub: 연결 관리, 메시지 라우팅 (580줄)
├── auth.py # JWT 토큰 + registration code 인증 (127줄)
├── gateway.py # HTTP REST API + /ws endpoint (168줄)
├── bridge.py # 파일 기반 IPC (레거시 fallback) (270줄)
├── watcher.py # Brain 디렉토리 변경 감시 (290줄)
├── parser.py # Markdown → Discord 변환 (245줄)
├── ── Extension 측 (TypeScript) ──
├── extension/src/
│ ├── extension.ts # 메인: SDK init, activate, 오케스트레이션 (650줄)
│ ├── step-probe.ts # 폴링 + 응답 처리 + 승인 전략 (1,479줄)
│ │ # setupMonitor(), processResponseFile(),
│ │ # writePendingApproval(), tryApprovalStrategies()
│ ├── http-bridge.ts # HTTP 서버 (Renderer↔Extension Host 통신) (280줄)
│ │ # startHttpBridge(), getDeterministicPort()
│ ├── html-patcher.ts # AG HTML 패치 + product.json 체크섬 (280줄)
│ │ # setupApprovalObserver(), updateProductChecksums()
│ ├── command-handler.ts # Discord→AG 명령어 처리 (175줄)
│ │ # watchCommandsDir(), handleWSCommand()
│ ├── observer-script.ts # DOM Observer 스크립트 생성 (698줄)
│ │ # generateApprovalObserverScript()
│ ├── ws-client.ts # WSBridgeClient: Hub 연결, 재연결, 큐 (505줄)
│ ├── step-utils.ts # Step 파싱 순수 함수 4개 (114줄)
│ │ # extractPlannerText, filterEphemeral,
│ │ # extractToolCommand, extractToolDescription
│ └── sdk/ # Antigravity SDK 로컬 임베드
│ ├── index.js # SDK 런타임 (4,014줄)
│ └── index.d.ts # SDK 타입 정의 (2,297줄)
├── ── 테스트 ──
├── tests/
│ ├── test_ws_hub.py # Hub WS 연결 테스트
│ └── test_syntax.py # Python 구문 검증
├── ── 문서 / 설정 ──
├── .env # 환경변수 (git 제외)
├── .agents/references/ # AI 에이전트 레퍼런스
├── docs/devlog/ # 작업 로그
└── start_bot.bat # 윈도우용 봇 시작 스크립트
```
---
## 3. 핵심 모듈 상세
### 3.1 Hub (hub.py) — WebSocket 메시지 허브
**역할**: Extension ↔ Bot 간 실시간 양방향 통신 중계
| 기능 | 설명 |
|------|------|
| 연결 관리 | 프로젝트별 다중 인스턴스, 인스턴스 번호 자동 부여 |
| JWT 인증 | registration_code → JWT 발급 → 이후 토큰 재인증 |
| 메시지 라우팅 | pending, chat, register, auto_resolve, brain_event |
| 응답 역라우팅 | request_id → pending_owners → 원본 Extension으로 전달 |
| Rate limiting | per-connection 100msg/10s |
| Dedup | msg_id 기반 60s TTL 중복 제거 |
| Heartbeat | 30s 간격 ping/pong |
**프로토콜**:
```
1. Client → Server: {type:"auth", registration_code/token, project, pc}
2. Server → Client: {type:"auth_ok", conn_id, instance_number, session_token}
3. 양방향 메시지 교환:
- Extension→Hub: pending, chat, register, auto_resolve, brain_event
- Hub→Extension: response, command, instance_update, error
```
### 3.2 Auth (auth.py) — 인증 관리
| 기능 | 설명 |
|------|------|
| Registration Code | 사전 공유 코드로 최초 인증 |
| JWT 발급 | HMAC-SHA256, 24시간 유효 |
| 토큰 검증 | 만료/위조 감지, 프로젝트+PC 메타데이터 포함 |
### 3.3 Bot (bot.py) — Discord 인터페이스
| 기능 | 설명 |
|------|------|
| 승인 UI | Approve/Reject 버튼, diff_review Accept/Reject |
| Auto-approve | `!auto` 토글, 세션 간 초기화 |
| 채널 관리 | `#ag-{project}` 자동 채널 매칭 |
| 스냅샷 전달 | 2000자 초과 시 파일 첨부 |
| 명령어 | !approve, !reject, !auto, !status, !send |
| Hub 연동 | on_hub_pending, on_hub_chat, on_hub_register 핸들러 |
| IDLE 알림 | AI step 종료 시 Discord 알림 |
### 3.4 Extension (extension.ts) — VS Code 확장 (오케스트레이터)
| 기능 | 설명 |
|------|------|
| AG SDK 연동 | getCascadeStatus, getAllTrajectories RPC |
| 세션 감지 | activeSessionId 자동 추적 |
| 프로젝트 자동 감지 | git remote URL 기반 |
| 모듈 초기화 | HTTP bridge, observer, command handler 시작 |
| WS bridge | WSBridgeClient 통한 Hub 연결 (우선) |
| Status bar | SDK 상태 + 연결 상태 표시 |
### 3.4a HTTP Bridge (http-bridge.ts)
`HttpBridgeContext` 인터페이스로 extension.ts의 공유 상태 참조:
| 기능 | 설명 |
|------|------|
| POST /pending | Renderer가 발견한 승인 버튼 보고 |
| GET /response/:rid | Renderer가 Discord 응답 폴링 |
| GET /trigger-click | Extension→Renderer 클릭 트리거 |
| GET/POST /deep-inspect* | DOM 심층 검사 |
| getDeterministicPort | 프로젝트명 기반 결정적 포트 |
### 3.4b HTML Patcher (html-patcher.ts)
| 기능 | 설명 |
|------|------|
| setupApprovalObserver | AG Workbench HTML 파일에 observer 스크립트 인라인 삽입 |
| updateProductChecksums | product.json SHA256 체크섬 업데이트 (vscode-file:// 프로토콜용) |
| CSP 패치 | script-src에 'unsafe-inline' 추가 |
| .orig 백업 | 최초 패치 전 원본 백업, 손상 시 자동 복구 |
### 3.4c Command Handler (command-handler.ts)
`CommandHandlerContext` 인터페이스로 extension.ts 상태 참조:
| 기능 | 설명 |
|------|------|
| watchCommandsDir | commands/ 디렉토리 fs.watch + 3s 폴링 |
| handleWSCommand | WS Hub 경유 명령어 처리 |
| !stop, !auto | AG 에이전트 제어 명령어 |
| 텍스트 전달 | Discord → AG `sendPromptToAgentPanel` |
### 3.5 Step Probe (step-probe.ts) — 상태 폴링
`BridgeContext` 인터페이스로 extension.ts와 상태 공유:
| 기능 | 설명 |
|------|------|
| setupMonitor | 3초 간격 SDK 폴링, 세션/step 변화 감지 |
| processResponseFile | Discord 응답 → AG RPC 실행 |
| writePendingApproval | 승인 요청 파일/WS 전송 |
| tryApprovalStrategies | 다단계 승인: DOM click → VS Code command → RPC |
| setupResponseWatcher | response/ 디렉토리 파일 감시 |
**BridgeContext 필드** (14개):
`bridgePath`, `projectName`, `sdk`, `wsBridge`, `logToFile`, `autoApproveEnabled`, `activeSessionId`, `setClickTrigger`, `recentDiscordSentTexts`, `writeChatSnapshot`, `writeChatSnapshotWithFiles`, `workspaceUri`, `diffReviewMetadata`, `sessionStalled`, `lastPendingStepIndex`, `stallProbed`, `sawRunningAfterPending`
### 3.6 WS Client (ws-client.ts) — Hub 클라이언트
| 기능 | 설명 |
|------|------|
| 연결 관리 | WebSocket + 자동 재연결 |
| Backoff | 1s→60s 지수 백오프 + ±30% jitter |
| 메시지 큐 | 200개 버퍼, 재연결 시 자동 flush |
| Heartbeat | 25s 간격 ping |
| 인증 | registration_code 또는 session_token |
| API | sendPending, sendChat, sendRegister, sendAutoResolve |
### 3.7 Observer Script (observer-script.ts)
AG Webview의 DOM을 관찰하여 승인 버튼을 자동 감지/클릭:
- MutationObserver로 `.actions-container` 감시
- 버튼 텍스트 매칭으로 Approve/Reject 자동 실행
- `postMessage`로 Extension과 통신
### 3.8 Step Utils (step-utils.ts)
순수 함수 4개:
- `extractPlannerText(content)` — AI 응답 텍스트 추출
- `filterEphemeral(text)` — 시스템 메시지 필터링
- `extractToolCommand(content)` — 도구 명령어 추출
- `extractToolDescription(content)` — 도구 설명 추출
---
## 4. 데이터 흐름
### 4.1 승인 요청 플로우
```
AG Engine → SDK RPC → Extension(step-probe.ts)
→ setupMonitor: WAITING step 감지
→ writePendingApproval: pending 데이터 생성
→ [WS] wsBridge.sendPending() → Hub → Bot → Discord (버튼 UI)
→ [파일] bridge/pending/{id}.json (fallback)
```
### 4.2 승인 응답 플로우
```
Discord (사용자 버튼 클릭) → Bot
→ [Hub connected] Hub.route_response() → WS → Extension
→ [File fallback] bridge/response/{id}.json → setupResponseWatcher
→ processResponseFile → tryApprovalStrategies
1차: DOM observer script (webview inject)
2차: VS Code command (cascade.approveCurrentStep)
3차: Direct RPC (acknowledgeCodeActionStep)
```
### 4.3 채팅 스냅샷 플로우
```
Extension(step-probe.ts) → 새 step 텍스트 감지
→ writeChatSnapshot(text) → truncation + dedup
→ [WS] wsBridge.sendChat() → Hub → Bot → Discord (#ag-{project})
→ [파일] bridge/pending/ snapshot 파일 (fallback)
```
### 4.4 Diff Review 플로우
```
AG Engine → 파일 수정 → Extension(step-probe.ts)
→ edit_step_indices + modified_files 메타데이터 수집
→ writePendingApproval (step_type="diff_review", 8초 지연)
→ Discord (Accept all / Reject all 버튼)
→ 응답 → handleDiffReviewResponse()
→ openReviewChanges → 파일별 포커스 → agentAcceptAllInFile VS Code 커맨드
```
> [!IMPORTANT]
> diff_review는 **VS Code 커맨드만 유효**합니다. RPC 방식(AcknowledgeCascadeCodeEdit 등)은 모두 실패 확정.
> 상세 경위는 known-issues-archive.md의 "Diff Review 관련" 섹션을 참조하세요.
---
## 5. 통신 프로토콜
### 5.1 WebSocket 메시지 타입
**Extension → Hub (upstream)**:
| type | data 필드 | 설명 |
|------|-----------|------|
| `auth` | registration_code/token, project, pc | 최초 인증 |
| `pending` | request_id, command, description, buttons | 승인 요청 |
| `chat` | content, attached_files, conversation_id | 채팅 스냅샷 |
| `register` | conversation_id, project_name | 세션 등록 |
| `auto_resolve` | request_id | 자동 해결 알림 |
| `brain_event` | (payload) | 브레인 이벤트 |
| `heartbeat` | - | 연결 유지 |
**Hub → Extension (downstream)**:
| type | data 필드 | 설명 |
|------|-----------|------|
| `auth_ok` | conn_id, instance_number, session_token | 인증 성공 |
| `auth_fail` | reason | 인증 실패 |
| `response` | request_id, approved, button_index | 승인 응답 |
| `command` | text, action | Discord 명령어 |
| `instance_update` | active_count, instances[] | 인스턴스 변경 |
| `error` | error | 에러 |
### 5.2 BOT_MODE 동작 차이
| 모드 | Watcher | Hub/Gateway | 용도 |
|------|---------|-------------|------|
| `local` | ✅ (brain 감시) | ❌ | 로컬 개발 (Extension과 같은 PC) |
| `gateway` | ❌ | ✅ (port 8585) | 서버 배포 (WS Hub + Gateway) |
| `remote` | ✅ | ❌ | ⚠️ **DEPRECATED** — 레거시 Collector (WS Hub 사용 권장) |
---
## 6. 보안
| 항목 | 구현 |
|------|------|
| WS 인증 | Registration Code → JWT (HMAC-SHA256, 24h) |
| Gateway API | API Key 헤더 (`X-API-Key`) |
| Rate limit | per-connection 100msg/10s |
| 메시지 dedup | msg_id 기반 60s TTL |
| Discord | Bot 토큰 + Guild ID 제한 |
---
## 7. Extension 설정 (VS Code)
| 설정 키 | 설명 | 기본값 |
|---------|------|--------|
| `gravityBridge.bridgePath` | Bridge 디렉토리 경로 | `~/.gemini/antigravity/bridge` |
| `gravityBridge.projectName` | 프로젝트 이름 | git remote 자동 감지 |
| `gravityBridge.hubUrl` | WebSocket Hub URL | (비어있으면 WS 비활성) |
| `gravityBridge.registrationCode` | Hub 등록 코드 | (서버에서 발급) |

View File

@@ -0,0 +1,41 @@
# Bug Report Protocol
> 이 프로토콜은 코드 감사 또는 디버깅 시 false positive를 방지하기 위한 6단계 검증 절차입니다.
> AGENT.md 규칙 #10의 세부 구현입니다.
## 절차
```
1. Identify: 로컬 코드에서 잠재적 이슈 식별
2. Trace: 해당 데이터의 전체 생명주기 추적
- Producer (생성자) 파일을 열어 확인
- Transport (전달 경로: file, HTTP, RPC) 확인
- Consumer (소비자) 파일을 열어 확인
3. Defend: 기존 방어 메커니즘 검색
- try-catch, idempotency guard, dedup logic
- upstream validation, downstream tolerance
4. Disprove: 버그가 아닌 이유를 적극적으로 찾기
- "이 코드가 안전한 이유는?"
- 방어 메커니즘이 존재하면 → false positive 가능성 높음
5. Prove: 여전히 버그라면 트리거 경로를 증명
- 구체적 입력 → 구체적 경로 → 구체적 실패
- "A가 B를 호출하면 C에서 D가 발생" 형태
6. Report: 증명된 버그만 보고
- 트리거 경로 + 심각도 + 영향 범위 포함
- 증명 못 한 것은 보고하지 않음
```
## 결정 기준
| 상황 | 판정 |
|------|------|
| 방어 메커니즘 존재 + 트리거 경로 없음 | ❌ 보고 안 함 |
| 방어 메커니즘 없음 + 트리거 경로 증명됨 | ✅ 보고 |
| 방어 메커니즘 존재 + 우회 경로 증명됨 | ✅ 보고 (우회 경로 명시) |
| 잘 모르겠음 | 🔍 추가 조사 후 판단 (추측으로 보고 금지) |
## 근거
- Anthropic Code Review: "verification step attempts to disprove each finding"
- LLM Self-Verification: 자기 결과를 검증하지 않으면 noise와 과신 리포트 양산
- Systems Thinking: 개별 컴포넌트가 아닌 관계와 상호의존성에 집중

View File

@@ -0,0 +1,125 @@
# Coding Conventions
> AI 에이전트는 코드를 작성하기 전 이 컨벤션을 확인합니다.
## 네이밍
### Python (서버)
| 대상 | 규칙 | 예시 |
|------|------|------|
| 변수/함수 | snake_case | `write_pending_approval()` |
| 클래스 | PascalCase | `GravityBot`, `WSHub`, `TokenManager` |
| 상수 | UPPER_SNAKE_CASE | `MAX_MSG_SIZE`, `HEARTBEAT_INTERVAL` |
| 파일명 | snake_case | `hub.py`, `ws_client.py` |
| 로거명 | 모듈명 | `logging.getLogger("hub")` |
### TypeScript (Extension)
| 대상 | 규칙 | 예시 |
|------|------|------|
| 변수/함수 | camelCase | `writePendingApproval()`, `setupMonitor()` |
| 클래스 | PascalCase | `WSBridgeClient` |
| 인터페이스 | PascalCase | `BridgeContext`, `WSPendingData` |
| 상수 | UPPER_SNAKE_CASE | `MAX_QUEUE_SIZE`, `AUTH_TIMEOUT` |
| 파일명 | kebab-case | `ws-client.ts`, `step-probe.ts` |
| export 함수 | camelCase | `initStepProbe()`, `generateApprovalObserverScript()` |
## 코드 스타일
| 항목 | Python | TypeScript |
|------|--------|-----------|
| 들여쓰기 | 4 spaces | 4 spaces |
| 따옴표 | 쌍따옴표 `"` (f-string 포함) | 작은따옴표 `'` |
| 세미콜론 | N/A | 사용 |
| 줄바꿈 | LF (Unix) | CRLF (Windows, git 자동 변환) |
| 최대 줄 길이 | 120자 권장 | 120자 권장 |
| 타입 힌트 | 적극 사용 (`-> list[str]`) | strict (`BridgeContext` 인터페이스) |
## 커밋 메시지
```
<type>(<scope>): <description>
type: feat|fix|refactor|test|docs|chore|ci|infra
scope: server|extension|hub|bot|gateway|bridge (선택)
```
**예시:**
- `feat(hub): WebSocket Hub 구현 + JWT 인증`
- `refactor(extension): 모듈 분리 (step-probe, observer-script)`
- `fix(bot): auto-approve 세션 간 초기화`
- `docs: architecture.md 전면 재작성`
관련 Vikunja 태스크가 있으면: `feat(hub): WS Hub 구현 #task-395`
## 주석
- 한국어/영어 혼용 가능
- TODO 주석: `// TODO: 설명` 형식
- 섹션 구분: `// ─── Section Name ───` (TypeScript), `# ─── Section ───` (Python)
- 복잡한 로직에는 반드시 WHY(왜) 주석 추가
- 함수 docstring: Python은 `"""..."""`, TypeScript는 `/** ... */`
## 모듈 분리 패턴
Extension 모듈 분리 시 사용하는 패턴:
| 패턴 | 용도 | 예시 |
|------|------|------|
| **순수 함수 추출** | 외부 상태 참조 없는 함수 | `step-utils.ts` |
| **독립 스크립트** | 문자열 반환 함수 | `observer-script.ts` |
| **Context 패턴** | 공유 상태가 많은 함수 그룹 | `step-probe.ts` (BridgeContext) |
| **클래스 추출** | 자체 상태 + 메서드 | `ws-client.ts` (WSBridgeClient) |
## 테스트
| 항목 | 위치 | 도구 |
|------|------|------|
| Python 구문 검사 | `tests/test_syntax.py` | `ast.parse` |
| WS Hub 연결 | `tests/test_ws_hub.py` | `websockets` |
| TypeScript 컴파일 | `npx tsc --noEmit` | TypeScript compiler |
| E2E | 수동 (Discord 버튼 클릭) | — |
## 로깅
| 측 | 방식 | 포맷 |
|----|------|------|
| Python | `logging.getLogger(name)` | `YYYY-MM-DD HH:MM:SS [name] LEVEL: message` |
| Extension | `logToFile(msg)` → bridge/log/ | `[HH:MM:SS] message` + `[WS]` prefix |
| Hub | `[HUB]` prefix | `[HUB] Auth OK: {conn_id} project={project}` |
| Gateway | `[GATEWAY]` prefix | `[GATEWAY] HTTP API started on {host}:{port}` |
## WS / File Bridge 상호 배타 패턴
> [!IMPORTANT]
> WS Hub과 파일 bridge는 **항상 상호 배타적**이어야 합니다.
> 양쪽에 동시에 쓰면 이중 전달 버그가 발생합니다. (known-issues-archive 참조)
**Extension (TypeScript):**
```typescript
// ✅ 올바른 패턴
if (ctx.wsBridge?.isConnected()) {
ctx.wsBridge.sendPending(data);
return; // ← 반드시 return으로 파일 쓰기 건너뛰기
}
// File fallback
fs.writeFileSync(pendingPath, JSON.stringify(data));
```
**Bot (Python):**
```python
# ✅ 올바른 패턴
if self.hub:
await self.hub.send_response(...)
else:
bridge.write_response(...)
```
**금지 패턴:**
```python
# ❌ 이중 쓰기 — 절대 금지
if self.hub:
await self.hub.send_response(...)
bridge.write_response(...) # ← Hub 성공해도 파일에도 씀 → 이중 처리
```

View File

@@ -0,0 +1,581 @@
# Known Issues — Archive
> **해결 완료된 이슈 아카이브입니다.**
> 현재 활성 이슈는 [`known-issues.md`](file:///c:/Users/Variet-Worker/Desktop/gravity_control/.agents/references/known-issues.md)를 참조하세요.
> 비슷한 문제가 재발할 때 이 문서에서 과거 해결 방법을 검색하세요.
---
## 포맷
```markdown
### [날짜] [키워드] — 한줄 요약
- **증상**: 무엇이 잘못되었는가
- **원인**: 근본 원인
- **해결**: 올바른 해결 방법
- **주의**: 재발 방지를 위한 교훈
```
---
## 승인 전략 결정 체인 (2026-03-08~09 전체 요약)
> **이 섹션은 2026-03-08~09 전체 세션의 시행착오를 요약합니다.**
> **이미 거부된 접근을 다시 시도하지 마세요.**
### ❌ 시도 후 거부된 접근 (재시도 금지)
| # | 접근 | 결과 | 거부 사유 |
|---|------|------|-----------|
| 1 | LS RPC `HandleCascadeUserInteraction` | `socket hang up` | AG 빌드에서 핸들러 미구현 |
| 2 | LS RPC `ResolveOutstandingSteps` | CANCEL 동작 (승인이 아님) | 명칭과 달리 step을 취소함 |
| 3 | VS Code 7개 승인 명령 (`terminalCommand.run` 등) | `command not found` | AG 런타임에 미등록 |
| 4 | 키보드 시뮬레이션 (`type {Enter}`) | 빈 메시지 전송 | Chat input에 캡처됨 |
| 5 | `sendChatActionMessage` / `executeCascadeAction` | 미등록 | 119개 명령에 없음 |
| 6 | pywinauto (OS 레벨) | 사용자 결정 폐기 | 크로스플랫폼 불가, 창 겹침 문제 |
| 7 | CDP (Chrome DevTools Protocol) | **사용자 명시적 거부** | 비표준, `--remote-debugging-port` 필요 |
| 8 | Renderer v1 DOM Click (flat scan) | Run 버튼 미발견 | webview iframe 격리 |
### 🔑 핵심 전제
- Run/Accept 버튼은 **`vscode-webview://` origin의 격리된 iframe** 안에 있음
- 외부 workbench DOM에서 `querySelector`로 접근 **불가** (cross-origin)
- `<webview>.executeJavaScript()`**유일한 표준 관통 경로** (Electron API)
- AG 패치 후 **반드시 풀 프로세스 재시작** 필요 (Reload Window 불충분)
- **양쪽 HTML (workbench.html + workbench-jetski-agent.html) 모두 inline 패치** 필수
- HTML 패치 변경 시 **`%APPDATA%\Antigravity\CachedData` 삭제 필수** (V8 바이트코드 캐시 무효화)
- **CSP `script-src``'unsafe-inline'` 패치 필수** (없으면 인라인 스크립트 무조건 차단)
---
## Renderer / HTML 패치 관련 (2026-03-08~09)
### [2026-03-08] Antigravity Renderer Injection — Electron 캐시 차단
- **증상**: workbench.html, workbench-jetski-agent.html에 `<script>` 태그 추가 후 리로드해도 실행되지 않음
- **원인**: Electron의 V8 코드 캐시가 수정된 HTML을 무시하고 캐시된 버전을 서빙
- **해결**: 렌더러 인젝션 방식 **포기**. Extension Host에서 RPC 폴링 방식으로 전환
- **주의**: Antigravity는 `workbench-jetski-agent.html`을 사용 (Jetski = 내부 코드네임)
### [2026-03-08] Antigravity 승인 대기 = RUNNING (NOT IDLE)
- **증상**: IDLE 기반 승인 감지가 실제 승인 대기를 놓침
- **원인**: 승인 대기 시 세션 상태가 `CASCADE_RUN_STATUS_RUNNING` (IDLE 아님), `IDLE`은 대화 대기(notify_user 후)
- **해결**: `RUNNING + delta=0` (stall) 기반 감지로 전환. 6 polls (30초) 이상 FROZEN 시 pending 생성
- **주의**: Thinking/생성 중에도 `RUNNING + delta=0`이 발생 → `lastModifiedTime`으로 구분 시도했으나 불완전
### [2026-03-08] ResolveOutstandingSteps RPC — 승인이 아닌 취소!
- **증상**: Discord 승인 → `ResolveOutstandingSteps` 호출 → step이 취소됨
- **원인**: `ResolveOutstandingSteps`는 blocking steps를 "resolve" = **REJECT/CANCEL**, approve가 아님
- **해결**: `ResolveOutstandingSteps` 제거. `HandleCascadeUserInteraction``socket hang up`
- **주의**: KI에 "more reliable"로 기록되어 있으나 실제 동작은 cancel임. KI 업데이트 필요
### [2026-03-08] VS Code Accept Commands — Silent Success 문제
- **증상**: 4개 accept command 모두 OK(undefined) 반환하나 실제 승인 안 됨
- **원인**: webview에 활성 포커스가 필요. `panel.focus()`로는 충분하지 않음
- **해결**: **미해결**. Windows UI Automation 등 OS 레벨 접근 필요
- **주의**: reject commands는 동작함. accept만 focus 의존성 있음
### [2026-03-08] Multi-Window 세션 등록 경쟁 조건
- **증상**: 이 창(gravity_control)의 대화가 `#ag-variet_agent` 채널로 메시지 전달
- **원인**: `writeRegistration()`이 폴링 루프에서 호출 → 먼저 폴링한 확장이 세션을 자기 프로젝트로 등록
- **해결**: `writeRegistration`을 폴링에서 제거, `writeChatSnapshot`/`writePendingApproval`에서만 지연 호출
- **주의**: `GetAllCascadeTrajectories`는 모든 창의 세션을 반환하므로 세션→창 매핑은 불가능. 활동 기반 등록만 신뢰 가능
### [2026-03-08] 공유 렌더러 스크립트 파일 덮어쓰기 문제
- **증상**: DOM Observer 렌더러 스크립트가 잘못된 HTTP bridge 포트에 연결
- **원인**: 두 확장이 동일한 `ag-sdk-variet-gravity-bridge.js` 파일에 각자 포트를 씀 → 마지막 확장 것만 남음
- **해결**: `ag-bridge-ports.json`에 모든 확장의 port를 JSON으로 기록, 렌더러가 all ports를 순회하며 ping
- **주의**: 렌더러 스크립트 파일 경로는 SDK patcher namespace에 의해 고정 — 변경 불가
### [2026-03-08] workbench.html vs workbench-jetski-agent.html
- **증상**: 렌더러에서 `[GB Observer]` 로그가 전혀 안 나옴
- **원인**: DevTools가 `workbench.html`을 로드 — 스크립트 태그는 `workbench-jetski-agent.html`에만 패치됨
- **해결**: `workbench.html`에도 스크립트 태그 필요. Antigravity 재설치 후 SDK patcher가 올바르게 패치하도록 함
- **주의**: SDK patcher는 `both` HTML 파일을 패치하지만, 수동 수정은 Antigravity integrity check에 의해 되돌려질 수 있음
### [2026-03-08] product.json 체크섬 불일치 → 렌더러 스크립트 미로딩
- **증상**: `<script>` 태그가 HTML에 존재하고 .js 파일도 디스크에 있으나, 렌더러 콘솔에 스크립트 로그가 전혀 없음
- **원인**: Antigravity 재설치 시 `product.json`의 SHA256 체크섬이 원본으로 리셋됨. Extension이 HTML을 패치하지만 `IntegrityManager.suppressCheck()`를 호출하지 않아 체크섬 불일치. `vscode-file://` 프로토콜이 체크섬 불일치 파일을 무시하고 **원본 캐시 HTML**을 서빙
- **해결**: `product.json``checksums` 항목에서 수정된 파일(workbench.html, workbench-jetski-agent.html)의 SHA256 해시를 실제 파일 기준으로 업데이트. SDK `IntegrityManager.suppressCheck()` 호출 또는 수동 스크립트로 해결
- **주의**: Extension `setupApprovalObserver()``suppressCheck()` 호출을 영구 추가해야 재설치마다 반복 안 됨. 해시 = `base64(sha256(file)).replace(/=+$/, '')`
### [2026-03-08] vscode-file:// 프로토콜 — 커스텀 .js 파일 서빙 불가
- **증상**: `<script src="./ag-sdk-variet-gravity-bridge.js">` 태그가 HTML에 있으나 `net::ERR_FILE_NOT_FOUND` 발생, GB Observer 로그 전혀 없음
- **원인**: `vscode-file://` 프로토콜은 원본 배포에 포함된 파일만 서빙. Extension이 디스크에 쓴 커스텀 `.js` 파일은 프로토콜 레벨에서 차단됨
- **해결**: 외부 `<script src>` 참조 대신 **인라인 `<script>...코드...</script>`** 방식으로 HTML에 직접 삽입
- **주의**: `ag-bridge-ports.json`도 같은 이유로 XHR 로딩 불가. 모든 렌더러 스크립트/데이터는 HTML 인라인으로 전달해야 함
### [2026-03-08] Renderer 포트 디스커버리 — ag-bridge-ports.json XHR 실패
- **증상**: `[GB Observer] Port discovery timeout after 2min` — 렌더러가 bridge 포트를 찾지 못함
- **원인**: 렌더러 스크립트가 `./ag-bridge-ports.json`을 동기 XHR로 읽으려 하나, `vscode-file://` 프로토콜이 `.json` 파일 서빙 거부
- **해결**: (1) 프로젝트명 해시 기반 **결정론적 포트** 사용 (`gravity_control→34332`), (2) 스크립트 생성 시 포트를 `HARDCODED_PORT=${port}`로 직접 삽입
- **주의**: `server.listen(0)` 랜덤 포트 → 매 재시작마다 변경되어 렌더러와 불일치. 결정론적 포트는 `EADDRINUSE` 시 랜덤 폴백 필요
### [2026-03-08] GetCascadeTrajectorySteps — cascadeId 파라미터 발견
- **증상**: `GetCascadeTrajectorySteps``trajectoryId` 파라미터 → 500 "trajectory not found"
- **원인**: 파라미터명이 `trajectoryId`가 아니라 **`cascadeId`**. 값은 `GetAllCascadeTrajectories.trajectorySummaries`의 맵 키(세션 ID)
- **해결**: `{ cascadeId: sessionId }`로 호출 → 전체 step 배열 반환 성공
- **주의**: `latestToolCallStep` 필드는 `GetAllCascadeTrajectories` 응답에 **존재하지 않음** (KI 오류)
### [2026-03-08] Step 구조 — CORTEX_STEP_STATUS_WAITING 즉시 감지
- **증상**: stall-based 감지(100초)가 너무 느림
- **원인**: 이제 `GetCascadeTrajectorySteps`로 최신 step의 status를 직접 확인 가능
- **해결**: stall 5초 후 step probe → `CORTEX_STEP_STATUS_WAITING` 확인 → 즉시 pending 생성
- **Step 구조**: `{type: "CORTEX_STEP_TYPE_RUN_COMMAND", status: "CORTEX_STEP_STATUS_WAITING", metadata: {toolCall: {name, argumentsJson}}, runCommand, requestedInteraction}`
- **주의**: 775-step 하드 리밋은 여전히 존재. 긴 세션에서는 fallback(40초) 사용
### [2026-03-08] Extension 재설치 안전성 — 자동 패치 메커니즘
- **증상**: Antigravity 삭제 후 재설치 시 렌더러 스크립트가 동작 안 함
- **원인**: 재설치 시 HTML/product.json이 원본으로 리셋됨
- **해결**: Extension의 `setupApprovalObserver()`**자동으로** 모든 패치를 수행
- **주의**: 패치 후 반드시 **Antigravity 풀 재시작** 필요 (Reload Window 불가)
### [2026-03-08] Response 파일 Race Condition — DOM Observer 승인 실패
- **증상**: Discord에서 승인 → `[RESPONSE] renderer-handled approval` 로그 출력 → 실제 버튼 클릭 안 됨
- **원인**: `processResponseFile` (파일 감시자)이 response 파일을 즉시 삭제 → renderer의 `pollResponse`가 HTTP `GET /response/:rid`로 조회 시 파일 이미 없음
- **해결**: DOM observer 소스일 때는 response 파일을 삭제하지 않도록 수정. HTTP endpoint가 renderer에게 서빙한 후 삭제
- **주의**: non-DOM (stall/step_probe relay)는 watcher에서 삭제해도 됨
### [2026-03-08] Renderer 스크립트 소스 혼동 — 3곳의 코드
- **증상**: `extension.ts`에 BTN-DUMP 추가 → Reload 2번 → 콘솔에 안 나옴
- **원인**: renderer 코드가 **3곳**에 존재: (1) `extension.ts``generateApprovalObserverScript()` (소스), (2) `ag-sdk-variet-gravity-bridge.js` (배포됨, Reload시 소스에서 재생성), (3) `workbench-jetski-agent.html` inline (HTML, JS파일과 중복로드 방지됨)
- **해결**: 항상 `extension.ts``generateApprovalObserverScript()` 함수를 수정 → 컴파일 → 배포 → Reload
- **주의**: HTML inline은 JS파일이 먼저 로드되어 `window.__agSDK` 가드에 의해 실행 안 됨. 실제 실행되는 것은 JS파일 경로의 스크립트
---
## 승인 / RPC 관련 (2026-03-09)
### [2026-03-09] VS Code Accept — SDK 승인 명령이 AG에 미등록
- **증상**: Discord 승인 → `antigravity.terminalCommand.run` 등 7개 명령 → 모두 `command not found`
- **원인**: SDK(command-bridge.ts)에 정의된 7개 승인 명령이 현재 AG 빌드에 **등록되어 있지 않음**
- **해결**: Renderer DOM Click 구현 → v3 `deepFindButtons()` 업그레이드
- **주의**: `agentPanel.focus`도 미등록, `agentSidePanel.focus`만 존재
### [2026-03-09] Renderer DOM — webview iframe 격리 확인 + v3 deep traversal
- **증상**: Renderer trigger-click이 `document.querySelectorAll('button')`으로 버튼 검색 → Run 버튼 미발견
- **원인**: Run/Accept 버튼은 AG 채팅 webview iframe (`vscode-webview://` origin) 안에 렌더링
- **해결**: Renderer v3 `deepFindButtons()` 구현 (iframe contentDocument + webview.executeJavaScript + shadow DOM)
- **주의**: CDP(Chrome DevTools Protocol)는 **사용자 결정에 의해 명시적으로 거부됨**
### [2026-03-09] Deep Inspect HTTP Endpoint — curl로 DOM 분석 트리거
- **증상**: AG DevTools 콘솔에 붙여넣기 불가 (Chromium 보안 정책)
- **원인**: Electron 렌더러 DevTools에서 `allow pasting`이 SyntaxError 발생
- **해결**: Extension HTTP bridge에 `/deep-inspect` 엔드포인트 추가
- **주의**: 시작 3초 후 자동 실행 + curl로 재트리거 가능
### [2026-03-09] workbench.html inline 스크립트 미삽입 — jetski만 패치한 버그
- **증상**: AG 재시작 후 `/deep-inspect` timeout — renderer v3 스크립트 미로딩
- **원인**: `setupApprovalObserver()``workbench-jetski-agent.html`에만 inline 삽입
- **해결**: HTML 패치 로직을 **양쪽 모두 inline** 삽입으로 변경
- **주의**: **항상 양쪽 HTML을 동일하게 패치**
### [2026-03-09] V8 CachedData — 체크섬 정상이어도 스크립트 미실행
- **증상**: HTML 패치 + product.json 체크섬 일치 → 그런데도 renderer 스크립트 미실행
- **원인**: V8 바이트코드 캐시가 `vscode-file://` 프로토콜에도 적용
- **해결**: `%APPDATA%\Antigravity\CachedData\*` 전체 삭제 후 AG 풀 재시작
- **주의**: **HTML 패치 변경 시마다 CachedData 삭제 필수**
### [2026-03-09] CSP script-src — 인라인 스크립트 무조건 차단
- **증상**: HTML 패치 ✅, 체크섬 ✅, CachedData 삭제 ✅ — 그런데도 renderer 미실행
- **원인**: CSP `script-src``'unsafe-inline'` 없음
- **해결**: CSP `script-src``'unsafe-inline'` 추가
- **주의**: `style-src`에는 `'unsafe-inline'`이 있어 스타일은 동작 → 스크립트만 차단되는 것이 함정
### [2026-03-09] 중복 승인 요청 — DOM scan + Step probe 동시 발동
- **증상**: Discord에 같은 명령에 대해 승인 요청이 2개 도착
- **원인**: DOM Observer + Step probe가 동일 step에 대해 2개 파일 생성
- **해결**: `writePendingApproval()`에 15초 dedup 윈도우 추가
- **주의**: DOM scan은 제거 불가 — step probe가 감지 못하는 UI 버튼 존재
### [2026-03-09] Step probe reject → ResolveOutstandingSteps가 AI 작업 취소
- **증상**: Discord에서 거부 클릭 → AI의 현재 step뿐 아니라 진행 중인 작업 전체가 중단
- **원인**: step probe 경로의 `tryApprovalStrategies(approved=false)``ResolveOutstandingSteps` RPC 호출
- **해결**: step probe 경로에서 reject 시 `tryApprovalStrategies` 호출 제거
- **주의**: `ResolveOutstandingSteps`는 이름과 달리 "해결"이 아닌 "취소". 승인에 **절대 사용 금지**
### [2026-03-09] Pending 파일 무한 누적 — write_response 후 미삭제
- **증상**: `bridge/pending/` 디렉토리에 79개 이상의 .json 파일 누적
- **원인**: `write_response()`가 pending 파일을 삭제하지 않음
- **해결**: pending 파일 삭제 + 5분 age filter + 시작 시 cleanup
- **주의**: 봇 재시작 시 자동 정리
### [2026-03-09] Discord 승인 "Run" 표시 — DOM/step_probe 타이밍 불일치
- **증상**: Discord에 상세 명령어 대신 "Run"만 표시
- **원인**: DOM observer가 "Run" pending 생성 → 봇이 3초 후 전송 → step_probe MERGE가 10초 후 완료
- **해결**: step_probe가 기존 DOM pending에 MERGE + 봇에서 짧은 명령어 대기
- **주의**: MERGE 타이밍은 최소 10초
### [2026-03-09] DOM observer false positive — Proceed/Continue/Open 버튼 오감지
- **증상**: 작업 전환 시 승인 요청 없는데도 Discord에 승인 요청 도착
- **원인**: DOM observer가 AG UI의 PathsToReview 버튼을 승인 버튼으로 오인
- **해결**: FALSE_POSITIVE_RE 필터 추가 + sessionStalled 조건
- **주의**: 렌더러 인라인 스크립트는 VSIX 빌드 필요
### [2026-03-09] Discord ApprovalView timeout — 5분 후 버튼 무응답
- **증상**: 시간이 지난 후 Discord 승인 버튼 클릭해도 반응 없음
- **원인**: Discord.py View의 기본 timeout이 300초
- **해결**: timeout을 1800초로 증가
- **주의**: Discord View timeout은 서버 재시작 후만 적용
---
## 멀티 프로젝트 / Pending 관련 (2026-03-10)
### [2026-03-10] DOM Observer 승인 ENOENT — response 파일 Race Condition
- **증상**: Discord에서 ✅승인 → `ENOENT: response/xxx.json` 에러
- **원인**: `processResponseFile()`이 response 파일 즉시 삭제 → renderer 조회 실패
- **해결**: DOM observer 경로에서 response 파일을 삭제하지 않고 HTTP handler가 서빙 후 삭제
- **주의**: DOM observer와 step_probe 두 경로가 독립적
### [2026-03-10] Allow Once + Allow This Conversation — 별개 pending으로 분리되는 문제
- **증상**: 파일 접근 시 Discord에 2개 별도 메시지로 도착
- **원인**: renderer `scan()`이 한 사이클에 한 버튼만 처리
- **해결**: `findButtonContainer()` + `collectSiblingButtons()`로 그룹화, `buttons` 배열 전송
- **주의**: `buttons` 배열이 없는 legacy pending은 기존 2버튼(✅승인/❌거부)으로 표시
### [2026-03-10] step_probe verbosity — argumentsJson 미포함
- **증상**: Discord 승인 메시지에 파라미터 이름만 표시, 값 없음
- **원인**: `GetCascadeTrajectorySteps` 기본 verbosity에서 `argumentsJson` 빈 문자열
- **해결**: `verbosity: 1` (DEBUG) 추가
- **주의**: verbosity 0=NORMAL (키만), 1=DEBUG (값 포함)
### [2026-03-10] 파일 권한 응답 — "unexpected user interaction type: not file permission"
- **증상**: `.agents` 디렉토리 접근 시 에러
- **원인**: 봇이 file_permission pending에 잘못된 interaction type 전송
- **해결**: step_type별 올바른 RPC 라우팅
- **주의**: file_permission과 run_command가 동시에 대기 시 올바른 RPC 라우팅 필수
### [2026-03-10] active_project.lock — 멀티 프로젝트 동시 사용 차단
- **증상**: 여러 AG 프로젝트 실행 시 첫 번째만 bridge 연결
- **원인**: `active_project.lock` 파일이 단일 프로젝트만 허용
- **해결**: lock 메커니즘 완전 제거, `project_name` 필터 기반으로 전환
- **주의**: bridge 격리는 `project_name` 필드 기반 filtering으로 충분
### [2026-03-10] step_probe file_permission — 3-button 미주입
- **증상**: Discord에 3개 선택지 대신 2개만 표시
- **원인**: `writePendingApproval()`이 buttons 배열 미주입
- **해결**: `step_type === 'file_permission'`일 때 자동 3-button 배열 주입
- **주의**: DOM observer 경로는 기존 command 텍스트 기반 감지 유지
### [2026-03-10] GetAllCascadeTrajectories — 크로스 윈도우 세션 가로채기
- **증상**: Deriva AG에서 대화 시작 → gravity_control 채널에 Deriva 내용이 릴레이
- **원인**: `GetAllCascadeTrajectories`가 모든 인스턴스의 세션 반환
- **해결**: `workspaces[0].workspaceFolderAbsoluteUri` 비교하여 자기 workspace 세션만 처리
- **주의**: workspace URI normalize 필수 (protocol strip, %3A decode, 슬래시 통일, lowercase)
### [2026-03-10] 크로스 프로젝트 Response Watcher 우회
- **증상**: Deriva 세션 승인 시도가 gravity_control에서 실패
- **원인**: pending 파일 삭제 후 response watcher가 project_name 체크 건너뜀
- **해결**: response JSON의 project_name으로 fallback 필터
- **주의**: response 데이터 자체에 project_name 필수
### [2026-03-10] file_permission — write 도구 3-button 미주입
- **증상**: `replace_file_content` 등 파일 수정 시 Discord에 2개만 표시
- **원인**: step_probe의 file_permission 도구 리스트에 write 도구 누락
- **해결**: write 도구를 file_permission 리스트에 추가
- **주의**: AG가 파일 접근 권한을 요청하는 모든 도구는 이 리스트에 포함필요
### [2026-03-10] bestSession IDLE 고착 — RUNNING 세션 못 잡는 버그
- **증상**: 새 대화 시작 → bridge가 구 IDLE 세션만 추적
- **원인**: `bestSession` 선택이 `lastModifiedTime`만 비교
- **해결**: RUNNING 세션이 IDLE보다 항상 우선
- **주의**: Reload Window로도 해결되지만 근본적으로는 RUNNING 우선 로직 필요
### [2026-03-10] Bot IDLE 채널 자동 생성 — 불필요한 Discord 채널 증식
- **증상**: 봇 시작 시 모든 등록된 프로젝트의 채널을 자동 생성
- **원인**: `pending_approval_scanner`가 매 사이클마다 채널 생성
- **해결**: 자동 채널 생성 루프 제거, on-demand 생성
- **주의**: `_get_channel()`은 이미 on-demand 생성 로직 포함
### [2026-03-10] Reload Window 후 세션 stale
- **증상**: Reload Window 후 세션이 IDLE/구 stepCount로 고정
- **원인**: LS 프로세스는 유지되어 trajectory tracker 캐시 미갱신
- **해결**: AG 완전 종료 → 재실행 (Full restart)
- **주의**: Extension 코드 변경 후 배포 시 Full restart 권장
### [2026-03-10] start_bot.bat — Windows Store Python 스텁 우선 실행
- **증상**: `start_bot.bat` 실행 시 스텁이 먼저 실행
- **원인**: `where python`이 Windows Store의 Python 스텁을 먼저 찾음
- **해결**: conda 경로를 우선 확인
- **주의**: Windows 10/11에서 App Aliases의 python.exe가 PATH에 기본 포함
### [2026-03-10] VSIX 빌드 — SDK JS 파일 미포함 (require 실패)
- **증상**: Extension 활성화 후 `SDK not initialized`
- **원인**: TypeScript 컴파일러가 `.js` 파일을 `out/`에 복사하지 않음
- **해결**: `compile` 스크립트에 복사 단계 추가 (`src/sdk/``out/sdk/`)
- **주의**: VSIX 패키징은 `out/sdk/`를 포함함. 문제는 빌드 단계 복사 누락
### [2026-03-10] SDK _findLSProcess — 대소문자 구분 workspace hint 매칭 실패
- **증상**: variet-agent AG에서 Discord에 신호 미도달
- **원인**: SDK가 workspace hint를 대소문자 구분으로 비교
- **해결**: `fixLSConnection()` 함수로 대소문자 무시 비교 + 재연결
- **주의**: 각 AG 창마다 별도 LS 프로세스 존재 (workspace_id로 구분)
---
## 환경변수 / 설정 관련 (2026-03-11)
### [2026-03-11] config.py BRAIN_PATH — `.env` 빈 문자열 → CWD 해석 버그
- **증상**: 봇이 Extension의 snapshot/pending을 전혀 읽지 못함
- **원인**: `.env``BRAIN_PATH=` (빈 값)이면 빈 문자열 반환
- **해결**: `os.getenv("BRAIN_PATH") or default` 패턴
- **주의**: `os.getenv(key, default)`는 빈 값이라도 default 미사용
### [2026-03-11] Extension DEDUP MERGE — 크로스 프로젝트 pending 오염
- **증상**: `#ag-lifetimepd` 채널에 variet_agent의 승인 요청 표시
- **원인**: DEDUP 로직이 `project_name`을 체크하지 않음
- **해결**: 3곳 dedup 조건에 `project_name` 가드 추가
- **주의**: 모든 Extension 인스턴스가 동일한 `bridge/pending/` 디렉토리 공유
### [2026-03-11] Collector 동기 HTTP — aiohttp 전환
- **증상**: Collector가 이벤트 루프 전체 블로킹
- **원인**: `urllib.request.urlopen()` 사용 (blocking I/O)
- **해결**: `aiohttp.ClientSession` 기반 비동기 전환
- **주의**: `import aiohttp`는 lazy
---
## Rate Limit / 무한 루프 관련 (2026-03-12)
### [2026-03-12] RemoteTransport 429 무한 루프 — Extension 크래시 + AG 먹통
- **증상**: `429 Rate limited` 로그가 초당 수십 건 무한 반복
- **원인**: 3가지 복합 (백오프 없음 + 개별 HTTP 요청 + 공격적 rate limit)
- **해결**: 지수 백오프 + `Retry-After` 지원 + rate limit 완화
- **주의**: AG 먹통은 봇 자체가 유발한 문제
### [2026-03-12] workbench.html 0-byte 파괴 — AG 새 창 먹통
- **증상**: AG 새 창 열면 화면 먹통
- **원인**: 3개 Extension 인스턴스가 동시에 workbench.html 읽기/쓰기 → 0 bytes로 덮어쓰기
- **해결**: pre-patch backup(.orig) + 구조 검증 + 자동 복원
- **주의**: 멀티 윈도우 환경에서 HTML 패치 race condition은 파일 잠금 없이 완전 해결 불가
### [2026-03-12] workbench.html 크로스 복원 — CSS 미로딩으로 레이아웃 깨짐
- **증상**: 아이콘은 보이지만 레이아웃 완전 깨짐
- **원인**: workbench.html을 jetski HTML에서 복원할 때 CSS 교체 누락
- **해결**: 파일별 `requiredMarker` 검증 + `.orig` 백업 + 자동 복원
- **주의**: **workbench.html과 workbench-jetski-agent.html은 교환 불가능**
### [2026-03-12] Collector 단일 프로젝트 폴링 — 멀티 프로젝트 command 전달 불가
- **증상**: Deriva AG IDE에 명령 전달되지 않음
- **원인**: Collector가 단일 프로젝트만 폴링
- **해결**: `_discover_local_projects()`로 모든 프로젝트 폴링
- **주의**: `/api/commands/all` 엔드포인트는 크로스 PC 명령 오염을 유발
### [2026-03-12] RemoteTransport backing off 무한 반복
- **증상**: IDLE 시 `backing off 1s` 경고가 영구 반복
- **원인**: 3가지 구조적 결함 (즉시 리셋 + 불필요 요청 + asyncio burst)
- **해결**: 연속 5회 성공 후 절반 감소 + adaptive 간격 + 루프 stagger
- **주의**: `_reset_backoff()` 즉시 리셋 패턴은 다중 소비자 환경에서 **절대 사용 금지**
---
## DEDUP / 크로스 세션 관련 (2026-03-15)
### [2026-03-15] DEDUP step_index 크로스 세션 충돌 — 승인 신호 누락
- **증상**: WAITING step 감지 → pending 미생성 → 10분+ 대기
- **원인**: DEDUP 로직이 `conversation_id`를 비교하지 않음
- **해결**: DEDUP 조건에 `conversation_id` 가드 추가
- **주의**: `project_name` 가드만으로는 불충분 — 같은 Extension이 여러 세션을 볼 수 있음
### [2026-03-15] Discord Gateway MESSAGE_CREATE 중복 — embed 이중 전송
- **증상**: Discord 명령 시 동일 embed가 2개 전송
- **원인**: Discord Gateway가 WebSocket 불안정 시 이벤트 중복 전달
- **해결**: `on_message``_processed_message_ids` dedup 추가
- **주의**: Gateway reconnection, RESUME 실패 시 발생 빈도 증가
### [2026-03-15] HTML 패치 멀티 인스턴스 race condition — 화면 파괴
- **증상**: Extension 패치 후 AG 재시작 시 전체 화면 날아감
- **원인**: 2+ Extension 인스턴스가 동시에 같은 HTML에 readFileSync/writeFileSync
- **해결**: `.patch-lock` 파일 기반 cross-instance lock 추가
- **주의**: Lock은 "방지", .orig 백업은 "복구". 둘 다 유지
### [2026-03-15] 로컬 승인 ↔ Discord 승인 교차 race condition
- **증상**: AG에서 직접 Run 클릭 후 Discord 승인 요청이 "완료됨" 표시 안 됨
- **원인**: auto_resolve가 Discord에 알림 없음 + processResponseFile 상태 미체크
- **해결**: writeChatSnapshot 추가 + 상태 확인 후 skip + _approval_messages dict
- **주의**: processResponseFile L2534의 리셋이 핵심 gate
### [2026-03-15] 크로스 프로젝트 DEDUP MERGE — Deriva→gravity_control 오염
- **증상**: Deriva의 데이터가 gravity_control pending에 MERGE됨
- **원인**: MERGE 조건에 `project_name` 가드 없음
- **해결**: MERGE 조건에 `project_name` 추가
- **주의**: `bridge/pending/` 디렉토리는 모든 Extension 인스턴스가 공유
### [2026-03-15] Double-Fire Auto-Approve — AI 세션 중단
- **증상**: auto-approve ON 시 AI 세션이 간헐적으로 중단
- **원인**: Extension auto-approve 경로 + Bot auto-approve 경로 동시 실행 → 2번 RPC
- **해결**: Extension auto-approve 경로 제거. Bot만 담당
- **주의**: 단일 경로 원칙 유지
### [2026-03-15] DOM Observer "Deny" False Positive — Auto-approve 세션 크래시
- **증상**: auto-approve ON 시 "Deny" command가 자동 승인됨 → 세션 크래시
- **원인**: DOM observer가 Deny를 독립 pending으로 생성. default 분기로 잘못된 RPC 전송
- **해결**: FALSE_POSITIVE_RE에 Deny/Allow Once 등 추가 + reject-word 차단 가드
- **주의**: VSIX 빌드 → AG 풀 재시작 필요
### [2026-03-15] PATS 배열 Deny 트리거 — 근본 수정
- **증상**: Deny가 주 트리거로 사용됨
- **원인**: PATS 배열에 Deny 패턴 포함
- **해결**: PATS에서 거절/보조 버튼 제거. 긍정 버튼만 그룹 트리거
- **주의**: PATS = "그룹 생성 트리거", ALL_ACTION_RE = "형제 수집 패턴"
### [2026-03-15] Auto-Resolved 채팅 폭주 — 루프 내 writeChatSnapshot
- **증상**: "✅ AG에서 직접 승인됨" 메시지가 반복 전송
- **원인**: 루프 내부에서 매 파일마다 writeChatSnapshot 호출
- **해결**: 루프 바깥에서 1회 + conversation_id 조건 추가
- **주의**: 외부 시스템에 메시지 보낼 때는 반드시 루프 바깥에서 집계 후 1회 발송
### [2026-03-15] projectName=default 승인 오발
- **증상**: workspace 없는 AG 창이 다른 프로젝트의 WAITING을 감지
- **원인**: `detectProjectName()`이 workspace 없으면 "default" 반환
- **해결**: `projectName === 'default'`이면 pending 생성/auto-approve 억제
- **주의**: Empty Window에서는 bridge 기능을 최소화
### [2026-03-15] 이전 분석 오판(False Positive) — 교훈
- **증상**: P0/P1으로 보고한 문제들이 이미 방어되고 있었음
- **원인**: 로컬 코드 스니펫만 보고 판단
- **해결**: 전체 Flow 추적으로 교차 검증
- **주의**: **코드 감사 시 반드시 producer→transport→consumer→side effects 전체 경로를 추적**
---
## processResponseFile 상태 관리 (2026-03-16)
### [2026-03-16] processResponseFile 상태 리셋 — 무한 루프 vs auto_resolve 회귀
- **증상**: Discord 승인 후 같은 step에 대해 pending이 반복 생성 → 무한 auto-approve 루프
- **원인**: processResponseFile이 무조건 리셋 → step_probe가 같은 WAITING step을 새 step으로 착각
- **해결**: `sawRunningAfterPending = true`만 설정. lastPendingStepIndex와 stallProbed 유지
- **주의**: **processResponseFile의 상태 리셋은 sawRunningAfterPending = true만 설정**. `docs/approval-flow.md` 참조
### [2026-03-16] recentPendingSteps 메모리 dedup
- **증상**: pending 파일 삭제 → 같은 step_index로 새 pending 생성
- **원인**: writePendingApproval()의 dedup이 파일 존재 여부에만 의존
- **해결**: `recentPendingSteps` Map (TTL 60초) 추가
- **주의**: DOM observer HTTP 경로는 이 메모리 dedup 미적용
### [2026-03-16] 멀티 프로젝트 동시 신호 정지 — Scanner O(N) Discord API 병목
- **증상**: 여러 프로젝트 동시 pending → 모든 프로젝트 신호 전달 정지
- **원인**: scanner가 1 tick에 모든 pending 순차 처리 → Discord 429 rate limit
- **해결**: `discord.utils.get(guild.channels)` 캐시 + per-tick cap (5건)
- **주의**: `guild.channels`는 discord.py 내부 캐시
---
## Diff Review 관련 (2026-03-16)
### [2026-03-16] step_type 매핑 버그 — write_to_file이 file_permission으로 잘못 매핑
- **증상**: 코드 편집 승인 시 잘못된 RPC 전송
- **원인**: 쓰기 도구가 읽기 도구와 함께 `file_permission`으로 매핑
- **해결**: 읽기/쓰기 도구 분리, 쓰기는 `code_edit` step_type 사용
- **주의**: AG는 대부분 파일 쓰기에 WAITING 안 만듦
### [2026-03-16] diff_review isDirty 실패 — AG diff는 VS Code dirty 아님
- **증상**: Accept 클릭 → `isDirty` 문서 0개 → 효과 없음
- **원인**: AG stacked code review는 VS Code `isDirty`와 무관
- **해결**: `AcknowledgeCascadeCodeEdit` RPC → fallback으로 VS Code 커맨드
- **주의**: diff_review pending에 `modified_files``edit_step_indices` 필수
### [2026-03-16] diff_review pending 순서 — AI 응답보다 먼저 Discord 도착
- **증상**: diff_review 버튼이 먼저, AI 응답 텍스트가 나중
- **원인**: pending_approval_scanner가 chat_snapshot_scanner보다 먼저 fire
- **해결**: diff_review pending 생성을 `setTimeout(8000)`으로 지연
- **주의**: 8초는 전체 전파 경로 고려
### [2026-03-16] diff_review AcknowledgeCascadeCodeEdit steps=[] — Collector pending 삭제 race
- **증상**: Accept all 클릭 → RPC SUCCESS → diff review 바 안 사라짐
- **원인**: Collector가 pending 파일 즉시 삭제 → Extension이 메타데이터 못 읽음
- **해결**: `diffReviewMetadata` 인메모리 Map 추가
- **주의**: Extension Reload 시 소실되지만 새 diff_review는 정상 동작
### [2026-03-16] AcknowledgeCascadeCodeEdit SUCCESS → diff review bar 미해제 — 잘못된 RPC 메서드명
- **증상**: RPC SUCCESS 반환 → diff review bar 여전히 표시
- **원인**: RPC 메서드명 자체가 틀렸음 (`AcknowledgeCascadeCodeEdit` → 실제는 `acknowledgeCodeActionStep`)
- **해결**: `agentAcceptAllInFile` / `agentRejectAllInFile` VS Code 커맨드 사용
- **주의**: AG의 RPC는 잘못된 메서드명도 에러 없이 `{}` 반환. **RPC `{}`는 실패로 간주해야 함**
### [2026-03-16] diff_review RPC 3개 전략 모두 dead-end
- **증상**: 3개 RPC 전략 모두 실패
- **원인**: (1) submitCodeAcknowledgement 미등록, (2) acknowledgeCodeActionStep 404, (3) AcknowledgeCascadeCodeEdit no-op
- **해결**: VS Code 커맨드 기반 (`agentAcceptAllInFile` / `agentRejectAllInFile`)
- **주의**: **diff_review를 RPC로 해결하려는 시도 모두 실패 확정. VS Code 커맨드 기반만 유효**
### [2026-03-16] AG 소스 역분석 — diff review 내부 동작 체인
- **증상**: Accept all / Reject all 내부 동작을 재현해야 함
- **원인**: AG 공식 API/문서 없음
- **해결**: AG 설치 경로의 JS 소스에서 역분석
- **주의**: minified JS에서 변수명은 버전마다 변경됨
### [2026-03-16] diff_review 이중 승인 요청 — DOM observer가 Accept/Reject 버튼 캡처
- **증상**: diff_review 외에 별도 "Accept"/"Reject" pending 도착
- **원인**: `openReviewChanges` 커맨드가 diff UI 패널 → DOM observer 감지
- **해결**: FALSE_POSITIVE_RE에 Accept/Reject all 추가
- **주의**: diff review bar 버튼은 전용 시스템에서 처리
### [2026-03-16] diff_review가 brain/ artifact에도 트리거
- **증상**: task.md만 수정해도 "코드 리뷰" pending 생성
- **원인**: diff_review 감지 로직이 모든 수정 파일 추적
- **해결**: `.gemini/antigravity/brain/` 경로 파일 필터링하여 제외
- **주의**: 코드 파일 + brain artifact 혼합 시 코드 파일만 diff_review
---
## WS Hub 전환 관련 (2026-03-16~17)
### [2026-03-16] !auto 이중 메시지 — Extension echo + Bot embed
- **증상**: `!auto` 토글 시 메시지 2개 표시
- **원인**: Bot embed + Extension writeChatSnapshot echo
- **해결**: Extension의 `!auto` handler에서 writeChatSnapshot echo 제거
- **주의**: Bot 재시작 시 auto_approve_projects 초기화 → 수동 모드 복귀
### [2026-03-16] 병렬 WAITING step 누락 — step_probe break문
- **증상**: 병렬 tool call 시 1개만 승인 요청 도착
- **원인**: step_probe 루프에서 `break`문이 첫 번째 WAITING 후 종료
- **해결**: `break` 제거, 모든 WAITING step에 pending 생성
- **주의**: 중복 방지는 `recentPendingSteps` Map이 처리
### [2026-03-16] Bot chat_snapshot 전송 로깅 부재
- **증상**: 전송 성공/실패를 Bot 로그에서 확인 불가
- **원인**: `channel.send()` 성공 후 INFO 로그 없음
- **해결**: 전송 성공/실패 로그 추가
- **주의**: `_get_channel()` 실패 시 WARNING은 이전에도 있었음
### [2026-03-16] 크로스 프로젝트 이벤트 폭주 — Watcher/Collector 무필터
- **증상**: /start 시 타 프로젝트 알림 유입
- **원인**: watcher.py가 brain/ 전체를 감시
- **해결**: `_is_my_session()` 필터 + 이벤트 전달 필터 추가
- **주의**: 미등록 세션은 allow-through 방식
### [2026-03-16] pending 파일 139개 누적 — 정리 로직 부재
- **증상**: bridge/pending/에 139개 파일 누적
- **원인**: auto_resolved/expired 파일을 아무도 삭제 안 함
- **해결**: Collector에서 auto_resolved/expired 전달 후 삭제 + 10분 자동 삭제
- **주의**: startup_pending은 정리 대상에서 제외
### [2026-03-17] NPM WebSocket 프록시 — Upgrade 헤더 미전달
- **증상**: `wss://ag.variet.net/ws` 연결 시 HTTP 400
- **원인**: Nginx Proxy Manager에 WebSocket Support 미활성화
- **해결**: NPM 대시보드에서 Websockets Support 체크
- **주의**: 새 프록시 호스트 생성 시 반드시 확인
### [2026-03-17] WS auth_fail 무한 재연결 — _cleanup() close 이벤트
- **증상**: Auth failed 후 60초마다 재연결 반복
- **원인**: `_cleanup()`이 ws.close() → close 이벤트 → _scheduleReconnect() 체인
- **해결**: `this.shouldReconnect = false``_cleanup()` 이전에 설정
- **주의**: `_cleanup()`은 이벤트 핸들러를 트리거하므로 상태 변경은 반드시 호출 전에
### [2026-03-17] initStepProbe workspaceUri 누락 — 세션 감지 완전 불능
- **증상**: POLL alive만 출력, SESSION-FILTER/SNAPSHOT 없음
- **원인**: `initStepProbe()` 호출 시 필수 필드 미전달 + `as` 캐스트가 런타임 검증 없음
- **해결**: `workspaceUri`, `diffReviewMetadata: new Map()` 추가
- **주의**: TypeScript `as` 캐스트는 런타임 검증 없음
### [2026-03-17] WS 명령어 에코 릴레이 — Discord 메시지 2번 표시
- **증상**: Discord 메시지 입력 → 같은 메시지가 다시 표시
- **원인**: handleWSCommand에서 `recentDiscordSentTexts`에 마킹 안 함
- **해결**: `recentDiscordSentTexts.set()` 추가
- **주의**: 파일 기반 경로에는 이미 마킹 있었음
### [2026-03-17] writeRegistration 이중 쓰기 — WS 전송 후 파일도 작성
- **증상**: WS 상태에서도 register/ 파일 생성
- **원인**: WS 전송 후 `return` 없이 파일 쓰기 실행
- **해결**: WS 전송 후 `return` 추가
- **주의**: 새 WS 전송 함수 추가 시 file fallback과 상호 배타적 `return` 확인

View File

@@ -1,263 +1,629 @@
# Known Issues & Lessons Learned # 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>슂.
> <EFBFBD><EFBFBD>뀡 醫낅즺 <20><20>깉濡<EAB989> 諛쒓껄<EC9293><20><EFBFBD>뒋瑜<EB928B> <20><20><EFBFBD><EFBFBD>뿉 異붽<E795B0><EBB6BD><EFBFBD><EFBFBD><EFBFBD>떎.
# Known Issues & Lessons Learned
> **씠 뙆씪 SSOT(Single Source of Truth)엯땲떎.**
> 뵒踰꾧퉭씠굹 援ы쁽 쟾뿉 **諛섎뱶떆** 씠 뙆뙆씪쓣 솗씤븯꽭슂.
> 꽭뀡 醫낅즺 떆 깉濡 諛쒓껄맂 씠뒋瑜 씠 뙆뙆씪뿉 異붽빀땲떎.
> [!TIP]
> 빐寃 셿猷뚮맂 怨쇨굅 씠뒋뒗 [`known-issues-archive.md`](file:///c:/Users/Variet-Worker/Desktop/gravity_control/.agents/references/known-issues-archive.md)뿉 蹂닿릺뼱 엳뒿땲떎.
> 鍮꾩듂븳 臾몄젣媛 옱諛쒗븯硫 archive뿉꽌 寃깋븯꽭슂.
### [2026-04-19] [Observer] ★ Accept all 버튼이 `<span>`으로 렌더링 — Observer 감지 실패 (v0.5.101)
- **증상**: "Accept all" diff review 버튼이 화면에 보이지만 Observer가 감지하지 못함. Discord에 "Accept all" 자동 승인 알림이 안 옴.
- **원인**: AG Native UI 업데이트로 "Accept all"이 `<button>`이 아닌 `<span class="cursor-pointer">`로 렌더링됨. Observer의 `allBtns = querySelectorAll('button, [role="button"]')`에 span이 포함되지 않음. ACCEPT-SCAN 로그: `tag=SPAN cls=hover:text-ide-button-hover-color cursor-po txt=Accept all`.
- **해결 (v0.5.101)**: `allBtns` 선택자에 `span.cursor-pointer` 추가.
- **주의**: observer-dev-guide 섹션 3.3 "Accept all — Observer 접근 불가"는 outdated. UI 변경으로 chat panel footer에 Accept all이 표시됨. 가이드 업데이트 필요.
### [2026-04-19] [Bridge] ★ auto-approve response 파일에 `_from_ws` 마커 누락 — Observer polling 실패 (v0.5.103)
- **증상**: Observer가 "Accept all"을 감지하고 bridge가 자동 승인했지만, Observer의 `pollResponseGroup` GET `/response/{rid}`가 항상 `{waiting: true}` 반환. 버튼 클릭이 실행되지 않음.
- **원인**: http-bridge의 auto-approve 경로에서 response JSON 파일에 `_from_ws: true` 마커가 없음 → `processResponseFile`(response watcher)이 Observer보다 먼저 파일을 읽고 삭제 → Observer polling 시 파일 부재. known-issues [2026-04-18] WS response 삭제 버그와 동일 패턴.
- **해결 (v0.5.103)**: auto-approve response에 `_from_ws: true` + `_auto_approve_ttl` 마커 추가.
- **주의**: **response 디렉토리에 파일을 쓰는 모든 경로**는 반드시 `_from_ws: true` 마커를 포함해야 함. processResponseFile이 먼저 소비하는 race condition 항상 존재.
### [2026-04-18] [Extension] ★ WS response 파일이 processResponseFile에 의해 삭제 → Observer pollResponseGroup 실패 (v0.5.78)
- **증상**: `!auto` Retry가 작동하지 않음. Observer가 `/response/{rid}`를 폴링하지만 항상 `{waiting: true}` 반환.
- **원인**: extension.ts의 WS 응답 핸들러가 `response/{rid}.json` 파일 작성 → 300ms 후 response watcher(`processResponseFile`)가 파일 감지 → pending 파일이 없어 `isDomObserver=false``fs.unlinkSync()` 실행 → Observer가 폴링할 때 파일이 이미 삭제됨.
- **해결 (v0.5.78)**: WS 응답 파일에 `_from_ws: true` 마커 추가. `processResponseFile`에서 `_from_ws` 파일 스킵 (WS 핸들러에서 이미 `tryApprovalStrategies` 실행하므로 중복 방지도 함께 해결).
- **주의**: http-bridge의 `_handlePending`는 pending 파일을 생성하지 않음 (WS 전송만 수행). 따라서 `processResponseFile``isDomObserver` 판별이 실패함. WS 경로로 들어오는 모든 response는 반드시 마커로 구분해야 함.
### [2026-04-18] [Extension] ★ extractContextFromNearby 조상 탐색만으로는 명령어 추출 불가 (v0.5.79)
- **증상**: Discord auto-approve 알림에 "Always run"만 표시되고 실제 명령어가 안 보임. depth를 10으로 늘려도 동일.
- **원인**: AG Native DOM 구조에서 "Always run" 버튼은 `footer` 내부에 있고, 실제 명령어(`pre.font-mono`)는 `footer`**형제(sibling)** 요소에 있음. 조상 탐색(parentElement)으로는 도달 불가. trail: `d0:button → d1:div → d2:footer` (footer.parentElement가 null).
- **해결 (v0.5.79)**: `extractContextFromNearby`에 형제 탐색 로직 추가. 각 depth에서 `node.parentElement.children`을 순회하며 `pre.font-mono, pre, code`를 찾음 → `CONTEXT-OK src=sibling` 성공.
- **주의**: Observer 코드 변경은 HTML 인라인 스크립트이므로 AG 2번 재시작(Quit + Relaunch × 2) 필요.
### [2026-04-18] [Extension] Thinking 블록이 AI 응답으로 릴레이됨
- **증상**: AI의 내부 사고 과정(thinking/reasoning)이 Discord에 릴레이됨.
- **원인**: Observer의 `scanChatBodies``.leading-relaxed.select-text` 블록을 전부 캡처하는데, thinking 블록도 이 셀렉터에 매칭됨.
- **해결**: thinking 블록의 조상에 `max-h-[200px]` 클래스가 있는지 확인하여 필터링.
- **주의**: AG UI 업데이트로 thinking 블록의 클래스가 변경될 수 있음.
- **증상**: Step Probe의 RT-CAPTURE, HB-CAPTURE가 현재 대화 중에 전혀 발동하지 않음. POLL에서 `status=IDLE, steps=928, delta=0` 고정. Heartbeat probe에서도 `real=928 known=928` 불변.
- **원인**: AG Native의 `GetCascadeTrajectorySteps` API는 **cascade가 완전히 종료(IDLE 전환)된 후에만** 새 step을 반환합니다. 진행 중인 cascade에서는 step count가 동결됩니다. `GetAllCascadeTrajectories``stepCount`도 마찬가지.
- **영향**: Step Probe 기반의 모든 실시간 캡처(RT-CAPTURE, HB-CAPTURE, USER_INPUT 감지)가 **구조적으로 불가능**. Observer DOM이 유일한 실시간 데이터 경로.
- **해결**: Observer DOM 기반 chat relay를 재활성화 (v0.5.72). Step Probe는 cascade 완료 후 보완 용도로만 사용.
- **주의**: 이 제약은 AG Native 아키텍처의 근본적 특성. Polling 주기나 heartbeat 빈도를 변경해도 해결 불가. **실시간 릴레이는 반드시 Observer DOM 경로를 사용해야 함.**
- **참조**: `.agents/references/relay-architecture.md` (상세 분석)
### [2026-04-18] [Extension] Observer의 /pending POST에 명령어 컨텍스트가 없음 (Always run만 전달)
- **증상**: Discord auto-approve 알림에 "Always run"만 표시되고 실제 명령어가 안 보임.
- **원인**: Observer가 `/pending` POST 시 `command: "Always run"`, `description: "Always run"`만 보냄. `extractContextFromNearby(btn)`이 버튼 주변 DOM에서 유의미한 텍스트를 찾지 못함.
- **해결 (부분)**: v0.5.69에서 bridge/pending/ 디렉토리의 최신 Step Probe pending 파일에서 명령어를 읽는 fallback 추가. Step Probe pending이 있을 때만 작동.
- **주의**: Step Probe WAITING 감지가 진행 중 cascade에서 불가하므로 (위 이슈 참조), 현재 대부분의 경우 fallback도 실패. Observer의 DOM 컨텍스트 추출 개선이 필요.
### [2026-04-18] [Extension] Observer의 사용자 메시지 셀렉터 미매칭
- **증상**: 사용자가 AG에서 입력한 메시지가 Discord에 전혀 전달되지 않음.
- **원인**: Observer의 셀렉터(`.text-ide-message-block-user-color`, `[data-message-role="user"]` 등)가 AG Native DOM에서 매칭되지 않음. AI 응답만 `.leading-relaxed.select-text`로 매칭됨.
- **해결**: DOM 덤프에서 사용자 메시지 블록의 실제 CSS 클래스를 식별 후 셀렉터 추가 필요.
- **주의**: Step Probe의 USER_INPUT 캡처도 진행 중 cascade에서 불가 (위 이슈 참조).
### [2026-04-16] [Extension] ★ AG Native 세션 AI 응답이 Discord에 CSS로 전달됨 (v0.5.52 수정, #632)
- **증상**: Discord에 AI 대화 응답 대신 **CSS 스타일시트 코드** (`remark-github-blockquote-alert/alert.css`)가 전달됨. `scanChatBodies()` → POST /chat 경로는 작동하지만 내용이 CSS.
- **원인**: `extractCleanStepText()`에서 clone한 DOM에서 버튼/SVG/아이콘은 제거하지만 **`<style>` 태그는 제거하지 않음**. AG Native 마크다운 렌더러가 `.leading-relaxed.select-text` 내부에 `<style>` 블록을 주입하는데, 이 CSS textContent가 AI 응답 텍스트로 추출됨.
- **해결 (v0.5.52)**: `extractCleanStepText()` 최상단에 `clone.querySelectorAll('style, script, noscript, link[rel="stylesheet"]')` 제거 로직 추가. CSS/JS가 텍스트로 포함되는 것을 원천 차단.
- **배포**: v0.5.52 VSIX 설치 + v0.5.50/out/ JS 직접 복사 + V8 CachedData 삭제. AG File→Quit 재시작 필요.
- **이전 블로커 해결 이력**:
- SDK 경로: AG Native는 Cascade trajectory API 미등록 → step-probe RT-CAPTURE 불가 (구조적 한계)
- DOM 경로: v15에서 `#conversation` + `.leading-relaxed.select-text` 셀렉터 추가로 해결
- BEACON=0: AG 프로세스 완전 재시작으로 해결 (Reload Window로는 렌더러 HTML 캐시 유지)
- **주의**: AG Native 마크다운 렌더링은 `<style>` 블록을 응답 DOM 내부에 인라인으로 삽입함. DOM 텍스트 추출 시 반드시 style/script 태그를 먼저 제거해야 함.
- **Vikunja**: #632
### [2026-04-16] [Extension] ★ AG Native Observer innerText로 인한 마크다운 포맷 유실 및 사용자 요청 누락
- **증상**: Discord로 릴레이되는 AI 응답에서 리스트 번호(`1. 2.`)나 불릿(`-`) 등 마크다운 포맷이 완전히 유실되고 텍스트만 이어져서 나옴. 그리고 사용자가 입력한 메시지(요청)는 아예 릴레이되지 않음.
- **원인 1**: `extractCleanStepText()`에서 `innerText`를 사용하여 텍스트를 추출할 때, 렌더링되지 않은 DOM이나 CSS 카운터로 생성된 list-item 마커가 무시됨.
- **원인 2**: `scanChatBodies()` 로직이 `.leading-relaxed.select-text` (AI 응답 블록)만을 타겟팅하여 사용자의 메시지 박스(예: `.text-ide-message-block-user-color`)는 추출 대상에서 제외됨.
- **해결 (계획 중)**: `innerText` 대신 HTML DOM 노드를 순회하며 `convertNodeToMarkdown` 변환 함수를 통해 마크다운 문법을 보존하도록 개선. User 블록도 함께 감지하여 브릿지에 `role: 'user'` 플래그를 추가 전송하도록 수정 예정.
- **주의**: Webview 내에서 텍스트 노드를 파싱할 때 `innerText`는 브라우저 레이아웃 엔진에 의존하므로 완전한 마크다운 보존을 위해서는 Node Type 순회를 활용한 구조 복원이 보장되어야 함.
### [2026-04-16] [Extension] 터미널 출력(stdout) 텍스트가 명령어로 Discord에 전송 (v0.5.50)
- **증상**: Discord에 `cmd="No extension.log found"`, `cmd="AG CLI not found..."`, `cmd="Log found: C:\..."` 등 터미널 **출력** 텍스트가 명령어로 전송됨
- **원인**: Observer가 code 블록 2개를 감지: (1) 프롬프트+명령어 → JUNK_CODE_RE로 스킵, (2) 터미널 출력 → 유효한 code로 판단 → description에 포함. http-bridge enrichment에서 description에 prompt marker(`>`)가 없으면 rawDesc 전체를 enrichedCmd로 채택
- **해결 (v0.5.50)**: `promptMatch` 실패 시(description에 `>` 없음) → 터미널 OUTPUT으로 판단하여 `terminal_output` 사유로 즉시 필터. 실제 명령어는 항상 `…\project > command` 프롬프트를 포함
- **주의**: enrichment은 반드시 prompt marker가 있는 경우에만 수행. description에 `>` 없으면 code 블록의 출력 텍스트
### [2026-04-15] [Extension] Observer fallback 컨텍스트가 채팅/UI 텍스트를 명령어로 추출 (v0.5.46)
- **증상**: Discord에 `cmd="실 동작검증을 해봐야하는데"`, `cmd="variet.gravity-bridge"` 등 사용자 채팅/AI 응답 텍스트가 명령어로 전송됨
- **원인**: v0.5.45에서 `PROMPT_ONLY_RE``code/pre` 요소 스킵 성공했으나, `extractContextFromNearby()`의 fallback(`span/div/p` 수집)이 여전히 작동하여 DOM 트리의 채팅 본문/UI 라벨을 명령어로 추출
- **해결 (v0.5.46)**: Observer v13에 `_promptOnlySkipped` 플래그 — code 요소가 모두 prompt-only이면 fallback 비활성화. http-bridge에 generic button + no-context일 때 무조건 필터
- **주의**: 프롬프트 스킵과 fallback 비활성화는 항상 연동해야 함. VSIX 설치 누락 방지를 위해 빌드 후 즉시 `code --install-extension` 확인 필수
### [2026-04-15] [Extension] PROMPT_ONLY_RE regex fixes — prompt-only terminal text leaking as enriched cmd (v0.5.45)
- **증상**: Discord에 `cmd="…\gravity_control >"` (실제 명령어 없는 빈 터미널 프롬프트)가 전송됨. 명령어가 포함된 경우는 정상 작동
- **원인 1 (Observer)**: `PROMPT_ONLY_RE``\\\\s`(4중 backslash)가 template literal 안의 regex 리터럴에서 "literal backslash+s"가 되어 whitespace class가 아닌 문자열 매칭
- **원인 2 (http-bridge)**: `PROMPT_ONLY_RE = /^[\s\\\/]*[\w_.-]+\s*[>»$#]\s*$/` — AG 터미널 프롬프트가 `…`(U+2026 ellipsis)로 시작하는데 첫 문자 클래스에 포함 안 됨
- **해결 (v0.5.45, `d2c44fe`)**: Observer `\\s``\s`, http-bridge 패턴을 `/^.*[>»$#]\s*$/`로 단순화
- **검증**: 11개 테스트 케이스 전체 통과
--- ---
## 포맷 ### [2026-04-14] [Extension] Observer template literal 정규식 이스케이핑 오류 — "Running N commands" 스킵 미작동
- **증상**: v9에서 `Running N commands` 그룹 헤더를 스킵하는 정규식 `/^Running\s*\d+\s*commands?$/i`를 추가했으나, 실제로 "Running2 commands" 텍스트를 전혀 매칭하지 못하여 여전히 Discord에 `cmd="Running2 commands"`가 전송됨
- **원인**: `observer-script.ts`의 전체 코드가 TypeScript template literal (`` ` `` ... `` ` ``) 안에 있으므로, 정규식 리터럴의 백슬래시가 2중 해석됨:
- TS 소스 `\\\\s` (4중) → template literal 출력 `\\s`**브라우저에서 regex 리터럴 `\\s` = 리터럴 백슬래시+s**
- TS 소스 `\\s` (2중) → template literal 출력 `\s`**브라우저에서 regex 리터럴 `\s` = whitespace class**
- TS 소스 `\s` (1중) → template literal에서 이스케이프 소멸 → **출력 `s`**
- **영향 범위**: `NOISE_CODE_RE`, `cleanLines()` (word boundary `\b`, newline `\n`), `cleanButtonText()` (whitespace `\s`), port 탐색 regex `\d`, "Running N commands" 스킵 regex 전부 미작동이었음
- 단, `NOISE_RE` (new RegExp() 사용)는 문자열 이스케이핑이므로 4중 백슬래시가 올바름 (string → RegExp는 별도 이스케이핑 레이어)
- **해결**: 모든 정규식 리터럴 (`/pattern/flags`) 안의 `\\\\s``\\s`, `\\\\d``\\d`, `\\\\b``\\b`, `\\\\n``\\n` 으로 수정 (v0.5.41, `8e89209`)
- **주의**: **template literal 안에서 정규식 특수문자를 쓸 때 반드시 구분:**
- `new RegExp('pattern')` 문자열: `\\\\s` (4중) — string 이스케이핑(1번)+regex 이스케이핑(1번) = 총 2번
- `/pattern/` 정규식 리터럴: `\\s` (2중) — template literal 이스케이핑(1번)만 = 총 1번
- **검증법**: `node -e "var f = require('./extension/out/observer-script.js').generateApprovalObserverScript; require('fs').writeFileSync('tmp.js', f(0)); console.log(require('fs').readFileSync('tmp.js','utf8').match(/Running.{10}/g));"` 으로 생성된 코드 확인
각 항목은 아래 형식을 따릅니다: ---
### [2026-04-13] [Extension] Observer v8 "Running N commands" 그룹 헤더를 승인 버튼으로 오인 — Discord 빈 내용+잘못된 버튼
- **증상**: Discord 승인 요청에 command="Running2 commands", description 비어있음, 버튼도 "Running2 commands / Always run" 형태. 실제 코드/명령어 내용이 전혀 표시되지 않음
- **원인 1**: `observer-script.ts``isActionBtn()``/Running\\s*\\d*\\s*command/i` 패턴이 있어 AG UI의 그룹 헤더 버튼("Running 3 commands")을 승인 버튼으로 분류. `scan()`이 이 버튼을 먼저 만나고 `break`로 나가 실제 "Always run"/"Cancel" 버튼은 처리 안 됨
- **원인 2**: `extractStepContext()``data-step-index` 속성 없으면 `cleanButtonText(btn)` = "Running2 commands"를 그대로 반환. AG Native에는 `data-step-index`/`data-testid` 속성이 없음 (DOM 덤프로 확인)
- **원인 3**: `http-bridge.ts`의 "Run/Always run" 필터가 step-probe 미활성(activeSessionId 비어있음) 시에도 DOM observer 신호를 차단
- **해결**: observer v9 (v0.5.40):
1. `isActionBtn()`에서 "Running N commands" 패턴 제거
2. `scan()`에서 `^Running\\s*\\d+\\s*commands?$` 명시적 스킵
3. `extractContextFromNearby()` 추가: `data-step-index` 없이 DOM 트리를 20레벨까지 올라가며 `pre`/`code` 블록에서 실제 명령어 추출
4. `collectSiblingButtons()` 범위를 parent → grandparent → great-grandparent로 확대, 그룹 헤더 스킵
5. `http-bridge.ts`의 "Run" 필터에 `ctx.activeSessionId` 체크 추가 — step-probe 미활성 시 DOM observer 허용
- **주의**: AG Native UI의 "Running N commands"는 아코디언/그룹 헤더이며, 실제 승인 버튼은 하위 레벨의 "Run"/"Always run"/"Cancel". DOM 구조상 버튼 탐색 시 그룹 헤더를 반드시 스킵해야 함
### [2026-04-13] [Extension] HTTP Bridge UTF-8 인코딩 깨짐 — 한글 description 손실
- **증상**: pending/ 파일의 description 필드에서 한글이 `[AI ]`처럼 깨져서 저장됨. Discord로 전달되는 승인 요청 본문도 깨짐
- **원인**: Node.js HTTP 서버의 `req.on('data', chunk)` 콜백에서 chunk가 Buffer 타입으로 전달되는데, `body += chunk`로 string 결합 시 Buffer의 기본 인코딩(latin1)이 사용되어 multi-byte UTF-8 문자가 손실됨
- **해결**: 모든 POST 핸들러(`/pending`, `/dump-html`, `/chat`, `/deep-inspect-result`, `/test-rpc`)에 `req.setEncoding('utf8')` 추가 (v0.5.39)
- **주의**: Node.js HTTP 서버에서 POST body를 문자열로 수집할 때는 반드시 `req.setEncoding('utf8')`을 호출하거나, Buffer를 배열로 모은 후 `Buffer.concat().toString('utf8')`로 변환해야 함
### [2026-04-13] [Extension] Observer noise 필터 미작동 — textContent가 아이콘 텍스트를 줄바꿈 없이 합침
- **증상**: pending description에 `Thought for 1s`, `chevron_right` 등 Material 아이콘명과 UI 노이즈가 그대로 남아있음
- **원인**: DOM `textContent`는 block 요소 사이에 newline을 삽입하지 않아 `[AI 본문 요약]Thought for 1schevron_right[결행 명령]`처럼 한 줄로 합쳐짐. `cleanLines()`의 줄 단위 noise 필터(`^pattern$`)가 매칭 실패. 또한 `codeText` 추출에는 `cleanLines()`가 아예 미적용
- **해결**: `cleanLines()`에 인라인 pre-strip 추가 — icon명 18종을 regex로 먼저 `\n`으로 치환 후 줄 단위 필터 적용. `codeText`에도 `cleanLines()` 적용 (v0.5.39)
- **주의**: DOM에서 텍스트 추출 시 `textContent`는 레이아웃 무시, `innerText`는 detached 노드에서 미작동. 노이즈 필터링은 줄 단위뿐 아니라 인라인 패턴 제거도 병행해야 함
### [2026-04-13] [Extension] html-patcher String.replace() `$'` 특수 패턴으로 인라인 스크립트 SyntaxError
- **증상**: Observer v8 인라인 스크립트가 workbench.html에 삽입되었으나 렌더러에서 전혀 실행되지 않음 (BEACON 핑 0건). V8 캐시 삭제 + AG 재시작 후에도 동일
- **원인**: `html-patcher.ts`에서 `html.replace('</body>', '\n' + inlineBlock + '\n</body>')`를 사용. 인라인 스크립트의 NOISE_RE 정규식에 `')$', 'i'`가 있는데, `$'`는 JS `String.replace()`의 특수 대체 패턴(match 뒤의 텍스트)으로 해석됨. 이로 인해 `</body>` 뒤의 원본 HTML 구조(`<!-- Startup -->`, `<script src="workbench.js">`, `</html>`)가 JS 코드 중간(정규식 리터럴 안)에 삽입되어 **치명적 SyntaxError** 발생
- **해결**: `inlineBlock.replace(/\$/g, '$$$$')`로 모든 `$`를 이스케이프한 후 replacement 문자열로 사용 (v0.5.38, `d6ed876`)
- **주의**: `String.prototype.replace()`의 replacement 문자열에서 `$&`, `$'`, `` $` ``, `$1` 등은 특수 패턴. 동적 콘텐츠를 replacement로 사용할 때 반드시 `$` → `$$` 이스케이프 필요
### [2026-04-12] [Extension] V8 CachedData가 Observer 스크립트 로딩을 차단
- **증상**: html-patcher가 workbench-jetski-agent.html에 observer v7 인라인 스크립트를 성공적으로 삽입했지만, deep-inspect가 렌더러에서 응답 없음 (10s timeout). AG 재시작 후에도 observer가 로드되지 않음
- **원인**: `%APPDATA%\Antigravity\CachedData\` (50MB)에 V8 캐시가 남아있어, AG가 패치된 HTML 대신 캐시된 이전 버전을 로드. extension.log에 `patcher.install() called (needs reload)` 메시지가 표시되지만 실제 적용이 안 됨
- **해결**: `Remove-Item "$env:APPDATA\Antigravity\CachedData\*" -Recurse -Force` 실행 후 AG 재시작. known-issues-archive #6에도 동일 케이스 있음
- **주의**: HTML 패치 변경 시 **반드시** V8 CachedData 삭제 + AG 재시작 필요. 단순 AG 재시작만으로는 부족
### [2026-04-12] [DOM] text-ide-message-block-bot-color는 AI 응답 컨테이너가 아닌 NUX tooltip 전용
- **증상**: observer-script가 `.text-ide-message-block-bot-color`를 AI 응답 컨테이너로 사용하지만, 실제 AI 텍스트를 추출하지 못함
- **원인**: 번들 분석(jetskiAgent/main.js 10.8MB)으로 확인 결과, 이 클래스는 `hsn` 컴포넌트(NUX Tooltip 텍스트 색상)에서만 사용. AI 응답 텍스트는 `plannerResponse` step의 `Whi` 렌더러 → `div.px-2.py-1``MarkdownRenderer` 내부에 렌더링됨
- **해결**: observer-script에서 `.text-ide-message-block-bot-color` 의존성 제거 필요. `markdown-body` 클래스도 AG Native에 존재하지 않음
- **주의**: AI 응답 마크다운은 `prose` 관련 클래스나 MarkdownRenderer 내부 구조로 타겟팅해야 함. 실제 DOM 덤프로 정확한 셀렉터 확인 필요
### [2026-04-12] [SDK/DOM] AG Native 세션은 Cascade SDK API에 등록되지 않음 — DOM이 유일한 데이터 소스
- **증상**: AG Native 세션에서 Discord 릴레이로 AI 응답이 전혀 전달되지 않고, 대신 UI 노이즈(`content_copy`, `Always run`, `keyboard_arrow_up`, `Cancel`)가 전송됨
- **원인 1 (SDK)**: `GetCascadeTrajectorySteps(cascadeId=세션ID)``500 trajectory not found`. `GetDiagnostics``404`. AG Native 세션은 Cascade trajectory API에 전혀 등록되지 않는 별도 시스템
- **원인 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` 속성 존재 여부 반드시 확인 필요
### [2026-04-09] [Bridge] Discord Body Content Missing Due to Step Probe Dummy Payload
- **利앹긽**: <20><><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>맖.
- **<2A><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
- **利앹긽**: <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>썝.
- **二쇱쓽**: 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>
- **利앹긽**: 臾댁뾿<EB8C81><20>옒紐삳릺<EC82B3><EFBFBD>뒗媛<EB9297>
- **<2A><EFBFBD>씤**: 洹쇰낯 <20><EFBFBD>
- **<2A>빐寃<EBB990>**: <20>삱諛붾Ⅸ <20>빐寃<EBB990> 諛⑸쾿
- **二쇱쓽**: <20>옱諛<EC98B1> 諛⑹<E8AB9B><E291B9><EFBFBD> <20><EFBFBD>븳 援먰썕
```markdown
### [날짜] [키워드] — 한줄 요약
- **증상**: 무엇이 잘못되었는가
- **원인**: 근본 원인
- **해결**: 올바른 해결 방법
- **주의**: 재발 방지를 위한 교훈
``` ```
---
## 공통 이슈
### [2026-03-08] PowerShell curl — Invoke-WebRequest 충돌
- **증상**: `curl` 명령이 예상과 다른 응답 형식을 반환
- **원인**: PowerShell에서 `curl``Invoke-WebRequest`의 별칭
- **해결**: **`curl.exe`**를 명시적으로 사용
- **주의**: HTTP 관련 모든 명령에서 `curl.exe` 사용 필수
### [2026-03-08] PowerShell npm — 실행 정책 오류
- **증상**: `npm run` 명령이 `실행 정책` 관련 오류로 실패
- **원인**: PowerShell 스크립트 실행 정책이 제한적으로 설정됨
- **해결**: `cmd /c npm run dev` 형식으로 cmd를 통해 실행
- **주의**: npm 관련 명령은 항상 `cmd /c` 접두어 사용 권장
--- ---
## 프로젝트별 이슈
> 아래에 프로젝트 특화 이슈를 추가하세요.
### [2026-03-08] Antigravity Renderer Injection — Electron 캐시 차단 ### [2026-04-08] [Discord Bot] Channel Deletion Cache Desync
- **증상**: workbench.html, workbench-jetski-agent.html에 `<script>` 태그 추가 후 리로드해도 실행되지 않음
- **원인**: Electron의 V8 코드 캐시가 수정된 HTML을 무시하고 캐시된 버전을 서빙
- **해결**: 렌더러 인젝션 방식 **포기**. Extension Host에서 RPC 폴링 방식으로 전환
- **주의**: Antigravity는 `workbench-jetski-agent.html`을 사용 (Jetski = 내부 코드네임)
### [2026-03-08] Antigravity 승인 대기 = RUNNING (NOT IDLE) - **利앹긽**: 遊뉗씠 耳쒖졇 <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>븿.
- **증상**: IDLE 기반 승인 감지가 실제 승인 대기를 놓침
- **원인**: 승인 대기 시 세션 상태가 `CASCADE_RUN_STATUS_RUNNING` (IDLE 아님), `IDLE`은 대화 대기(notify_user 후)
- **해결**: `RUNNING + delta=0` (stall) 기반 감지로 전환. 6 polls (30초) 이상 FROZEN 시 pending 생성
- **주의**: Thinking/생성 중에도 `RUNNING + delta=0`이 발생 → `lastModifiedTime`으로 구분 시도했으나 불완전
### [2026-03-08] ResolveOutstandingSteps RPC — 승인이 아닌 취소! - **<2A><EFBFBD>씤**: ot.py<70>쓽 self.project_channels <20><EFBFBD><EFBFBD>꼫由ъ뿉 梨꾨꼸 媛앹껜媛<EABB9C> 罹먯떆<EBA8AF><EFBFBD><20><EFBFBD>뼱, API <20>샇異<EC8387> <20><EFBFBD>씠 罹먯떆<EBA8AF>맂(<28><EFBFBD><EFBFBD>맂) 梨꾨꼸濡<EABCB8> 硫붿떆吏<EB9686><EFBFBD> 蹂대궡<EB8C80><20><EFBFBD><EFBFBD><EFBFBD>떎 404 <20><EFBFBD>윭 諛쒖깮 <20><20><EFBFBD><EFBFBD>븿.
- **증상**: Discord 승인 → `ResolveOutstandingSteps` 호출 → step이 취소됨
- **원인**: `ResolveOutstandingSteps`는 blocking steps를 "resolve" = **REJECT/CANCEL**, approve가 아님
- **해결**: `ResolveOutstandingSteps` 제거. `HandleCascadeUserInteraction``socket hang up`
- **주의**: KI에 "more reliable"로 기록되어 있으나 실제 동작은 cancel임. KI 업데이트 필요
### [2026-03-08] VS Code Accept Commands — Silent Success 문제 - **<2A>빐寃<EBB990>**: 梨꾨꼸 留듯븨<EB93AF>씠 瑗ъ<E79197><D18A><EFBFBD><20><EFBFBD>뒗 **Python 遊<>(Docker 而⑦뀒<E291A6><EFBFBD>꼫)<29><20><EFBFBD><EFBFBD>옉**<2A><EFBFBD>뿬 罹먯떆瑜<EB9686> 珥덇린<EB8D87><EFBFBD>븯怨<EBB8AF> 梨꾨꼸 紐⑸줉<E291B8><20>깉濡<EAB989> 媛깆떊<EAB986>븯寃<EBB8AF> <20>븿.
- **증상**: 4개 accept command 모두 OK(undefined) 반환하나 실제 승인 안 됨
- **원인**: webview에 활성 포커스가 필요. `panel.focus()`로는 충분하지 않음
- **해결**: **미해결**. Windows UI Automation 등 OS 레벨 접근 필요
- **주의**: reject commands는 동작함. accept만 focus 의존성 있음
### [2026-03-08] Multi-Window 세션 등록 경쟁 조건 - **二쇱쓽**: 梨꾨꼸 愿<>由щ뒗 罹먯떆<EBA8AF><20>쓽議댄븯湲<EBB8AF> <20>븣臾몄뿉 媛뺤젣濡<ECA0A3> Discord UI<55><EFBFBD>꽌 梨꾨꼸<EABEA8>쓣 吏<><EFA79E><EFBFBD><20><EFBFBD>뒗 諛섎뱶<EC848E>떆 遊뉗쓣 <20>옱援щ룞<D189><EFBFBD><20>븿.
- **증상**: 이 창(gravity_control)의 대화가 `#ag-variet_agent` 채널로 메시지 전달
- **원인**: `writeRegistration()`이 폴링 루프에서 호출 → 먼저 폴링한 확장이 세션을 자기 프로젝트로 등록
- **해결**: `writeRegistration`을 폴링에서 제거, `writeChatSnapshot`/`writePendingApproval`에서만 지연 호출
- **주의**: `GetAllCascadeTrajectories`는 모든 창의 세션을 반환하므로 세션→창 매핑은 불가능. 활동 기반 등록만 신뢰 가능
### [2026-03-08] 공유 렌더러 스크립트 파일 덮어쓰기 문제
- **증상**: DOM Observer 렌더러 스크립트가 잘못된 HTTP bridge 포트에 연결
- **원인**: 두 확장이 동일한 `ag-sdk-variet-gravity-bridge.js` 파일에 각자 포트를 씀 → 마지막 확장 것만 남음
- **해결**: `ag-bridge-ports.json`에 모든 확장의 port를 JSON으로 기록, 렌더러가 all ports를 순회하며 ping
- **주의**: 렌더러 스크립트 파일 경로는 SDK patcher namespace에 의해 고정 — 변경 불가
### [2026-03-08] workbench.html vs workbench-jetski-agent.html
- **증상**: 렌더러에서 `[GB Observer]` 로그가 전혀 안 나옴
- **원인**: DevTools가 `workbench.html`을 로드 — 스크립트 태그는 `workbench-jetski-agent.html`에만 패치됨
- **해결**: `workbench.html`에도 스크립트 태그 필요. Antigravity 재설치 후 SDK patcher가 올바르게 패치하도록 함
- **주의**: SDK patcher는 `both` HTML 파일을 패치하지만, 수동 수정은 Antigravity integrity check에 의해 되돌려질 수 있음
### [2026-03-08] product.json 체크섬 불일치 → 렌더러 스크립트 미로딩 ### [2026-04-08] [Extension] Multiple Workspace LS Cross-Connection
- **증상**: `<script>` 태그가 HTML에 존재하고 .js 파일도 디스크에 있으나, 렌더러 콘솔에 스크립트 로그가 전혀 없음
- **원인**: Antigravity 재설치 시 `product.json`의 SHA256 체크섬이 원본으로 리셋됨. Extension이 HTML을 패치하지만 `IntegrityManager.suppressCheck()`를 호출하지 않아 체크섬 불일치. `vscode-file://` 프로토콜이 체크섬 불일치 파일을 무시하고 **원본 캐시 HTML**을 서빙
- **해결**: `product.json``checksums` 항목에서 수정된 파일(workbench.html, workbench-jetski-agent.html)의 SHA256 해시를 실제 파일 기준으로 업데이트. SDK `IntegrityManager.suppressCheck()` 호출 또는 수동 스크립트로 해결
- **주의**: Extension `setupApprovalObserver()``suppressCheck()` 호출을 영구 추가해야 재설치마다 반복 안 됨. 해시 = `base64(sha256(file)).replace(/=+$/, '')`
### [2026-03-08] vscode-file:// 프로토콜 — 커스텀 .js 파일 서빙 불가 - **利앹긽**: ariet-llm 李쎌뿉<EC8E8C>꽌 耳곗쑝<EAB397>굹 gravity_control<6F>쓽 諛깃렇<EAB983><EFBFBD><EFBFBD>뱶 援щ룞 以묒씤 LS<4C><20>뿰寃곕릺<EAB395><20>옄湲<EC9884> <20><EFBFBD>떊 李쎌쓽 <20><EFBFBD>샇瑜<EC8387> <20>옟吏<EC989F> 紐삵븿.
- **증상**: `<script src="./ag-sdk-variet-gravity-bridge.js">` 태그가 HTML에 있으나 `net::ERR_FILE_NOT_FOUND` 발생, GB Observer 로그 전혀 없음
- **원인**: `vscode-file://` 프로토콜은 원본 배포에 포함된 파일만 서빙. Extension이 디스크에 쓴 커스텀 `.js` 파일은 프로토콜 레벨에서 차단됨
- **해결**: 외부 `<script src>` 참조 대신 **인라인 `<script>...코드...</script>`** 방식으로 HTML에 직접 삽입
- **주의**: `ag-bridge-ports.json`도 같은 이유로 XHR 로딩 불가. 모든 렌더러 스크립트/데이터는 HTML 인라인으로 전달해야 함
### [2026-03-08] Renderer 포트 디스커버리 — ag-bridge-ports.json XHR 실패 - **<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>뿰寃곕맖.
- **증상**: `[GB Observer] Port discovery timeout after 2min` — 렌더러가 bridge 포트를 찾지 못함
- **원인**: 렌더러 스크립트가 `./ag-bridge-ports.json`을 동기 XHR로 읽으려 하나, `vscode-file://` 프로토콜이 `.json` 파일 서빙 거부
- **해결**: (1) 프로젝트명 해시 기반 **결정론적 포트** 사용 (`gravity_control→34332`), (2) 스크립트 생성 시 포트를 `HARDCODED_PORT=${port}`로 직접 삽입
- **주의**: `server.listen(0)` 랜덤 포트 → 매 재시작마다 변경되어 렌더러와 불일치. 결정론적 포트는 `EADDRINUSE` 시 랜덤 폴백 필요
### [2026-03-08] Extension 컴파일 경로 != 설치 경로 - **<2A>빐寃<EBB990>**: <20><><EFBFBD><EFBFBD>긽 李쎌뿉<EC8E8C>꽌 Developer: Reload Window <20><EFBFBD><20>썑 **<2A><EFBFBD><EFBFBD>뱶諛붿쓽 濡쒖뺄 Antigravity 梨쀫큸 <20><EFBFBD><EFBFBD><20>븳 踰<> <20><EFBFBD>뼱** <20><EFBFBD><EFBFBD>쓽 LS <20>봽濡쒖꽭<EC9296>뒪瑜<EB92AA> <20><EFBFBD><20><EFBFBD>뿉 Gravity Bridge瑜<65> Start<72>븿.
- **증상**: `npm run compile` 후 Extension 동작이 변하지 않음
- **원인**: `npm run compile``extension/out/extension.js`에만 빌드. Antigravity는 `~/.antigravity/extensions/variet.gravity-bridge-X.X.X/out/extension.js`에서 로드
- **해결**: 컴파일 후 반드시 설치 경로로 수동 복사하거나, `vsce package` → VSIX 재설치
- **주의**: `extension/out/` ≠ 실행 경로. 항상 설치 경로 확인 필요
### [2026-03-08] Electron 메인 프로세스 체크섬 캐시 — Reload Window 불충분 - **二쇱쓽**: 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>맖.
- **증상**: product.json 체크섬 업데이트 + HTML 패치 후 `Reload Window` → 패치 미적용
- **원인**: Electron 메인 프로세스가 시작 시 product.json 체크섬을 메모리에 캐시. `Reload Window`는 렌더러만 재시작하므로 캐시된 구 체크섬 사용
- **해결**: Antigravity를 **완전 종료 후 재시작** 필요 (File → Exit 후 재실행)
- **주의**: `Reload Window` ≠ 앱 재시작. 체크섬 변경 시 항상 풀 재시작 필요
### [2026-03-08] Renderer 동기 XHR — Electron 보안 정책 차단
- **증상**: `tryPing()` 함수가 동기 `XMLHttpRequest`로 HTTP bridge에 연결 시도 → 타임아웃
- **원인**: Electron 렌더러 프로세스에서 동기 XHR이 보안 정책에 의해 차단됨
- **해결**: `fetch()` + `AbortSignal.timeout(2000)` 비동기 방식으로 교체 (`tryPingAsync`)
- **주의**: `async/await` 사용 불가 (ES5 환경). `.then()` 체이닝으로 구현
### [2026-03-08] DOM Observer — Run 버튼 감지 불가 (webview iframe 격리)
- **증상**: Allow Once/Allow This Conversation는 감지되나 Run/Accept 버튼은 감지 안 됨
- **원인**: Trust/permission 버튼은 워크벤치 외부 DOM에 렌더링, Run/Accept는 **Antigravity 채팅 webview iframe** 내부의 별도 DOM에 렌더링. 렌더러 스크립트의 `document.querySelector()`는 iframe 내부 접근 불가
- **해결**: Run 버튼은 DOM Observer가 아닌 **`latestToolCallStep` RPC 기반** 즉시 감지로 대체
- **주의**: webview iframe에 스크립트 주입은 Electron `executeJavaScript()`로 가능하나, 현재 RPC 방식이 더 안정적
### [2026-03-08] Accept all/Reject all 리뷰 바 — agent 패널 밖 DOM ## <EFBFBD><EFBFBD> Active/Recent Issues
- **증상**: 코드 변경 리뷰 바(Accept all/Reject all)가 DOM Observer에 감지 안 됨
- **원인**: `scan()` 함수가 `findPanel()` (`.antigravity-agent-side-panel` 등) 내부만 검색. 리뷰 바는 에디터/notification 영역에 렌더링되어 패널 밖에 있음
- **해결**: `scan()` 검색 범위를 `document.body` 전체로 확장, `Accept all` / `Reject all` 패턴 추가
- **주의**: 패널+body 이중 검색 시 dedupe 필요 (같은 버튼이 두 번 잡힐 수 있음)
### [2026-03-08] GetCascadeTrajectorySteps — cascadeId 파라미터 발견
- **증상**: `GetCascadeTrajectorySteps``trajectoryId` 파라미터 → 500 "trajectory not found"
- **원인**: 파라미터명이 `trajectoryId`가 아니라 **`cascadeId`**. 값은 `GetAllCascadeTrajectories.trajectorySummaries`의 맵 키(세션 ID)
- **해결**: `{ cascadeId: sessionId }`로 호출 → 전체 step 배열 반환 성공
- **주의**: `latestToolCallStep` 필드는 `GetAllCascadeTrajectories` 응답에 **존재하지 않음** (KI 오류)
### [2026-03-08] Step 구조 — CORTEX_STEP_STATUS_WAITING 즉시 감지
- **증상**: stall-based 감지(100초)가 너무 느림
- **원인**: 이제 `GetCascadeTrajectorySteps`로 최신 step의 status를 직접 확인 가능
- **해결**: stall 5초 후 step probe → `CORTEX_STEP_STATUS_WAITING` 확인 → 즉시 pending 생성
- **Step 구조**: `{type: "CORTEX_STEP_TYPE_RUN_COMMAND", status: "CORTEX_STEP_STATUS_WAITING", metadata: {toolCall: {name, argumentsJson}}, runCommand, requestedInteraction}`
- **주의**: 775-step 하드 리밋은 여전히 존재. 긴 세션에서는 fallback(40초) 사용
### [2026-03-08] Extension 재설치 안전성 — 자동 패치 메커니즘 ### [2026-04-09] [Extension] Agent UI Native Migration & Icon Text Gluing
- **증상**: Antigravity 삭제 후 재설치 시 렌더러 스크립트가 동작 안 함
- **원인**: 재설치 시 HTML/product.json이 원본으로 리셋됨
- **해결**: Extension의 `setupApprovalObserver()`**자동으로** 모든 패치를 수행:
1. `workbench.html` + `workbench-jetski-agent.html` 인라인 스크립트 삽입
2. `product.json` SHA256 체크섬 자동 업데이트
3. HTTP bridge 서버 시작 + 결정론적 포트
- **주의**: 패치 후 반드시 **Antigravity 풀 재시작** 필요 (Reload Window 불가). Extension VSIX만 설치하면 수동 패치 불필요
### [2026-03-08] Response 파일 Race Condition — DOM Observer 승인 실패 - **利앹긽**: UI Tailwind/Native 留덉씠洹몃젅<EBAA83><EFBFBD>뀡 諛<> <20><EFBFBD>씠肄<EC94A0> <20><EFBFBD><20>썑, Discord 釉뚮┸吏<E294B8><EFBFBD> <20><EFBFBD>샇媛<EC8387> <20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD>쓬.
- **증상**: Discord에서 승인 → `[RESPONSE] renderer-handled approval` 로그 출력 → 실제 버튼 클릭 안 됨
- **원인**: `processResponseFile` (파일 감시자)이 response 파일을 즉시 삭제 → renderer의 `pollResponse`가 HTTP `GET /response/:rid`로 조회 시 파일 이미 없음
- **해결**: DOM observer 소스일 때는 response 파일을 삭제하지 않도록 수정. HTTP endpoint가 renderer에게 서빙한 후 삭제
- **주의**: non-DOM (stall/step_probe relay)는 watcher에서 삭제해도 됨
### [2026-03-08] Renderer 스크립트 소스 혼동 — 3곳의 코드 - **<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>븿.
- **증상**: `extension.ts`에 BTN-DUMP 추가 → Reload 2번 → 콘솔에 안 나옴
- **원인**: renderer 코드가 **3곳**에 존재: (1) `extension.ts`의 `generateApprovalObserverScript()` (소스), (2) `ag-sdk-variet-gravity-bridge.js` (배포됨, Reload시 소스에서 재생성), (3) `workbench-jetski-agent.html` inline (HTML, JS파일과 중복로드 방지됨). 직접 JS파일 패치는 Reload시 소스에서 재생성되어 **덮어씌워짐**
- **해결**: 항상 `extension.ts``generateApprovalObserverScript()` 함수를 수정 → 컴파일 → 배포 → Reload
- **주의**: HTML inline은 JS파일이 먼저 로드되어 `window.__agSDK` 가드에 의해 실행 안 됨. 실제 실행되는 것은 JS파일 경로의 스크립트
### [2026-03-09] VS Code Accept — SDK 승인 명령이 AG에 미등록 - **<2A>빐寃<EBB990>**: `observer-script.ts`<60><20>뒪罹<EB92AA>, Sibling 踰꾪듉 <20>닔吏<EB8B94>, Webview Trigger-click <20>벑 `textContent`瑜<> 異붿텧<EBB6BF><EFBFBD>뒗 紐⑤뱺 DOM <20>씫湲<EC94AB> 援ш컙<D188>뿉 `txt.replace(/^[^a-zA-Z0-9]+/, '')` <20>쟾泥섎━瑜<E29481> <20><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD>뻾 湲고샇/<2F><EFBFBD>씠肄섏쓣 <20><EFBFBD><EFBFBD>븯寃<EBB8AF> <20>젣嫄<ECA0A3>.
- **증상**: Discord 승인 → `antigravity.terminalCommand.run` 등 7개 명령 → 모두 `command not found`
- **원인**: SDK(command-bridge.ts)에 정의된 7개 승인 명령이 현재 AG 빌드에 **등록되어 있지 않음**. 활성 시 72개, 세션 중 119개로 동적 등록되지만 승인 관련 명령은 없음 - **二쇱쓽**: 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>븿.
- **검증**:
- `HandleCascadeUserInteraction` RPC 3 variants → 모두 `socket hang up`
- `ResolveOutstandingSteps``run state not found` (500 에러, 실제로는 CANCEL 동작)
- `sendChatActionMessage`, `executeCascadeAction` → 119개 명령 중 미등록 ### [2026-04-09] [Extension] Agent UI Native Migration & CodeLens False Positive Filter
- 존재하는 approval-like 명령: `agentAcceptAllInFile` (코드 diff), `agentAcceptFocusedHunk` (hunk), `acceptCompletion` (자동완성) — 터미널 승인과 무관
- **해결**: ~~Renderer DOM Click 구현됨 (미검증)~~**v1 검증 실패: webview iframe 격리 확인**. v3 `deepFindButtons()`로 업그레이드 (iframe contentDocument + webview.executeJavaScript + shadow DOM). AG 완전 재시작 후 DOM-DUMP로 접근 가능 여부 확인 필요 - **利앹긽**: UI Tailwind/Native 留덉씠洹몃젅<EBAA83><EFBFBD><20><EFBFBD><20>썑, Discord 釉뚮┸吏<E294B8><EFBFBD> <20><EFBFBD>샇媛<EC8387> <20><EFBFBD><EC9FBE><EFBFBD> <20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD>
- **주의**: `agentPanel.focus`도 미등록, `agentSidePanel.focus`만 존재
- **<2A><EFBFBD>씤**: Agent <20><EFBFBD><EFBFBD><20>꺆/<2F><EFBFBD><EFBFBD>꽣 蹂몃Ц<EBAA83>뿉 吏곸젒 <20><EFBFBD>뜑留곷릺硫댁꽌, 湲곗〈 <20><EFBFBD><EFBFBD>룞 諛⑹<E8AB9B><E291B9> 濡쒖쭅(`if (b.closest('.monaco-editor'))`)<29><20><EFBFBD><20>쟾泥<EC9FBE> 踰꾪듉<EABEAA><20>룷李⑸릺<E291B8>뼱 臾댁떆<EB8C81>
- **<2A>빐寃<EBB990>**: <20>꼫臾<EABCAB> 愿묐쾾<EBAC90><EFBFBD>븳 `.monaco-editor` 諛⑹뼱瑜<EBBCB1> <20><EFBFBD><EFBFBD>븯怨<EBB8AF>, 肄붾뱶 <20>젋利<ECA08B> 怨좎쑀 而⑦뀒<E291A6><EFBFBD><EFBFBD>씤 `.codelens-decoration` <20>궡遺<EAB6A1><E981BA>씪 寃쎌슦<EC8E8C>뿉留<EBBF89> 臾댁떆<EB8C81><EFBFBD>룄濡<EBA384> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD>
- **二쇱쓽**: DOM <20><EFBFBD><EC8383><EFBFBD><EFBFBD> <20><EFBFBD>꽣 議곌굔 <20><EFBFBD><20><20><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD>뒗 UI <20><EFBFBD><EFBFBD>씤 媛쒗렪(Native, Editor Tab <20><20>쐞移<EC909E><><EFBFBD>)<29>뿉 留ㅼ슦 痍⑥빟<E291A5>븿. 媛<><E5AA9B>옣 援ъ껜<D18A><EFBFBD><20>궡遺<EAB6A1> <20><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><20><><EFBFBD><EFBFBD> 怨좎쑀 <20><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD>꽣留곹븷 寃<>
### [2026-03-31] [step-probe] GetAllCascadeTrajectories 10-Item Hard Limit (Signal Drop)
- **利앹긽**: `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-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).
- **<2A>빐寃<EBB990>** (v0.5.12):
1. `hub.py`: `{"type": "heartbeat"}` JSON 硫붿떆吏<EB9686> <20><EFBFBD><20>떆 紐낆떆<EB8286><EFBFBD>쑝濡<EC919D> `{"type": "pong"}` JSON<4F><20><EFBFBD><EFBFBD><EFBFBD>룄濡<EBA384> <20><EFBFBD>젙.
2. `ws-client.ts`: 紐낆떆<EB8286>쟻 `pong` <20><EFBFBD><EFBFBD>윭 異붽<E795B0><EBB6BD>. JSON pong 吏<><EFA79E><20>꽌踰꾧굅<EABEA7>굹 Node.js ws瑜<73> <20><EFBFBD><EFBFBD><20>븣留<EBB8A3> 60珥<30> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>썐 寃<>利앹쓣 嫄곗튂<EAB397>룄濡<EBA384> 議곌굔 蹂닿컯 (`forceHeartbeatTimeoutIfNoPong`).
- **二쇱쓽**: 釉뚮씪<EB9AAE><EFBFBD><EC8AA6><EFBFBD> <20>몴以<EBAAB4> WebSockets(W3C)<29>뒗 ping/pong <20><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD>쓣 JS濡<53> <20>끂異쒗븯吏<EBB8AF> <20><EFBFBD>쓬. <20>뤃由ы븘/<2F>겕濡쒖뒪<EC9296><EFBFBD><EFBFBD>뤌 WS <20><EFBFBD><20><EFBFBD><20><20><EFBFBD>듃鍮꾪듃<EABEAA>뒗 諛섎뱶<EC848E>떆 JSON 硫붿꽭吏<EABDAD> <20><EFBFBD><EFBFBD>쓽 Application Layer Ping/Pong<6E>쑝濡<EC919D> <20><><EFBFBD><EFBFBD><EFBFBD>궡嫄곕굹, Native WS API <20>뿬遺<EBBFAC><EFBFBD> <20><EFBFBD><EFBFBD>엳 泥댄겕<EB8C84><EFBFBD><20>븿.
### [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>룄.
- **<2A>빐寃<EBB990>** (v0.5.11): `catch` 釉붾줉<EBB6BE><EFBFBD>꽌 UTF-8 <20><EFBFBD>윭 媛먯<E5AA9B><EBA8AF> <20>떆 `stepOffset=currentCount-20`<60>쑝濡<EC919D> fallback <20>슂泥<EC8A82>. offset<65><20><EFBFBD><20>떆 `stallProbed=true` <20><EFBFBD><EFBFBD><EFBFBD>뿬 猷⑦봽 李⑤떒. `delta>0` <20>씠踰ㅽ듃 諛쒖깮 <20>떆 L433<33><EFBFBD><20><EFBFBD>룞 由ъ뀑.
- **二쇱쓽**: `stallProbed=true`<60><20>쁺援<EC81BA> Lock<63><20><EFBFBD><20><><EFBFBD> `delta>0` <20><20><EFBFBD>룞 由ъ뀑. UTF-8 <20><EFBFBD><EFBFBD>뒗 AG <20>꽌踰<EABD8C><> 臾몄젣(<28>씠誘몄<E8AA98><EBAA84>/諛붿씠<EBB6BF>꼫由<EABCAB> <20><EFBFBD><EFBFBD>꽣媛<EABDA3> ephemeral message<67><20><EFBFBD>븿)<29>씠誘<EC94A0><EFBFBD> Extension<6F><EFBFBD>꽌 graceful fallback留<6B> 泥섎━.
### [2026-03-28] [approval-handler] stepIndex 誘명솗<EBAA85><20>떆 wrong-stepIndex RPC <20>궘鍮<EAB698>
- **利앹긽**: DOM observer 寃쎈줈濡<ECA488> `terminal_command` pending <20><EFBFBD><20>썑 Discord <20><EFBFBD><20>떆 `HandleCascadeUserInteraction(stepIndex=0)` <20>넂 `"input not registered for step 0"` <20>넂 LS reconnect <20><20><EFBFBD><EFBFBD><20>넂 DOM click fallback<63>쑝濡<EC919D> <20><><EFBFBD><EFBFBD>븯. (wrong-LS<4C><53><EFBFBD> <20><EFBFBD><EFBFBD>븳 利앹긽<EC95B9><EFBFBD><20>떎瑜<EB968E> <20><EFBFBD>씤)
- **<2A><EFBFBD>씤**: `ctx.lastPendingStepIndex=-1` (step-probe媛<65> UTF-8 <20><EFBFBD>윭濡<EC9CAD> WAITING 誘멸컧吏<ECBBA7>)<29><EFBFBD><EFBFBD>룄 `Math.max(0, -1)=0`<60>쑝濡<EC919D> clamp<6D><EFBFBD>뼱 議댁옱<EB8C81>븯吏<EBB8AF> <20><EFBFBD>뒗 step 0<>뿉 RPC <20><EFBFBD>넚.
- **<2A>빐寃<EBB990>** (v0.5.11): `effectiveStepIndex = stepIndex >= 0 ? stepIndex : (lastPendingStepIndex >= 0 ? lastPendingStepIndex : -1)`. `effectiveStepIndex < 0`<60>씠硫<EC94A0> RPC 釉붾줉 <20>쟾泥<EC9FBE> skip <20>넂 DOM click 吏곹뻾 (湲곗〈怨<E38088> <20><EFBFBD><20><EFBFBD>씪, LS reconnect <20>궘鍮<EAB698> <20>젣嫄<ECA0A3>).
- **二쇱쓽**: 湲곗〈 洹쒖튃 #14(`uint32`<60><20><EFBFBD>닔 湲덉<E6B9B2><EB8D89>)<29><><EFBFBD> 異⑸룎泥섎읆 蹂댁씠<EB8C81>굹, `effectiveStepIndex=-1`<60><20>븣 RPC <20>옄泥대<EFA7A3><EB8C80> **<2A><EFBFBD><EFBFBD>븯吏<EBB8AF> <20><EFBFBD>쑝誘<EC919D><EFBFBD>** <20>쐞諛<EC909E> <20><EFBFBD>떂. RPC <20><EFBFBD><20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD>븳 stepIndex留<78> <20><EFBFBD>슜.
### [2026-03-25] [Architecture] Discord Signal Drop & Extension Freezes
- **利앹긽**: <20><EFBFBD>떆媛<EB9686> <20>옄由щ퉬<D189><ED89AC><EFBFBD> <20>썑 蹂듦<E8B982><EB93A6> <20>떆 Discord濡<64> <20><EFBFBD><20><EFBFBD>샇媛<EC8387> <20>삤吏<EC82A4> <20>븡嫄곕굹 VS Code UI媛<49> 媛꾪뿉<EABEAA>쟻/吏<><EFA79E><EFBFBD><EFBFBD>쑝濡<EC919D> 硫덉땄(Freeze).
- **<2A><EFBFBD>씤**:
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>엫.
### [2026-03-24] DOM Observer /trigger-click <20><EFBFBD>뜑留<EB9C91> <20><EFBFBD><20><EFBFBD><EFBFBD>룞 諛<> False Positive <20>봽由ъ쭠
- **利앹긽**: v0.5.9 <20>뙣移<EB99A3> <20><EFBFBD>썑 肄붾뵫 <20>떆 Agent <20>솕硫댁씠 <20><EFBFBD><EFBFBD><EFBFBD><20>꽌紐<EABD8C> <20><><EFBFBD><EFBFBD>(Pending) <20><EFBFBD>깭濡<EAB9AD> 硫덉땄. <20><EFBFBD><20><EFBFBD>뒪肄붾뱶<EBB6BE><EFBFBD>꽌 `Approve` <20><20><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD><EFBFBD>븳 `Run Test`(肄붾뱶 <20>젋利<ECA08B>)瑜<> <20>겢由<EAB2A2><E794B1>븿.
- **<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>씫)
- **<2A>빐寃<EBB990>** (v0.5.9): DOM scan, 由ъ뒯 <20><20>벑 紐⑤뱺 <20><EFBFBD>깋 濡쒖쭅 <20><><EFBFBD><EFBFBD><EFBFBD>꽣瑜<EABDA3> `button, [role="button"], vscode-button, .monaco-text-button` <20>쑝濡<EC919D> <20>쟾硫<EC9FBE> 媛쒗렪. <20>젙洹쒖떇<EC9296>쓣 `/^(?:Always )?Allow/i`濡<> <20><EFBFBD>젙.
### [2026-03-24] Python Hub <20><><EFBFBD><><EFBFBD> 而ㅻ꽖<E385BB>뀡 諛<> UI <20>봽由ъ쭠
- **利앹긽**: `npm run` 紐낅졊<EB8285>씠 `<EFBFBD><EFBFBD><20>젙梨<ECA099>` 愿<><E684BF><20>삤瑜섎줈 <20><EFBFBD>
- **<2A><EFBFBD>씤**: PowerShell <20><EFBFBD>겕由쏀듃 <20><EFBFBD><20>젙梨낆씠 <20><EFBFBD><EFBFBD>
- **<2A>빐寃<EBB990>**: `cmd /c npm run dev` <20><EFBFBD><EFBFBD>쑝濡<EC919D> cmd瑜<64> <20><EFBFBD><20><EFBFBD>
- **二쇱쓽**: 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>씤**: PowerShell<6C><EFBFBD>꽌 `curl`<60><><EFBFBD> `Invoke-WebRequest`<60>쓽 蹂꾩묶
- **<2A>빐寃<EBB990>**: **`curl.exe`**瑜<> 紐낆떆<EB8286><EFBFBD>쑝濡<EC919D> <20><EFBFBD>
- **二쇱쓽**: HTTP 愿<><E684BF>젴 紐⑤뱺 紐낅졊<EB8285><EFBFBD>꽌 `curl.exe` <20><EFBFBD><20><EFBFBD>
### [2026-03-09] Renderer DOM — webview iframe 격리 확인 + v3 deep traversal
- **증상**: Renderer trigger-click이 `document.querySelectorAll('button')`으로 버튼 검색 → Run 버튼 미발견. 감지된 것은 외부 DOM의 trust-level 버튼(`RunAlt+?`)뿐
- **원인**: Run/Accept 버튼은 AG 채팅 webview iframe (`vscode-webview://` origin) 안에 렌더링. 외부 workbench DOM (`vscode-file://` origin)에서 cross-origin으로 접근 불가
- **해결**: Renderer v3 `deepFindButtons()` 구현:
1. Main document 검색 (기존)
2. `iframe.contentDocument` 접근 시도 (same-origin이면 성공)
3. `<webview>.executeJavaScript()` 접근 시도 (Electron API)
4. Shadow DOM 재귀 탐색
**미검증** (AG 재시작 후 DOM-DUMP 결과 필요)
- **주의**: CDP(Chrome DevTools Protocol)는 **사용자 결정에 의해 명시적으로 거부됨** (`--remote-debugging-port` 필요, 비표준 접근). 표준 DOM API/Electron API 범위 내에서만 해결할 것
### [2026-03-09] Deep Inspect HTTP Endpoint — curl로 DOM 분석 트리거
- **증상**: AG DevTools 콘솔에 붙여넣기 불가 (Chromium 보안 정책)
- **원인**: Electron 렌더러 DevTools에서 `allow pasting`이 SyntaxError 발생 (`Unexpected identifier 'pasting'`)
- **해결**: Extension HTTP bridge에 `/deep-inspect` 엔드포인트 추가. `curl.exe http://127.0.0.1:34332/deep-inspect`로 bridge→renderer→재귀 DOM 분석→결과 JSON 반환. 결과는 `~/.gemini/antigravity/bridge/deep-inspect-result.json`에도 저장됨
- **주의**: 시작 3초 후 자동 실행 + curl로 재트리거 가능. renderer가 2초마다 `/deep-inspect-trigger` 폴링
--- ---
### [2026-03-09] workbench.html inline 스크립트 미삽입 — jetski만 패치한 버그
- **증상**: AG 재시작 후 `/deep-inspect` timeout — renderer v3 스크립트 미로딩
- **원인**: `setupApprovalObserver()``workbench-jetski-agent.html`에만 inline 삽입하고, `workbench.html`에는 외부 `<script src>`만 추가. `vscode-file://` 프로토콜은 커스텀 .js 파일을 서빙하지 않으므로 사실상 미로딩
- **해결**: HTML 패치 로직을 `htmlFiles = ['workbench.html', 'workbench-jetski-agent.html']` 루프로 변경하여 **양쪽 모두 inline** 삽입. 수동 pre-patch 스크립트(`/tmp/patch_workbench.py`)로 즉시 적용 + product.json 체크섬 업데이트
- **주의**: **항상 양쪽 HTML을 동일하게 패치**. AG가 어느 HTML을 로드할지 런타임까지 알 수 없음. pre-patch 후 1회 풀 재시작이면 충분 (체크섬 사전 업데이트 완료)
### [2026-03-09] V8 CachedData — 체크섬 정상이어도 스크립트 미실행
- **증상**: HTML 패치 + product.json 체크섬 일치 + Bridge 서버 정상 → 그런데도 renderer 스크립트 미실행 (`/deep-inspect` timeout) ## 誘명빐寃<EBB990> <20><EFBFBD>
- **원인**: AG 프로세스 인자 `--code-cache-schemes=vscode-webview,vscode-file`에 의해 V8 바이트코드 캐시가 `vscode-file://` 프로토콜에도 적용. product.json 체크섬 검증과 V8 코드 캐시는 **별도 메커니즘** — 체크섬이 맞아도 V8은 `%APPDATA%\Antigravity\CachedData`의 이전 컴파일된 바이트코드를 재사용
- **해결**: `%APPDATA%\Antigravity\CachedData\*` 전체 삭제 후 AG 풀 재시작. AG 실행 중에도 삭제 가능 (파일 잠금 없음 확인됨)
- **주의**: CachedData 삭제 후 첫 시작이 약간 느릴 수 있음 (V8이 모든 JS를 재컴파일). **HTML 패치 변경 시마다 CachedData 삭제 필수**. `product.json` 체크섬 업데이트만으로는 불충분
### [2026-03-23/24] <20><EFBFBD>깮 吏<><EFA79E><EFBFBD><EFBFBD>뒗 WebSocket 醫<><EFBFBD> 而ㅻ꽖<E385BB>뀡 諛<> False Positive 媛뺤젣 <20>뿰寃<EBBFB0> <20>걡源<EAB1A1> (v0.5.5 <20>넂 0.5.8)
- **利앹긽**:
1. (v0.5.5) <20><EFBFBD>쟾 紐⑤뱶 蹂듦뎄 <20><20><EFBFBD>뿰寃곗씠 <20><EFBFBD>뼱議뚯쓬<EB9AAF><EFBFBD><20><EFBFBD><EFBFBD><20>씠瑜<EC94A0> <20>씤吏<EC94A4><EFA79E>븯吏<EBB8AF> 紐삵븯<EC82B5>뒗 醫<><EFBFBD>(Half-open) <20>냼耳<EB83BC> 諛쒖깮.
2. (v0.5.6) 醫<><EFBFBD> <20>냼耳볦쓣 <20>옟湲<EC989F> <20><EFBFBD>빐 10珥<30> <20><><EFBFBD><EFBFBD>씠癒<EC94A0>(`pongTimeoutTimer`)瑜<> <20><EFBFBD><EFBFBD><EFBFBD>굹, VS Code<64>쓽 臾닿굅<EB8BBF><20><EFBFBD>씪 寃<><E5AF83><20>떆 Event Loop媛<70> 釉붾줈<EBB6BE><EFBFBD>릺硫<EBA6BA><>姨≫븳 <20>뿰寃곗씤<EAB397><EFBFBD><20><EFBFBD><20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>썐(False Positive) <20><EFBFBD><EFBFBD>쑝濡<EC919D> <20>뿰寃곗쓣 媛뺤젣 醫낅즺<EB8285>븿. <20>씠濡<EC94A0> <20><EFBFBD><20><EFBFBD><EFBFBD><20><EFBFBD>뿰寃<EBBFB0> <20><EFBFBD><EFBFBD>씠(Exponential Backoff)媛<> 60珥덇퉴吏<ED89B4> <20><EFBFBD><EFBFBD>굹硫댁꽌 <20><EFBFBD><EFBFBD><20>떖媛곹븯寃<EBB8AF> 硫덉땄(Freeze).
- **<2A><EFBFBD>씤**: Node.js `ws` <20><EFBFBD>씠釉뚮윭由ъ쓽 `ws.ping()`<60><><EFBFBD> 鍮꾨룞湲<EBA39E> I/O <20><EFBFBD><EFBFBD><EFBFBD><20>걧瑜<EAB1A7> <20><><EFBFBD><EFBFBD><EFBFBD>, `setTimeout(..., 10000)` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EC8D90><EFBFBD> Event Loop 釉붾줈<EBB6BE><20><EFBFBD>젣 吏곹썑 怨㏓컮濡<ECBBAE> 留뚮즺<EB9AAE><EFBFBD>뼱 踰꾨┝. <20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD>겕 I/O <20><EFBFBD>떟(pong)蹂대떎 濡쒖뺄 <20><><EFBFBD><EFBFBD>씠癒멸<E79992><EBA9B8> 癒쇱<E79992><EC87B1> <20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><20>냼耳볦쓣 二쎌엫.
- **<2A>빐寃<EBB990>** (v0.5.8 <20><EFBFBD>꽦):
- <20><EFBFBD><EFBFBD>븳 `setTimeout` 諛⑹떇 <20>룓湲<EBA393>.
- 湲곗〈<EAB397>쓽 25珥<35> 二쇨린 `setInterval` <20><EFBFBD>듃鍮꾪듃 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉 `Date.now() - lastPongTime > 60000` (60珥<30> 珥덇낵 <20><20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>썐) 寃<><EFBFBD> 濡쒖쭅<EC9296><20><EFBFBD>엯.
- 留뚯빟 Event Loop媛<70> <20><EFBFBD>떗 珥<><>由щ뜑<D189><EFBFBD>룄, 釉붾줈<EBB6BE><20><EFBFBD><20><20><EFBFBD>맂 I/O <20>씠踰ㅽ듃(`pong`)媛<> `setInterval` <20><><EFBFBD><EFBFBD>씠癒<EC94A0> 肄쒕갚 <20><EFBFBD><EFBFBD>뿉 癒쇱<E79992><EC87B1> 泥섎━<EC848E>릺嫄곕굹(Node.js Phase 洹쒖튃), <20><EFBFBD><EFBFBD>룄 60珥덈씪<EB8D88>뒗 踰꾪띁 <20>뜒遺꾩뿉 **False Positive 媛<><E5AA9B><EFBFBD><EFBFBD><20>썝泥<EC8D9D> 李⑤떒**<2A>븿怨<EBB8BF> <20><EFBFBD><EFBFBD>뿉 醫<><EFBFBD> <20>냼耳볦쓣 <20><EFBFBD><EFBFBD><EFBFBD>쑝濡<EC919D> <20>젣嫄고븿.
- **二쇱쓽**: Node.js<6A><20><EFBFBD><20><EFBFBD><EFBFBD>뱶 Event Loop <20>솚寃<EC869A>(<28><EFBFBD>엳 臾닿굅<EB8BBF><20>룞湲<EBA39E> <20><EFBFBD><EFBFBD><20><EFBFBD><EC98A6><EFBFBD> VS Code Extension)<29><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD>겕 I/O瑜<4F> 濡쒖뺄 `setTimeout`怨<> 寃쎌<(Race)<29><EFBFBD><EFBFBD><20>꽕怨꾨뒗 <20><EFBFBD><EFBFBD><EFBFBD>쑝濡<EC919D> False Positive瑜<65> <20><EFBFBD>쓬. Timestamp(`Date.now()`) 湲곕컲 媛꾧꺽 寃<><EFBFBD>(Interval check)<29><20><EFBFBD><20><EFBFBD><EFBFBD>븿.
### [2026-03-11] rejectAgentStep / !stop <20><><EFBFBD> AG 誘몃벑濡<EBB291> 而ㅻ㎤<E385BB>뱶 + <20><EFBFBD><EFBFBD><20><EFBFBD><20>븿<EFBFBD>닔 + <20><EFBFBD><EFBFBD><20>봽由щ<E794B1>명떚釉<EB969A>
- **利앹긽**: `!stop` 紐낅졊<EB8285>씠 AI瑜<49> 硫덉텛吏<ED859B> 紐삵븿. 濡쒓렇: "No active cascade" / "no session tracked yet"
- **<2A><EFBFBD>씤**: (1) `antigravity.agent.rejectAgentStep`<60><><EFBFBD> AG 誘몃벑濡<EBB291> 而ㅻ㎤<E385BB>뱶. (2) <20><><EFBFBD>泥댄븳 `getActiveCascadeId()`<60>뒗 **<2A><EFBFBD><EFBFBD>윭(DOM) <20><EFBFBD><20>븿<EFBFBD>닔** <20><><EFBFBD> Extension host<73><EFBFBD><20><EFBFBD>긽 `undefined` 諛섑솚. (3) **v0.4.5 <20><EFBFBD><EFBFBD><20><EFBFBD>뙣**: `extension.ts`<60>쓽 `getActiveSessionId: () => activeSessionId`媛<> module-level <20><EFBFBD>듃留<EB9383> <20>봽由щ<E794B1>명떚釉뚮<E98789><EB9AAE> 李몄“ <20><><EFBFBD> step-probe媛<65> `ctx.activeSessionId`瑜<> <20><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>룄 extension.ts<74>쓽 蹂<><E8B982><EFBFBD>뒗 遺덈<E981BA><EB8D88> (<28>봽由щ<E794B1>명떚釉<EB969A> 蹂듭궗)
- **<2A>빐寃<EBB990>** (2026-03-18 v0.4.6): `step-probe.ts`<60><EFBFBD>꽌 `getActiveSessionId()` getter <20>븿<EFBFBD>닔 export <20>넂 extension.ts closures<65><EFBFBD>꽌 `getStepProbeSessionId()` <20>샇異<EC8387>. <20><EFBFBD>젣 step-probe<62>쓽 live `ctx.activeSessionId`瑜<> 吏곸젒 <20><EFBFBD>쓬 (`ab0c116`)
- **二쇱쓽**: JS<4A><EFBFBD>꽌 **string/number<65><20>봽由щ<E794B1>명떚釉뚮씪 李몄“ <20><EFBFBD>떖 遺덇<E981BA><EB8D87>** <20><><EFBFBD> 媛앹껜 <20><EFBFBD><EFBFBD>쓣 怨듭쑀<EB93AD><EFBFBD>젮硫<ECA0AE> getter <20>븿<EFBFBD><EFBFBD>굹 媛앹껜 <20><EFBFBD><20><EFBFBD><20><EFBFBD>
- **Vikunja**: #411, #410
### [2026-03-19] browser_subagent Allow <20><><EFBFBD> <20>옒紐삳맂 RPC payload
- **利앹긽**: <20>꽌釉<EABD8C> <20><EFBFBD><EFBFBD><EFBFBD>듃 "execute JavaScript on localhost" Allow 踰꾪듉<EABEAA><20><EFBFBD><20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD>
- **<2A><EFBFBD>씤**: `step-probe.ts`<60><EFBFBD>꽌 `browser_subagent` toolName<6D>씠 step_type 遺꾨쪟 <20><EFBFBD>씠 raw toolName<6D>쑝濡<EC919D> <20><EFBFBD><20>넂 `approval-handler.ts`<60><EFBFBD>꽌 `runExtensionCode` 留ㅽ븨<E385BD><20><EFBFBD>븿<EFBFBD>릺吏<EBA6BA> <20><EFBFBD>븘 default `runCommand` RPC payload <20><EFBFBD><20>넂 AG媛<47> <20>옒紐삳맂 interaction type<70>쑝濡<EC919D> 臾댁떆
- **<2A>빐寃<EBB990>** (v0.5.1): `approval-handler.ts` L384<38>뿉 `browser_subagent` 異붽<E795B0><EBB6BD>, `step-probe.ts` L481/L549<34>뿉 `browser_subagent`/`open_browser_url` step_type 遺꾨쪟 異붽<E795B0><EBB6BD> (`549af6d`)
- **二쇱쓽**: <20>깉濡쒖슫 AG <20>룄援<EBA384> 異붽<E795B0><EBB6BD> <20>떆 諛섎뱶<EC848E>떆 (1) step-probe step_type 留ㅽ븨 (2) approval-handler RPC payload 留ㅽ븨 <20>뼇履<EBBC87> 紐⑤몢 <20><EFBFBD><EFBFBD><EFBFBD>
### [2026-03-21] Idle<6C>넂Resume <20><EFBFBD><20><EFBFBD><20><><EFBFBD> 3以<33> 踰꾧렇
- **利앹긽**: AG <20><EFBFBD>떆媛<EB9686> idle <20><20><EFBFBD><20>옱媛<EC98B1> <20>떆 Discord <20><EFBFBD><20><EFBFBD>샇媛<EC8387> <20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD>
- **<2A><EFBFBD>씤**: (1) `ws-client.ts` `auth_fail` <20>떆 `shouldReconnect=false` <20><><EFBFBD> JWT 24h 留뚮즺 <20>떆 WS <20>쁺援<EC81BA> 醫낅즺. (2) `hub.py` `_disconnect`<60><EFBFBD><20><EFBFBD><20>뿰寃<EBBFB0> <20>떆 `pending_owners` <20><EFBFBD><20><><EFBFBD> <20><EFBFBD>뿰寃<EBBFB0> <20>썑 Discord 踰꾪듉 臾댄슚. (3) `step-probe.ts` `stallProbed=true` + `lastPendingStepIndex=N`<60>씠 WS <20><EFBFBD>뿰寃<EBBFB0> <20>떆 由ъ뀑 <20><20><20><><EFBFBD> WAITING step <20><EFBFBD><EFBFBD><20>쁺援<EC81BA> 李⑤떒
- **<2A>빐寃<EBB990>** (v0.5.2): (1) `auth_fail` <20>넂 `registrationCode` <20><EFBFBD><EFBFBD>룄. (2) `pending_owners` orphan 留덉빱濡<EBB9B1> 蹂댁〈+<2B><EFBFBD><EFBFBD>떦. (3) `resetPendingStateForReconnect()` + `onConnected`<60><EFBFBD><20>샇異<EC8387>
- **二쇱쓽**: WS `onConnected`<60><EFBFBD>꽌 諛섎뱶<EC848E>떆 step-probe <20><EFBFBD>깭 由ъ뀑 <20><EFBFBD>닔. `stallProbed`/`lastPendingStepIndex`<60>뒗 TTL <20><EFBFBD><20>쁺援<EC81BA><>
--- ---
## 승인 전략 결정 체인 (다음 세션 필독)
> **이 섹션은 2026-03-08~09 전체 세션의 시행착오를 요약합니다.**
> **다음 세션은 이미 거부된 접근을 다시 시도하지 마세요.**
### ❌ 시도 후 거부된 접근 (재시도 금지) > [!NOTE]
| # | 접근 | 결과 | 거부 사유 | > v0.4.5 <20><EFBFBD><20><EFBFBD>빆(Hub pending_owners, diff_review WS, auto_approve <20>씠以묒벐湲<EBB290>, WS dual-write, ApprovalView fallback)<29><><EFBFBD>
|---|------|------|-----------|
| 1 | LS RPC `HandleCascadeUserInteraction` | `socket hang up` | AG 빌드에서 핸들러 미구현 |
| 2 | LS RPC `ResolveOutstandingSteps` | CANCEL 동작 (승인이 아님) | 명칭과 달리 step을 취소함 |
| 3 | VS Code 7개 승인 명령 (`terminalCommand.run` 등) | `command not found` | AG 런타임에 미등록 |
| 4 | 키보드 시뮬레이션 (`type {Enter}`) | 빈 메시지 전송 | Chat input에 캡처됨 |
| 5 | `sendChatActionMessage` / `executeCascadeAction` | 미등록 | 119개 명령에 없음 |
| 6 | pywinauto (OS 레벨) | 사용자 결정 폐기 | 크로스플랫폼 불가, 창 겹침 문제 |
| 7 | CDP (Chrome DevTools Protocol) | **사용자 명시적 거부** | 비표준, `--remote-debugging-port` 필요 |
| 8 | Renderer v1 DOM Click (flat scan) | Run 버튼 미발견 | webview iframe 격리 |
### ✅ 현재 진행 중인 접근 > 肄붾뱶 <20><EFBFBD><20>셿猷뚮맖. E2E <20><EFBFBD>빀 寃<>利앹<EFA79D><EC95B9> Vikunja #410<31><EFBFBD>꽌 異붿쟻 以<>.
| # | 접근 | 상태 | 다음 단계 |
|---|------|------|-----------|
| 1 | **Renderer v3 Deep DOM Traversal** | 패치+체크섬 OK, **V8 CachedData 삭제 완료, AG 재시작 후 검증** | AG 재시작 → `curl.exe http://127.0.0.1:34332/deep-inspect` |
| 2 | `<webview>.executeJavaScript()` | v3에 포함, **미검증** | 위 결과에서 `hasExecJS=true`이면 유력 |
| 3 | TerminalExecutionPolicy.EAGER (Turbo) | 미시도, **최후 수단** | 통제권 포기이므로 사용자 승인 필요 |
### 🔑 핵심 전제
- Run/Accept 버튼은 **`vscode-webview://` origin의 격리된 iframe** 안에 있음
- 외부 workbench DOM에서 `querySelector`로 접근 **불가** (cross-origin)
- `<webview>.executeJavaScript()`**유일한 표준 관통 경로** (Electron API)
- AG 패치 후 **반드시 풀 프로세스 재시작** 필요 (Reload Window 불충분)
- **양쪽 HTML (workbench.html + workbench-jetski-agent.html) 모두 inline 패치** 필수
- HTML 패치 변경 시 **`%APPDATA%\Antigravity\CachedData` 삭제 필수** (V8 바이트코드 캐시 무효화)
- **CSP `script-src``'unsafe-inline'` 패치 필수** (없으면 인라인 스크립트 무조건 차단)
### [2026-03-09] CSP script-src — 인라인 스크립트 무조건 차단 ### [2026-03-21] stepIndex=-1 <20><><EFBFBD> AG proto uint32 <20><EFBFBD>
- **증상**: HTML 패치 ✅, 체크섬 ✅, CachedData 삭제 ✅ — 그런데도 renderer v3 스크립트 미실행 (`/deep-inspect` timeout)
- **원인**: `workbench.html`의 CSP `<meta http-equiv="Content-Security-Policy">`에서 `script-src 'self' 'unsafe-eval' blob:`**`'unsafe-inline'` 없음**. 브라우저가 인라인 `<script>` 태그를 무조건 차단
- **해결**: CSP의 `script-src``'unsafe-inline'` 추가. Extension `setupApprovalObserver()`에 CSP 자동 패치 로직 영구 추가
- **주의**: `style-src`에는 `'unsafe-inline'`이 있어 스타일은 동작 → 스크립트만 차단되는 것이 함정. `connect-src``http://127.0.0.1:*` 존재하여 HTTP fetch는 허용됨 (CSP 통과 시 통신은 문제없음). 이 CSP 이슈는 **V8 CachedData 이슈와 동시에 존재**하여 진단이 매우 어려웠음
### [2026-03-09] 중복 승인 요청 — DOM scan + Step probe 동시 발동 - **利앹긽**: DOM observer媛<72> Allow 踰꾪듉 媛먯<E5AA9B><EBA8AF> <20>넂 Discord <20><EFBFBD><20>넂 RPC `HandleCascadeUserInteraction` 400 <20><EFBFBD>
- **증상**: Discord에 같은 명령에 대해 승인 요청이 2개 도착
- **원인**: DOM Observer가 `RunAlt+↵` 버튼을 감지하여 pending 생성 + Step probe가 `CORTEX_STEP_STATUS_WAITING` 감지하여 pending 생성 → 동일 step에 대해 2개 파일
- **해결**: `writePendingApproval()`에 15초 dedup 윈도우 추가 — 최근 DOM observer pending이 있으면 step probe pending 스킵
- **주의**: DOM scan은 제거 불가 — `Allow Once`, `Allow This Conversation` 등 step probe가 감지 못하는 UI 버튼 존재. 버튼 텍스트도 `(Alt|Ctrl|Shift|Meta)\+.*` 정규식으로 키보드 단축키 정제
### [2026-03-09] Step probe reject → ResolveOutstandingSteps가 AI 작업 취소 - **<2A><EFBFBD>씤**: DOM observer 寃쎈줈<EC8E88>뒗 step index瑜<78> 紐⑤쫫 <20>넂 `stepIndex=-1` <20><EFBFBD><20>넂 AG proto `uint32` <20><EFBFBD><EFBFBD><20><EFBFBD>닔 遺덇<E981BA><EB8D87>
- **증상**: Discord에서 거부 클릭 → AI의 현재 step뿐 아니라 진행 중인 작업 전체가 중단
- **원인**: step probe 경로의 `tryApprovalStrategies(approved=false)``ResolveOutstandingSteps` RPC 호출 → 이것은 step을 **CANCEL**하는 파괴적 동작
- **해결**: step probe 경로에서 reject 시 `tryApprovalStrategies` 호출 제거. reject은 로그만 남기고 파기적 동작 없음. DOM observer 경로만 실제 Reject 버튼 클릭 허용
- **주의**: `ResolveOutstandingSteps`는 이름과 달리 "해결"이 아닌 "취소". 승인에 절대 사용 금지 (이전 이슈 #55~59 참조)
- **<2A>빐寃<EBB990>**: `Math.max(0, ...)` 濡<> clamp. `permission` type <20>넂 `runExtensionCode.confirm` 留ㅽ븨 異붽<E795B0><EBB6BD> (v0.5.4)
- **二쇱쓽**: DOM observer 寃쎈줈<EC8E88>쓽 step_type<70><65><EFBFBD> <20><EFBFBD>긽 `stepIndex=-1`<60><20><20><EFBFBD>쑝誘<EC919D><EFBFBD> proto <20><EFBFBD><20><20><EFBFBD>닔 蹂댁옣 <20><EFBFBD>
### [2026-03-21] reviewAbsoluteUris <20><><EFBFBD> latestNotifyUserStep <20><EFBFBD>뱶紐<EBB1B6> 遺덉씪移<EC94AA>
- **利앹긽**: `notify_user`<60>쓽 PathsToReview <20><EFBFBD>씪 由대젅<EB8C80>씠媛<EC94A0> <20>븳 踰덈룄 <20><EFBFBD><EFBFBD>븯吏<EBB8AF> <20><EFBFBD>
- **<2A><EFBFBD>씤**: AG <20><EFBFBD><20><EFBFBD>뱶紐<EBB1B6> `reviewAbsoluteUris` vs 肄붾뱶 `pathsToReview`/`paths_to_review`/`filePaths`
- **<2A>빐寃<EBB990>**: `reviewAbsoluteUris` 瑜<><> 踰덉㎏ <20>썑蹂대줈 異붽<E795B0><EBB6BD> (v0.5.3)
- **二쇱쓽**: AG RPC <20><EFBFBD>뱶紐낆<EFA78F><EB8286> extension.log `[NOTIFY-STEP] keys=` 濡<> <20><EFBFBD>씤 媛<><E5AA9B>뒫. 異붿륫 湲덉<E6B9B2><EB8D89>
### [2026-03-21] <20><EFBFBD><20><EFBFBD><20><><EFBFBD><> WAITING 媛먯<E5AA9B><EBA8AF> 20-25s 吏<><EFA79E>
- **利앹긽**: <20><20><><EFBFBD><EFBFBD><20><EFBFBD><20>썑 泥<> run_command <20><EFBFBD><EFBFBD>씠 Discord<72><20><20>삤怨<EC82A4> AG<41><EFBFBD>꽌 吏곸젒 <20><EFBFBD><EFBFBD><EFBFBD><20>븿
- **<2A><EFBFBD>씤**: `lastModTime=''` 由ъ뀑 <20>넂 `modTimeChanged=true` <20>넂 THINKING 遺꾧린 諛섎났 <20>넂 probe 15-25s 吏<><EFA79E>
- **<2A>빐寃<EBB990>**: `lastModTime=currentModTime` + `return` <20>젣嫄<ECA0A3> + 利됱떆 probe 媛뺤젣 + <20>쉶洹<EC89B6><><E5AA9B>뱶 異붽<E795B0><EBB6BD> (v0.5.3)
- **二쇱쓽**: <20><EFBFBD><20><EFBFBD><20>떆 `wasRunning`/`pendingModifiedFiles` 由ъ뀑 <20><EFBFBD>닔 (<28><EFBFBD><20><EFBFBD><20><EFBFBD>뿬臾쇰줈 false diff_review 諛⑹<E8AB9B><E291B9>)
---
## <20><EFBFBD><20><EFBFBD>뾽 洹쒖튃 (怨쇨굅 <20><EFBFBD><EFBFBD><EFBFBD>꽌 諛섎났<EC848E><20><EFBFBD>꽩)
> <20><EFBFBD><EFBFBD>뒗 怨쇨굅 <20><EFBFBD><EFBFBD><EFBFBD>꽌 諛섎났<EC848E><EFBFBD>쑝濡<EC919D> <20><EFBFBD><EAB5B9><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD>쓣 洹쒖튃<EC9296>쑝濡<EC919D> <20>젙由ы븳 寃껋엯<EABB8B><EFBFBD>떎.
| # | 洹쒖튃 | 愿<><E684BF><20><EFBFBD>뒋 (archive 李몄“) |
|---|------|--------------------------|
| 1 | **Hub WS<57><53><EFBFBD> file bridge<67><20><EFBFBD>샇 諛고<E8AB9B><EAB3A0><EFBFBD>쟻** <20><><EFBFBD> `if hub: ws + return` / `else: file` | WS dual-write, _auto_approve <20>씠以<EC94A0> <20>벐湲<EBB290> |
| 2 | **WS 寃쎈줈 異붽<E795B0><EBB6BD> <20>떆 file-bridge<67>쓽 紐⑤뱺 遺꾧린瑜<EBA6B0> <20><EFBFBD>똿** | diff_review WS regression |
| 3 | **AG RPC `{}` <20><EFBFBD><EFBFBD><EB969F><EFBFBD> <20><EFBFBD>뙣濡<EB99A3> 媛꾩<** <20><><EFBFBD> 硫붿꽌<EBB6BF>뱶紐<EBB1B6> <20><><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD>씠 `{}` 諛섑솚 | AcknowledgeCascadeCodeEdit |
| 4 | **ResolveOutstandingSteps<70>뒗 CANCEL <20><EFBFBD>옉** <20><><EFBFBD> <20><EFBFBD><EFBFBD><20><EFBFBD><ECA085><EFBFBD> <20><EFBFBD>슜 湲덉<E6B9B2><EB8D89> | Step probe reject |
| 5 | **Extension 肄붾뱶 <20><EFBFBD><20>썑 諛섎뱶<EC848E>떆 VSIX 鍮뚮뱶 + AG <20><><EFBFBD> <20><EFBFBD><EFBFBD>옉** | Extension 踰꾩쟾 誘몃같<EBAA83>룷 |
| 6 | **HTML <20>뙣移<EB99A3><><EFBFBD> <20>떆 V8 CachedData <20><EFBFBD><20><EFBFBD>닔** | V8 CachedData, CSP |
| 7 | **`bridge/pending/` 議곗옉 <20>떆 諛섎뱶<EC848E>떆 `project_name` + `conversation_id` <20><EFBFBD>꽣** | <20>겕濡쒖뒪 <20>봽濡쒖젥<EC9296>듃 DEDUP MERGE |
| 8 | **`processResponseFile` <20><EFBFBD>깭 由ъ뀑<D18A><EB8091><EFBFBD> `sawRunningAfterPending=true`留<>** | processResponseFile 臾댄븳 猷⑦봽 |
| 9 | **fs.watch Windows 遺덉븞<EB8D89><20><><EFBFBD> 諛섎뱶<EC848E>떆 polling fallback 蹂묓뻾** | fs.watch silent fail |
| 10 | **diff_review<65>뒗 VS Code 而ㅻ㎤<E385BB>뱶留<EBB1B6> <20><EFBFBD>슚** <20><><EFBFBD> RPC 3媛<33> <20><EFBFBD>왂 紐⑤몢 <20><EFBFBD><20><EFBFBD>젙 | diff_review RPC dead-end |
| 11 | **HttpBridgeContext<78><20>봽由щ<E794B1>명떚釉<EB969A> by-value 蹂듭궗 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> 蹂꾨룄 媛앹껜 <20><EFBFBD><20>떆 getter <20><EFBFBD>슜 | HttpBridgeContext stale primitive |
| 12 | **<2A>깉 AG <20>룄援<EBA384> 異붽<E795B0><EBB6BD> <20>떆 step-probe step_type 留ㅽ븨 + approval-handler RPC payload 留ㅽ븨 <20>뼇履<EBBC87> <20><EFBFBD>닔** | browser_subagent Allow |
| 13 | **WS `onConnected`<60><EFBFBD>꽌 step-probe <20><EFBFBD>깭 由ъ뀑 <20><EFBFBD>닔** <20><><EFBFBD> `stallProbed`/`lastPendingStepIndex`<60>뒗 TTL <20><EFBFBD><20>쁺援<EC81BA><> | Idle<6C>넂Resume <20><EFBFBD><20><EFBFBD>떎 |
| 14 | **AG proto `uint32` <20><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD>떖 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> `stepIndex` <20><EFBFBD><EBB291><EFBFBD> `Math.max(0, ...)` <20><EFBFBD>닔 | stepIndex=-1 RPC 400 |
| 15 | **RPC "input not registered" = wrong-LS <20>뿰寃<EBBFB0>** <20><><EFBFBD> `fixLSConnection()` <20><EFBFBD><20><EFBFBD><EFBFBD><20><EFBFBD>닔, `lines.length<=1` 議곌린醫낅즺 湲덉<E6B9B2><EB8D89> | Deriva wrong-LS (v0.5.5) |
| 16 | **<2A><EFBFBD><EFBFBD><EFBFBD>뀡(Bridge)<29><><EFBFBD> <20><EFBFBD><EFBFBD>쟻 鍮꾩쫰<EABEA9><EFBFBD><20><EFBFBD><20><EFBFBD><ECA085><EFBFBD> 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> `SafeToAutoRun` <20><EFBFBD>쓽 議곌굔 釉뚮옖移<EC9896> 遺꾧린<EABEA7>뒗 紐⑤몢 遊뉗쑝濡<EC919D> <20><EFBFBD>엫 (Agnostic Bridge) | SafeToAutoRun Deadlock (v0.5.15) |
| 17 | **package.json 鍮뚮뱶 <20><EFBFBD>겕由쏀듃 媛뺤젣** <20><><EFBFBD> `vscode:prepublish` 異붽<E795B0><EBB6BD><EFBFBD> <20><EFBFBD><EAB68A><EFBFBD> <20><EFBFBD>뒪 諛고룷 <20>썝泥<EC8D9D> 李⑤떒 | VSIX v0.5.15 鍮뚮뱶 <20><EFBFBD>씫 |
| 18 | **<2A>룞湲곗떇 `cp.execSync` <20><EFBFBD>슜 湲덉<E6B9B2><EB8D89>** <20><><EFBFBD> Windows <20>솚寃쎌뿉<EC8E8C>꽌 硫붿씤 <20>씠踰ㅽ듃猷⑦봽 <20>봽由ъ쭠 諛<> WS heartbeat <20><EFBFBD><20>쑀諛<EC9180> | detectProjectName <20>봽由ъ쭠 |
### [2026-04-09] [Bot/Extension] Discord Signal Relay Failure & Empty Body
- **利앹긽**: <20><EFBFBD>뒪肄붾뱶 遊뉗<E9818A><EB8997> '<27><EFBFBD><20><EFBFBD><EFBFBD>맖'<27><20><EFBFBD>슦吏<EC8AA6><EFBFBD> <20><EFBFBD>젣 肄붾뱶 蹂몃Ц<EBAA83><20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20>븡怨<EBB8A1>, 梨꾨꼸<EABEA8>뿉 吏꾩쭨 梨꾪똿 硫붿떆吏<EB9686><EFA79E><20>븣由쇱씠 <20><EFBFBD><20><20><EFBFBD>뿉 諛<><E8AB9B><20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD>쓬.
- **<2A><EFBFBD>씤**: 1) observer-script.ts<74><EFBFBD>꽌 踰꾪듉 <20><EFBFBD><EFBFBD>듃 留ㅼ묶 <20>떆 Run <20><EFBFBD><EFBFBD>쓽 寃쎄퀎(\b) 泥섎━瑜<E29481> <20>븯吏<EBB8AF> <20><EFBFBD>븘 VS Code <20><EFBFBD><EFBFBD>쓽 'Running 1 command'瑜<><>濡쒖콈<EC9296>뼱 PENDING <20><EFBFBD>뙵 臾댄븳 <20><EFBFBD>꽦. 2) bot.py<70><EFBFBD><20><EFBFBD><20><EFBFBD>씤 Embed <20><EFBFBD><20>떆 req.description<6F>쓣 洹몃━吏<E29481> <20>븡怨<EBB8A1> 踰꾪듉 <20><EFBFBD><EFBFBD>듃(req.command)留<> <20><EFBFBD>떆. 3) step-probe.ts<74><EFBFBD><20><EFBFBD>뀡 援먯껜 <20>떆 理쒓렐 <20>븣由<EBB8A3> <20><EFBFBD><EFBFBD>뒪 珥덇린<EB8D87>솕瑜<EC8695> <20>옒紐삵븯<EC82B5><20><EFBFBD><EFBFBD>쓽 泥<> 硫붿떆吏<EB9686><EFBFBD> 臾댁“嫄<E2809C> <20>뱶濡<EBB1B6>.
- **<2A>빐寃<EBB990>**: DOM 媛먯<E5AA9B><EBA8AF> <20>젙洹쒖떇<EC9296>뿉 \b 媛뺤젣 遺<><E981BA>뿬 (/Run\b/), bot.py<70>쓽 Auto-Approve 履<> Embed 蹂몃Ц<EBAA83>뿉 req.description <20><EFBFBD>뜑留<EB9C91> 異붽<E795B0><EBB6BD>, step-probe.ts<74><EFBFBD>꽌 session init <20>떆 index瑜<78> -1濡<31> 由ъ뀑.
- **二쇱쓽**: Native UI <20><EFBFBD><EFBFBD>듃 媛먯<E5AA9B><EBA8AF> <20><20><EFBFBD>뼱 寃쎄퀎(\b)源뚯<E6BA90><EB9AAF><>利앺빐<EC95BA>빞 False Positive瑜<65> 留됱쓣 <20><20><EFBFBD>쑝硫<EC919D>, Auto-Approve<76>뒗 諛섎뱶<EC848E>떆 蹂몃Ц<EBAA83><20>끂異쒗빐<EC9297><20>븿.
### [2026-04-10] [Extension] AI Response Content Missing (Nested PlannerResponse)
- **利앹긽**: <20><EFBFBD>뒪肄붾뱶 梨꾪똿諛⑹뿉 Agent<6E><20><EFBFBD><EFBFBD><20><EFBFBD>떟(AI <20><EFBFBD>떟)<29><20><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD>쓬.
- **<2A><EFBFBD>씤**: GetCascadeTrajectorySteps媛<73> 諛섑솚<EC8491><EFBFBD>뒗 plannerResponse媛<65> <20>봽濡쒗넗肄<EB8497> 諛⑹떇<E291B9><20><EFBFBD>씪 理쒖긽<EC9296>떒(s.plannerResponse)<29><20><EFBFBD>땶 s.step.plannerResponse<73>뿉 以묒꺽<EBAC92><EFBFBD><20><EFBFBD><EFBFBD><20><20><EFBFBD>쓬. 湲곗〈 <20><EFBFBD><EFBFBD><20><EFBFBD>뱶肄붾뵫<EBB6BE><20><EFBFBD>뱶 諛<> <20><EFBFBD>옯 援ъ“留<E2809C> 議고쉶<EAB3A0><EFBFBD><20><EFBFBD><EFBFBD>쓣 踰꾨┝.
- **二쇱쓽**: AG RPC <20><EFBFBD>뱶紐<EBB1B6> 援ъ“ 異붿륫 湲덉<E6B9B2><EB8D89>. <20><EFBFBD><20><20><EFBFBD>뱶諛뺤뒪濡<EB92AA> <20>몢 媛<><EFBFBD> 援ъ“(Flat, Nested) 紐⑤몢 紐⑦궧<E291A6><EFBFBD>뿬 吏곸젒 <20><EFBFBD><20><EFBFBD>씤.
### [2026-04-10] [Extension] Fast Execution `<5s` Response Capture Missed (IDLE-to-IDLE)
- **利앹긽**: <20><EFBFBD>뒪肄붾뱶濡<EBB1B6> <20><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD>쓬. `[RT-CAPTURE]`, `[RESPONSE-CAPTURE]` 濡쒓렇 紐⑤몢 <20><EFBFBD><EC9FBE><EFBFBD> <20>궓吏<EAB693> <20><EFBFBD>쓬.
- **<2A><EFBFBD>씤**: AI <20><EFBFBD><EFBFBD><EFBFBD>굹 肄붾뵫 <20><EFBFBD><EFBFBD>씠 5珥<35>(<28>뤃留<EBA483> 二쇨린) 誘몃쭔<EBAA83>쑝濡<EC919D> 留ㅼ슦 鍮좊<E285A4> <20><EFBFBD>굹硫<EAB5B9>, <20><EFBFBD><EFBFBD>씠 `IDLE -> IDLE` <20><EFBFBD>깭留<EAB9AD><>李고븯硫<EBB8AF> `wasRunning` <20><EFBFBD>옒洹멸<E6B4B9><EBA9B8> `false`濡<> <20>쑀吏<EC9180><EFA79E>맖. 湲곗〈 `[RESPONSE-CAPTURE]` 議곌굔<EAB38C>떇(`wasRunning && !isRunning && currentCount > ...`)<29>씠 `wasRunning=false`濡<> <20><EFBFBD>빐 釉붾줉<EBB6BE><EFBFBD>뼱 罹≪쿂 <20>옄泥대<EFA7A3><EB8C80> <20><EFBFBD><EFBFBD>엳 嫄대꼫<EB8C80>쎇寃<EC8E87> <20>맖.
- **<2A>빐寃<EBB990>**: `wasRunning` 寃<>利앹쓣 <20><EFBFBD><EFBFBD>븯怨<EBB8AF> `!isRunning && currentCount > lastResponseCaptureStep` 議곌굔<EAB38C>쑝濡<EC919D> <20><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD>맂 step<65><20><EFBFBD><20>븣 臾댁“嫄<E2809C> 罹≪쿂<E289AA><EFBFBD>룄濡<EBA384><><EFBFBD>. 異붽<E795B0><EBB6BD><EFBFBD> <20><EFBFBD><EFBFBD>맂 `[RESPONSE-CAPTURE]` <20><20><EFBFBD>뱶肄붾뵫 <20><EFBFBD>꽌瑜<EABD8C> `extractPlannerText`濡<> <20><EFBFBD><EFBFBD><20><EFBFBD>슜.
- **二쇱쓽**: <20>뤃留<EBA483> 諛⑹떇<E291B9><EFBFBD><EFBFBD><20><EFBFBD>깭(RUNNING->IDLE) <20><EFBFBD>씠瑜<EC94A0> <20><EFBFBD><EFBFBD><20><20><EFBFBD>쑝誘<EC919D><EFBFBD>, Step Count(<28><EFBFBD><EFBFBD><20>쟾吏<EC9FBE>)<29><EFBFBD>뒗 100% <20>떊猶<EB968A><><E5AA9B><EFBFBD>븳 留덉빱瑜<EBB9B1> <20><EFBFBD><20><20><EFBFBD><20>뿬遺<EBBFAC><EFBFBD> 媛먯<E5AA9B><EBA8AF><EFBFBD><EFBFBD><20>븿.
### [2026-04-10] [Bot] chat_snapshot_scanner 臾댄븳 Abort 諛<> <20><EFBFBD><20>쟻泥<EC9FBB> (Exception <20><EFBFBD>씫)
- **利앹긽**: 遊뉗씠 <20><EFBFBD>뒪肄붾뱶濡<EBB1B6> AI <20>떟蹂<EB969F>(梨꾪똿 <20><EFBFBD><EFBFBD>꺑)<29><20><EFBFBD><EC9FBE><EFBFBD> <20><EFBFBD><EFBFBD>븯吏<EBB8AF> 紐삵븯怨<EBB8AF> <20><EFBFBD>씠 嫄몃┝. ridge/chat_snapshots/<2F>뿉 泥섎━<EC848E>릺吏<EBA6BA> <20><EFBFBD><EBB8A1><EFBFBD> JSON <20><EFBFBD><EFBFBD><20><EFBFBD>떗 媛<> <20>쟻泥대맖.
- **<2A><EFBFBD>씤**: ot.py<70>쓽 chat_snapshot_scanner<65><EFBFBD><20><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD><EFBFBD><20><20>궡遺<EAB6A1><E981BA> .unlink() 怨쇱젙<EC87B1><EFBFBD>꽌 諛쒖깮<EC9296><EFBFBD><20><EFBFBD><EFBFBD>굹 discord.Embed <20><EFBFBD><20><EFBFBD><20><EFBFBD>쓣 猷⑦봽 <20><EFBFBD><EFBFBD><20><EFBFBD>븘二쇱<E4BA8C><EC87B1> 紐삵븿. 泥<> <20><EFBFBD><20><EFBFBD>씪(poison pill)<29>쓣 留뚮굹<EB9AAE><20>닚媛<EB8B9A> 猷⑦봽 <20>쟾泥닿<EFA7A3><EB8BBF> <20><EFBFBD><EFBFBD><EFBFBD><20>뮘履쎌쓽 <20><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD>엳 泥섎━<EC848E>릺吏<EBA6BA> <20>븡怨<EBB8A1> <20><EFBFBD><20><20>뒪耳<EB92AA>以꾩뿉<EABEA9><20><EFBFBD>떆 泥<> <20><EFBFBD><EFBFBD>뿉 留됲옒.
- **<2A>빐寃<EBB990>**: 猷⑦봽 <20>궡遺<EAB6A1><E981BA>뿉 except Exception<6F>쓣 異붽<E795B0><EBB6BD><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD>쇅瑜<EC8785> <20><EFBFBD>븘 諛⑹뼱. <20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><EC94AA><EFBFBD> glob<6F><EFBFBD>꽌 諛섎났 <20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20>븡寃<EBB8A1> .json.failed濡<64> <20><EFBFBD>쉶(rename)<29>떆耳<EB9686> <20>걧瑜<EAB1A7> 鍮꾩썙以<EC8D99>.
- **二쇱쓽**: <20>뤃留<EBA483>/<2F>뒪罹먮꼫 or 猷⑦봽 <20>궡遺<EAB6A1><E981BA><EFBFBD><EFBFBD>뒗 媛쒕퀎 <20><EFBFBD><EFBFBD><20><EFBFBD><20>떒怨꾩뿉<EABEA9>꽌 諛쒖깮 媛<><E5AA9B><EFBFBD>븳 紐⑤뱺 <20><EFBFBD><20><EFBFBD><EFBFBD><20><><EFBFBD><EFBFBD>븳 Defensive Catch 諛<> Continue(<28><EFBFBD>쉶) 濡쒖쭅<EC9296><20><EFBFBD><EFBFBD>엫.
### [2026-04-10] [Extension] GetAllCascadeTrajectories 10-Item Hard Limit Bypass (Signal Drop)
- **증상**: 기존에 작성했던 { limit: 30 } 파라미터가 LS 백엔드에서 무시되어 최신 세션이 10개 제한에 걸려 잘려나감. (Discord로 메시지 단 한 글자도 안 넘어옴).
- **원인**: GetAllCascadeTrajectories는 구조적으로 pagination 옵션을 무시하거나 강제 10 제한이 걸림.
- **해결**: step-probe.ts에서 기본 GetAllCascadeTrajectories와 더불어 모든 트래젝토리를 덤프하는 GetDiagnostics API를 병행 호출하고 머지하여 최신 Session ID를 놓치지 않고 추출하게 함.
- **주의**: LS Backend에서 정의한 RPC의 한계상 Argument 조작으로 제한을 회피할 수 없으므로, 향후 GetDiagnostics 등 백도어 데이터를 활용할 것.
### [2026-04-10] [Probe Logging] <20><><EFBFBD> AI<41><EFBFBD><20><EFBFBD><EFBFBD>듃 & WAITING <20><EFBFBD><20><EFBFBD><20><EFBFBD>씫 踰꾧렇
- **利앹긽**: 援됱옣<EB90B1>엳 鍮좊Ⅸ AI <20><EFBFBD>떟(<28><EFBFBD>뒗 利됯컖<EB90AF><EFBFBD><20><20>샇異<EC8387>) <20>떆 `step-probe.ts`媛<> 硫붿떆吏<EB9686><EFA79E><EFBFBD><EFBFBD> <20><EFBFBD><20><EFBFBD><EFBFBD>뼹濡쒓렇瑜<EBA087> 紐⑤몢 Discord濡<64> 由대젅<EB8C80><EFBFBD>븯吏<EBB8AF> 紐삵븿.
- **<2A><EFBFBD>씤**: <20><EFBFBD>떆媛<EB9686> <20><EFBFBD><EFBFBD>듃 罹≪쿂(`delta > 0`) 議곌굔<EAB38C>뿉 `isRunning &&`<60>씠 嫄몃젮<EBAA83><EFBFBD>뼱, <20><EFBFBD>깭媛<EAB9AD> `WAITING`<60><EFBFBD>굹 `IDLE`濡<> 利됱떆 <20><EFBFBD>뼱媛<EBBCB1><EFBFBD> <20><EFBFBD><EFBFBD>듃瑜<EB9383> 罹≪쿂<E289AA><EFBFBD>뒗 猷⑦떞<E291A6><20>쟾遺<EC9FBE> <20><EFBFBD><EFBFBD>맖. <20><EFBFBD><20><20>닚媛<EB8B9A> `isStall` 議곌굔<EAB38C><20><><EFBFBD><EFBFBD> <20><EFBFBD>븘 `WAITING` <20><EFBFBD><EFBFBD><EFBFBD>룄 利앸컻<EC95B8>븿.
- **<2A>빐寃<EBB990>**: <20><EFBFBD>떆媛<EB9686> 罹≪쿂 濡쒖쭅<EC9296><EFBFBD>꽌 `isRunning &&` 議곌굔<EAB38C><20>젣嫄고븯怨<EBB8AF>, `delta > 0`<60><20>븣 異붽<E795B0><EBB6BD><EFBFBD>맂 理쒖떊 <20><EFBFBD><EFBFBD><20>뒪罹뷀븯硫댁꽌 `PLANNER_RESPONSE`<60><><EFBFBD> `WAITING` <20><EFBFBD><EFBFBD>쓣 紐⑤몢 泥섎━<EC848E><EFBFBD>룄濡<EBA384> <20><EFBFBD><EFBFBD>븿.
- **二쇱쓽**: LS Backend 10媛<30> Session <20><EFBFBD>븳 踰꾧렇媛<EBA087> <20><EFBFBD>뼱, <20>떎瑜<EB968E> 李쎌뿉<EC8E8C><20><EFBFBD>룞 梨꾪똿(`1fbca84c`)<29>씠 IDLE濡<45> <20><EFBFBD><EFBFBD><EFBFBD>쑝硫<EC919D> <20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD>뀡怨<EB80A1> <20>뿷媛덈┫ <20><20><EFBFBD><EFBFBD>굹, <20>씠 踰꾧렇<EABEA7>뒗 polling <20><><EFBFBD><EFBFBD>씠諛<EC94A0> 臾몄젣<EBAA84><ECA0A3><EFBFBD><EFBFBD>쓬.
### [2026-04-10] [Extension] AI Response Missing for New Sessions (Session Tracking Failure)
- **利앹긽**: <20>깉濡쒖슫 <20><><EFBFBD><EFBFBD>솕(Session) <20><EFBFBD><20>떆 泥<> AI <20><EFBFBD><20><EFBFBD><EFBFBD>듃媛<EB9383> <20><EFBFBD>뒪肄붾뱶<EBB6BE><20><EFBFBD><EC9FBE><EFBFBD> <20><EFBFBD><EFBFBD>릺吏<EBA6BA> <20><EFBFBD><20><EFBFBD>긽.
- **<2A><EFBFBD>씤**: 諛깆뿏<EAB986><EFBFBD>쓽 `GetAllCascadeTrajectories`媛<> 10媛<30> <20><EFBFBD>뀡留<EB80A1> 諛섑솚<EC8491><EFBFBD><20><20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD>맖. <20>씠瑜<EC94A0> 蹂댁셿<EB8C81>븯湲<EBB8AF> <20><EFBFBD>빐 `brain/` <20><EFBFBD><EFBFBD>넗由щ<E794B1><D189> <20>뒪罹뷀븯<EBB780>뒗 Fallback 濡쒖쭅<EC9296><20><EFBFBD><EFBFBD><EFBFBD><EFBFBD>굹, <20>떊洹<EB968A> <20><EFBFBD><EFBFBD>쓽 泥<> <20>떒怨꾩뿉<EABEA9>꽌 `GetCascadeTrajectorySteps`(stepOffset: 0) <20>샇異<EC8387> <20><20>궡遺<EAB6A1> <20><EFBFBD>떟(UTF-8 <20><EFBFBD><20>벑) <20><EFBFBD>윭濡<EC9CAD> <20><EFBFBD>빐 Exception<6F>씠 諛쒖깮, `trajectorySummaries`<60><20><EFBFBD><EFBFBD><20><EFBFBD><20>벑濡앸릺吏<EBA6BA> <20><EFBFBD>쓬. <20><EFBFBD><EFBFBD>씠 異붿쟻<EBB6BF>릺吏<EBA6BA> <20><EFBFBD><EFBFBD>땲 `delta > 0` 湲곕컲<EAB395><20><EFBFBD>떟 罹≪쿂媛<ECBF82> 諛쒖깮<EC9296>븯吏<EBB8AF> <20><EFBFBD>쓬.
- **<2A>빐寃<EBB990>**: `step-probe.ts`<60>쓽 Fallback 2 `catch` 釉붾줉<EBB6BE><EFBFBD><20><EFBFBD>윭媛<EC9CAD> 諛쒖깮<EC9296><EFBFBD><EFBFBD><EFBFBD>룄 媛뺤젣濡<ECA0A3> `stepCount: 1`濡<> <20><EFBFBD><EFBFBD><20>벑濡앺븯<EC95BA>룄濡<EBA384> <20>뙣移섑븯<EC8491><20><EFBFBD><20><EFBFBD><20><EFBFBD>떎 諛⑹<E8AB9B><E291B9>.
- **二쇱쓽**: API <20>샇異<EC8387> <20><EFBFBD>뙣瑜<EB99A3> 議곗슜<EAB397>엳 `catch`濡<> <20>꽆湲곕㈃ <20>쟾泥<EC9FBE> <20><EFBFBD><EFBFBD><EFBFBD><EFBFBD>씤(<28>뿬湲곗꽌<EAB397><20><EFBFBD><20>뤃留<EBA483>)<29><20><EFBFBD><20><EFBFBD><EFBFBD>꽣瑜<EABDA3> <20><EFBFBD><EFBFBD>엳 臾댁떆<EB8C81>븯寃<EBB8AF> <20><EFBFBD>뒗 移섎챸<EC848E>쟻 踰꾧렇媛<EBA087> 諛쒖깮<EC9296>븿. <20><EFBFBD><20><EFBFBD><20>꽕怨<EABD95> <20>떆 湲곕낯媛<EB82AF> 蹂듭썝(Fallback State) <20><EFBFBD><20><EFBFBD>닔.
### [2026-04-10] [Extension] Trigger-Click False Positives & Button Matching Failure
- **利앹긽**: <20><EFBFBD>뒪肄붾뱶<EBB6BE><EFBFBD><20><EFBFBD>씤(Approve)<29><20>늻瑜대㈃, <20><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD><20>봽濡쒓렇<EC9293><EFBFBD><20>븣留욎<EFA78D><EC9A8E> 踰꾪듉(<28>삁: `Always run`)<29><20>늻瑜댁<E7919C><EB8C81> 紐삵븯嫄곕굹, <20><EFBFBD><EFBFBD>븳 踰꾪듉(<28>삁: <20><EFBFBD><EFBFBD>쓽 `Running1 command`)<29><20><EFBFBD>윭踰꾨젮 <20><EFBFBD><20><EFBFBD>씤 泥섎━媛<E29481> <20><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD>긽.
- **<2A><EFBFBD>씤**: 1) UI 踰꾪듉 <20><EFBFBD><EFBFBD><EFBFBD>뿉 `keyboard_arrow_up` <20>벑 癒명떚由ъ뼹 <20><EFBFBD>씠肄<EC94A0> <20><EFBFBD><EFBFBD>듃媛<EB9383> <20>젒李<ECA092>(`Always runkeyboard_arrow_up`)<29><EFBFBD><20>젙洹쒖떇<EC9296><20><EFBFBD><EFBFBD>븷 寃껋쓣 <20><EFBFBD><EFBFBD><20><EFBFBD>뼱 寃쎄퀎(`\b`)瑜<> <20>젣嫄고븳 <20>뙣移섍<E7A7BB><EC848D> <20><EFBFBD>씤. <20><EFBFBD>뼱 寃쎄퀎媛<ED808E> <20><EFBFBD>씪吏<EC94AA>硫댁꽌 `/Run/i` <20><EFBFBD><EFBFBD>씠 `Running1 command` 媛숈<E5AA9B><EC8888> <20>떎瑜<EB968E> <20><EFBFBD><20><EFBFBD><EFBFBD>듃 踰꾪듉<EABEAA><20><EFBFBD>깘(False Positive)<29>맖. 2) DOM <20><EFBFBD><EFBFBD><20><EFBFBD><20><EFBFBD><EFBFBD>듃 踰꾪듉<EABEAA><20><EFBFBD><20><EFBFBD>쑝誘<EC919D><EFBFBD> <20><EFBFBD><EFBFBD>맂 踰꾪듉<EABEAA><20><EFBFBD><20>겢由<EAB2A2><E794B1>맖.
- **<2A>빐寃<EBB990>**: `trigger-click` 濡쒖쭅 <20><EFBFBD><20>쟾 踰꾪듉<EABEAA>쓽 `textContent`<60><EFBFBD>꽌 `keyboard_arrow_up` <20><20><EFBFBD>젮吏<ECA0AE> 瑗щ━ <20><EFBFBD>씠肄<EC94A0> 臾몄옄<EBAA84><EFBFBD>쓣 紐낆떆<EB8286><EFBFBD>쑝濡<EC919D> <20>젣嫄<ECA0A3>(strip)<29>븯怨<EBB8AF>, 紐⑤뱺 <20>듃由ш굅 <20>젙洹쒖떇<EC9296><20><EFBFBD><20><EFBFBD>뼱 寃쎄퀎(`\b`)瑜<> 媛뺤젣 <20><EFBFBD><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><20>썝泥<EC8D9D> 李⑤떒<E291A4>븿.
- **二쇱쓽**: UI <20><EFBFBD>냼瑜<EB83BC> DOM<4F><EFBFBD>꽌 湲곸뼱<EAB3B8><20><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><20>닲寃⑥쭊 <20><EFBFBD>씠肄<EC94A0>/<2F><EFBFBD><EFBFBD>듃 由ш굅爾<EAB585>(ligatures)媛<> <20><EFBFBD>뒗吏<EB9297><><E5AF83><EFBFBD><EFBFBD><20>븿. <20><EFBFBD>꽩 留ㅼ묶 <20>떆 瑗щ━<D189>몴瑜<EBAAB4> 癒쇱<E79992><EC87B1> <20>젣嫄고븯怨<EBB8AF> 紐낇솗<EB8287>븳 寃쎄퀎瑜<ED808E><><E981BA><EFBFBD>븷 寃<>.
### [2026-04-10] [Extension] Ghost Session Hijack & Infinite Polling Loop (trajectory not found)
- **利앹긽**: <20>떊洹<EB968A> <20><EFBFBD><20>떆 '<27><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EBBCB1><EFBFBD>' (Discord濡<64> 由대젅<EB8C80><20><20>맖). 濡쒓렇<EC9293>뿉 500 error trajectory not found 臾댄븳 諛섎났.\n- **<EFBFBD><EFBFBD>씤**: Antigravity媛<79> <20><EFBFBD><EFBFBD>븯硫댁꽌 brain/<2F>뿉 36湲<36><E6B9B2><20><EFBFBD>뜑瑜<EB9C91> <20><EFBFBD><EFBFBD><EFBFBD><EFBFBD>뜲, Cascade媛<65> <20><EFBFBD>땲誘<EB95B2><EFBFBD> GetCascadeTrajectorySteps<70><EFBFBD>꽌 500 <20><EFBFBD>윭瑜<EC9CAD> <20><EFBFBD><EFBFBD>떎. <20>븯吏<EBB8AF><EFBFBD> <20><EFBFBD><20>떊洹<EB968A> <20><EFBFBD><20><EFBFBD>떎 諛⑹<E8AB9B><E291B9> <20>뙣移섍<E7A7BB><EC848D> <20>씠 Ghost <20><EFBFBD><EFBFBD>쓣 RUNNING<4E>쑝濡<EC919D> 媛뺤젣 <20>벑濡앺븯硫댁꽌, <20><EFBFBD><20><EFBFBD>뀡(activeSessionId)<29><20>깉痍⑦븯怨<EBB8AF> 臾댄븳 <20><EFBFBD>윭 猷⑦봽<E291A6>뿉 鍮좎<E98DAE><ECA28E><EFBFBD> 留뚮뱾<EB9AAE><EFBFBD><EFBFBD><EFBFBD>떎.\n- **<EFBFBD>빐寃<EFBFBD>**: step-probe.ts<74><EFBFBD><20>뤃諛<EBA483> <20>벑濡<EBB291> <20>떆 error message<67>뿉 'trajectory not found'媛<> <20><EFBFBD>븿<EFBFBD>릺硫<EBA6BA> Ghost <20><EFBFBD><EFBFBD>쑝濡<EC919D> 媛꾩<EABEA9>빐 媛뺤젣 <20>벑濡<EBB291>(continue)<29>쓣 嫄대꼫<EB8C80>쎇寃<EC8E87> <20>븯怨<EBB8AF>, Stall Probe <20><EFBFBD>윭 catch<63><EFBFBD><EFBFBD>룄 UTF-8 <20><EFBFBD>윭媛<EC9CAD> <20><EFBFBD>땲硫<EB95B2> stallProbed=true瑜<65> 二쇱뼱 <20><EFBFBD><EFBFBD>룄 臾댄븳 猷⑦봽瑜<EBB4BD> <20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>떎.\n- **二쇱쓽**: uuid 湲몄씠(36<33>옄)留뚯쑝濡<EC919D> <20><EFBFBD><EFBFBD>넗由щ<E794B1><D189> <20>떇蹂꾪븷 <20>븣 Antigravity<74><79><EFBFBD> Google Agent媛<74> 紐⑦샇<E291A6>빐吏<EBB990> <20><20><EFBFBD>쑝誘<EC919D><EFBFBD>, 諛섎뱶<EC848E>떆 Backend <20><EFBFBD><EFBFBD><20><EFBFBD><EFBFBD><20><EFBFBD>윭(trajectory not found) 硫붿떆吏<EB9686><EFBFBD> <20><EFBFBD><20>뙋蹂꾩쓣 <20><EFBFBD><20><EFBFBD><EFBFBD>떎.\n

View File

@@ -0,0 +1,205 @@
# Observer Script 개발 가이드 — SSOT
> **이 문서는 Observer 코드 변경 전 반드시 확인하는 SSOT입니다.**
> 모든 Observer 관련 설계, 배포, 제약사항이 이 문서에 있습니다.
---
## 1. Observer 코드 특성
### 1.1 실행 환경
- Observer는 **workbench-jetski-agent.html**에 인라인 `<script>`로 삽입됨
- **AG Native의 Electron 렌더러 프로세스**에서 실행 (VS Code extension host가 아님)
- 렌더러는 **strict mode 아님** (확인 필요), 하지만 V8 parser는 일부 strict-like 규칙 적용
- `generateApprovalObserverScript(port)` 함수가 **TypeScript template literal**로 스크립트 생성
### 1.2 코드 작성 규칙 (위반 시 Observer 전체 크래시)
| 규칙 | 이유 | 예시 |
|------|------|------|
| **for 루프 안에 function 선언 금지** | V8 strict mode error | `var fn = function(){}` 사용 |
| **문자열 리터럴에 특수문자 금지** | template literal 이스케이핑 깨짐 | `'??'``'MAX'` |
| **regex에 `\\s` 등 이스케이프 금지** | template literal이 `\\\\s``\\s` (리터럴) | 문자열 비교 사용 |
| **ES6+ 구문 금지** | 구 V8 호환 | `var` 사용, `let/const/arrow` 금지 |
| **배포 전 SYNTAX CHECK 필수** | Observer 크래시 방지 | 아래 검증 명령어 참조 |
### 1.3 필수 검증 명령어 (모든 빌드 전 실행)
```powershell
npm.cmd run compile; node -e "const {generateApprovalObserverScript}=require('./out/observer-script'); let s=generateApprovalObserverScript(18080); try { new Function(s); console.log('SYNTAX OK'); } catch(e) { console.log('ERROR:', e.message); }"
```
**SYNTAX OK가 나오지 않으면 절대 배포하지 않는다.**
### 1.4 배포 전 자기검증 체크리스트 (MANDATORY)
**재시작을 요구하기 전 반드시 다음을 모두 통과해야 한다:**
1. [ ] **SYNTAX CHECK 통과**: `new Function(s)``SYNTAX OK`
2. [ ] **수정 방향 검증**: 이 수정이 문제를 해결하는 올바른 접근인지 스스로 2번 재검증
3. [ ] **template literal 규칙 위반 없음**: regex 이스케이프, 특수문자, function 선언 등
4. [ ] **변경 범위 최소화**: 불필요한 코드 포함 여부 확인
5. [ ] **재시작 사유 명시**: 사용자에게 (a) 무엇을 수정했고 (b) 왜 재시작이 필요한지 1~2줄로 설명
6. [ ] **재시작 횟수 명시**: Observer 변경 = 2회, Extension host만 변경 = 1회
7. [ ] **log() relay 필터 확인**: 새 로그 키워드 추가 시 log() 함수의 키워드 필터에도 추가했는지 확인 (섹션 3.5 참조)
8. [ ] **regex E2E 테스트**: Observer에서 사용하는 새 regex는 생성된 코드에서 직접 실행하여 매칭 검증
9. [ ] **구현 전 가정 검증**: 새 접근을 코딩하기 전에, 핵심 가정이 성립하는지 로그 1줄로 먼저 확인 (예: "Step Probe가 WAITING을 볼 수 있는가?" → `STEP-PROBE.*WAITING` 로그 검색)
**정당한 사유 없이 재시작을 요구하지 않는다.**
**DOM 구조를 먼저 파악하고 설계한 후 코드를 작성한다.**
**시행착오식(trial-and-error) 접근을 하지 않는다.**
**추측으로 코딩하지 않는다. 로그/데이터로 확인한 사실에 기반하여 코딩한다.**
---
## 2. 배포 프로세스
### 2.1 Observer 코드가 포함된 변경
Observer 코드 변경은 **extension host 코드 변경보다 비용이 높다**:
```
VSIX 빌드 → VSIX 설치 → AG 재시작 #1 (extension이 HTML 패치)
→ AG 재시작 #2 (패치된 HTML 로드) → Observer 실행
```
**총 2번 AG 재시작 필요**
### 2.2 Extension host 코드만 변경 (approval-handler, http-bridge 등)
```
VSIX 빌드 → VSIX 설치 → AG 재시작 #1 → 즉시 적용
```
**1번 AG 재시작 필요**
### 2.3 VSIX 설치 확인
```powershell
Get-ChildItem "$env:USERPROFILE\.vscode\extensions" -Filter "*gravity*" -Directory |
ForEach-Object { $j = Get-Content (Join-Path $_.FullName "package.json") | ConvertFrom-Json; "$($_.Name): v$($j.version)" }
```
### 2.4 HTML 패치 확인 (Observer 코드가 반영되었는지)
```powershell
Select-String -Path "$env:LOCALAPPDATA\Programs\Antigravity\resources\app\out\vs\code\electron-browser\workbench\workbench-jetski-agent.html" -Pattern "검색할_함수명" -Quiet
```
---
## 3. AG Native DOM 구조
### 3.1 Chat Panel (Observer가 접근 가능)
- 위치: `document.querySelector('#conversation')` 또는 `document.querySelector('[class*="conversation"]')`
- AI 응답 블록: `.leading-relaxed.select-text`
- 사용자 메시지 블록: `.select-text.rounded-lg` (v0.5.74+)
- Thinking 블록: 조상에 `max-h-[200px]` 클래스 있음 → 필터링
### 3.2 승인 버튼 — "Always run" (v0.5.93 BTN-DOM-DUMP 확인)
실제 DOM 구조 (v0.5.92 로그로 확인):
- d0: button.flex.cursor-pointer (Always run 버튼)
- d1: div.min-w-0
- d2: div.flex.items-center.justify-between.rounded-b.border-t (버튼 바)
- d3: div (이름 없는 컨테이너)
- div.mb-1 = "Running command" (헤더)
- div.flex = " gravity_control > ..." (실제 명령어, plain div!)
- div.flex = "Always run Cancel" (버튼들)
> 명령어는 pre.font-mono나 code가 아닌 plain div.flex에 있음.
> v30: "Running command" div의 형제를 탐색하여 프롬프트 마커 뒤의 명령어 추출.
- "Retry" 버튼: button 태그, chat panel 내
### 3.3 Diff Review (Accept all / Reject all) — Observer 접근 가능 (v0.5.101+)
- **v0.5.101 이전**: 에디터 webview에 렌더링, Observer document에서 접근 불가
- **v0.5.101 이후**: AG UI 업데이트로 chat panel footer에 `<span class="cursor-pointer">` 태그로 렌더링
- Observer의 `allBtns` 선택자에 `span.cursor-pointer` 포함 필수
- `matchedType = 'diff_review'`로 분류됨 (L1120: `txt.includes('Accept')`)
- auto-approve response 파일에 `_from_ws: true` 마커 필수 (processResponseFile race condition 방지)
### 3.4 DOM 렌더링 타이밍
- "Always run" 버튼이 DOM에 나타날 때 명령어 div도 함께 렌더링됨
- v30의 "Running command" div 탐색은 즉시 성공
### 3.5 log() relay 필터 규칙
Observer의 log() 함수는 키워드 필터로 일부 로그만 extension.log에 relay.
새 로그 키워드 추가 시 반드시 필터도 함께 수정해야 함.
현재 필터 키워드 (v0.5.92+):
CV-CLASSES, CV-CHILDREN, child[, CV found, Conversation view,
BEACON, ERROR, chat relay, user-cls,
CONTEXT, BTN-DOM, DEFERRED, DETECTED
---
## 4. 파일 경로 매핑
| 항목 | 경로 |
|------|------|
| AG 설치 경로 | `$env:LOCALAPPDATA\Programs\Antigravity\` |
| workbench HTML | `...\resources\app\out\vs\code\electron-browser\workbench\workbench-jetski-agent.html` |
| Extension 로그 | `$env:USERPROFILE\.gemini\antigravity\bridge\extension.log` |
| Pending 파일 | `$env:USERPROFILE\.gemini\antigravity\bridge\pending\*.json` |
| Response 파일 | `$env:USERPROFILE\.gemini\antigravity\bridge\response\*.json` |
| VSIX extensions | `$env:USERPROFILE\.vscode\extensions\variet.gravity-bridge-*\` |
| HTTP Bridge 포트 | 34332 (또는 `getDeterministicPort('gravity_control')`) |
| Discord 채널 | `#ag-gravity_control` (ID: 1483082084540223663) |
---
## 5. 과거 실수 및 교훈
### 5.1 VSIX 미설치 (v0.5.78~83)
- **증상**: 빌드만 하고 `code --install-extension` 실행 안 함
- **결과**: 설치된 버전이 v0.5.50, 모든 수정사항 미적용
- **교훈**: 빌드 후 반드시 `code --install-extension *.vsix --force` 실행
- **확인**: `Get-ChildItem "$env:USERPROFILE\.vscode\extensions" -Filter "*gravity*"`
### 5.2 function 선언 → Observer 크래시 (v0.5.84~86)
- **증상**: `function _isGenericDesc(d){}` 를 for 루프 내부에 선언
- **결과**: Observer 전체 크래시, chat relay + auto-approve 중단
- **교훈**: `var fn = function(){}` 사용, 배포 전 SYNTAX CHECK 필수
### 5.3 깨진 문자열 리터럴 (v0.5.86)
- **증상**: `{tag:'??',...}` (특수문자가 따옴표 깨뜨림)
- **결과**: SYNTAX ERROR, Observer 미작동
- **교훈**: template literal 안에서 특수문자/이모지 사용 주의
### 5.4 regex 이스케이핑 실패 (v0.5.83~84)
- **증상**: `/Always\\s+run/` → 생성 시 `\\s` (리터럴 백슬래시+s)로 출력
- **결과**: "Always run" 매칭 실패
- **교훈**: template literal 안에서 regex 대신 **문자열 비교** 사용
### 5.5 _from_ws 파일 무한 누적 (v0.5.78~84)
- **증상**: response 파일에 `_from_ws: true` 마커 → processResponseFile이 스킵 → 영원히 삭제 안 됨
- **결과**: 3초마다 4개 파일 × SKIP 로그 → 로그 스팸, 다른 처리 방해
- **교훈**: 보존 파일에는 반드시 **TTL(자동 만료)** 추가
---
## 6. 디버깅 체크리스트
### Observer가 작동하지 않을 때
1. `extension.log`에서 `setup complete` 확인 → Observer 로드 여부
2. `OBSERVER-LOG` 패턴 검색 → 스캔 활동 여부
3. `HTTP-REQ` 검색 → HTTP bridge에 요청 도달 여부
4. **SYNTAX CHECK** 실행 → 생성 스크립트 문법 검증
5. `SKIP _from_ws` 반복 확인 → stale response 파일 정리
### Discord에 메시지가 안 올 때
1. `POST /chat` 검색 → chat relay 전송 여부
2. `WS.*send` 검색 → WebSocket 전송 여부
3. Discord API로 직접 확인: `node extension/scratch/discord_read.js`
4. stale response 파일 확인: `Get-ChildItem $env:USERPROFILE\.gemini\antigravity\bridge\response\*.json`
---
## 7. 버전 히스토리 요약
| 버전 | 핵심 변경 | 결과 |
|------|----------|------|
| v0.5.50 | 기본 릴레이 시스템 | ✅ 안정 |
| v0.5.78 | `_from_ws` 마커 (Retry 보존) | ✅ 작동 (TTL 미구현) |
| v0.5.79 | sibling 탐색 + thinking 필터 | ✅ 작동 |
| v0.5.80~81 | Accept all offsetParent 완화 | ❌ 구조적 불가 (에디터 webview) |
| v0.5.82 | 버튼 셀렉터 확장 + ACCEPT-SCAN | 진단용 |
| v0.5.83 | DEFERRED 컨텍스트 500ms | regex 이스케이핑 실패 |
| v0.5.84 | regex → 문자열 비교 | function 선언 크래시 |
| v0.5.85 | `_from_ws` TTL 60초 | ✅ stale 정리 |
| v0.5.86 | function → var expression | 깨진 문자열 미발견 |
| v0.5.87 | 깨진 문자열 2건 수정 | ✅ SYNTAX OK |

View File

@@ -0,0 +1,216 @@
# AG Native 릴레이 아키텍처 분석
> **이 문서는 AG Native ↔ Discord 릴레이의 데이터 흐름 SSOT입니다.**
> 구현/디버깅 전 반드시 확인합니다.
---
## 1. 데이터 경로 요약
AG Native에서 Discord로 메시지를 전달하는 경로는 크게 2개:
| # | 경로 | 소스 | 실시간? | 상태 |
|---|------|------|---------|------|
| 1 | **Observer DOM** | workbench.html 인라인 스크립트 → DOM 관찰 → HTTP POST → http-bridge | ✅ 실시간 | AI 응답: ✅ 작동 (v0.5.72+), 사용자 메시지: ✅ 작동 (v0.5.74+) |
| 2 | **Step Probe (trajectory API)** | LS RPC `GetCascadeTrajectorySteps` → step 분석 | ❌ cascade 완료 후에만 | AI 응답: ❌ 실시간 불가, 사용자 메시지: ❌ 실시간 불가 |
### 1.1 핵심 API 제약 (2026-04-18 확인)
> [!CAUTION]
> **`GetCascadeTrajectorySteps`는 진행 중인 cascade의 step을 실시간으로 반환하지 않습니다.**
> step count는 cascade가 **완전히 종료**(IDLE 전환)된 후에만 업데이트됩니다.
> 따라서 Step Probe의 RT-CAPTURE, HB-CAPTURE 모두 **현재 진행 중인 대화에서는 작동하지 않습니다.**
**검증 데이터**:
- POLL에서 `status=CASCADE_RUN_STATUS_IDLE`, `steps=928`, `delta=0` 고정
- HEARTBEAT probe: `offset=927 got=1 real=928 known=928` → 변함 없음
- 실제로 수십 개의 tool call이 실행되었지만 step count 불변
- Cascade 종료 후 다음 poll에서 step count가 점프 (예: 733 → 865 → 928)
### 1.2 AG Native SDK EventMonitor
SDK에 이벤트 시스템이 있으나 **모두 polling 기반**:
- `EventMonitor.onStepCountChanged` — getDiagnostics 기반 polling
- `EventMonitor.onActiveSessionChanged` — state.vscdb 기반 polling
- **실시간 push (WebSocket/SSE)는 없음**
- 현재 상태: ERR_CONNECTION_REFUSED 문제로 비활성화됨
---
## 2. Observer DOM 경로 상세
### 2.1 Observer 스크립트 삽입 체인
```
extension.ts activate()
→ html-patcher.ts setupApprovalObserver()
→ observer-script.ts generateApprovalObserverScript(port)
→ workbench-jetski-agent.html에 인라인 <script> 삽입
→ AG 재시작 시 렌더러가 로드
```
### 2.2 Observer 주요 함수
| 함수 | 역할 |
|------|------|
| `scanChatBodies()` | 3초마다 실행, conversation view에서 메시지 블록 탐색 |
| `extractCleanStepText(el)` | DOM 클론 → style/script/button 제거 → textContent 추출 |
| `extractContextFromNearby(btn)` | 승인 버튼 주변 DOM에서 명령어 텍스트 추출 (v23: sibling 탐색 포함) |
| `pollResponseGroup(rid, btnRefs)` | response 파일 polling → 버튼 자동 클릭 |
### 2.3 AI 응답 감지 셀렉터
```javascript
var responseBlocks = cv.querySelectorAll(
'.leading-relaxed.select-text, ' // ← AI 응답 마크다운 블록 (주력)
+ '.text-ide-message-block-user-color, ' // ← 사용자 메시지 (미매칭)
+ '.text-ide-message-block-bot-color, ' // ← NUX tooltip 전용 (오매칭)
+ '.bg-ide-message-block-user-background, '// ← 사용자 메시지 (미매칭)
+ '[data-message-role="user"], ' // ← 사용자 메시지 (미매칭)
+ '[data-role="user"]' // ← 사용자 메시지 (미매칭)
);
```
> [!NOTE]
> **v0.5.74에서 사용자 메시지 셀렉터가 추가되었습니다.**
> AG Native 소스(`jetskiAgent/main.js`)의 `Esn` 컴포넌트 분석으로
> 사용자 메시지 CSS 클래스(`msn = "bg-gray-500/10 border border-gray-500/20 p-2 rounded-lg w-full text-sm select-text"`)를 식별.
> 셀렉터: `.select-text.rounded-lg`, 역할 판별: `rounded-lg` 있고 `leading-relaxed` 없으면 → user
### 2.4 AI 응답 추출 흐름
```
scanChatBodies() 3초 간격
→ cv = document.querySelector('#conversation')
→ responseBlocks = cv.querySelectorAll('.leading-relaxed.select-text, ...')
→ lastBlock = responseBlocks[last] (가장 최근 블록)
→ 이미 scrape 됐으면 skip
→ blockText = extractCleanStepText(lastBlock)
→ 안정화 대기 (3초 동안 텍스트 변경 없으면)
→ POST /chat { text, source, block_index, role }
→ http-bridge → writeChatSnapshot() → WS → Discord
```
### 2.5 Observer 업데이트 제약
> [!CAUTION]
> **Observer 코드는 workbench.html에 인라인 삽입됩니다.**
> extension reload만으로는 Observer 코드가 업데이트되지 않습니다.
> **AG 재시작 + V8 CachedData 삭제**가 필요합니다.
> (단, product.json 체크섬이 맞으면 CachedData 삭제 없이 AG 재시작만으로 충분할 수 있음)
---
## 3. 승인 버튼 (Auto-Approve) 경로
### 3.1 "Always run" 자동 승인 흐름
```
Observer DOM scan
→ "Always run" 버튼 텍스트 감지
→ POST /pending { command: "Always run", description: "...", buttons: [...] }
→ http-bridge _handlePending()
→ alwaysRunDetected = true
→ enrichment 시도:
1. rawDesc에서 > 프롬프트 마커 찾기 → ✅ 성공 (buttons=2일 때 desc에 프롬프트 포함)
2. rawDesc 최장 라인 사용 → buttons=1일 때 desc="Always run"이라 실패
3. v20 fallback: bridge/pending/ 최신 파일에서 command 읽기 → Step Probe pending 있을 때만
4. v23 sibling: Observer가 footer 형제 요소에서 pre.font-mono 탐색 → ✅ 성공
→ response 파일 작성 → Observer pollResponseGroup → 버튼 클릭
→ WS sendPending { status: 'auto_approved', command: displayCmd }
→ Discord embed 표시
```
### 3.2 명령어 enrichment 현황 (2026-04-18 검증)
| 조건 | 결과 | 빈도 |
|------|------|------|
| Observer가 buttons=2 (`["Always run","Cancel"]`)이고 desc에 `>` 포함 | ✅ 명령어 표시 | ~50% |
| Observer가 buttons=1 (`["Always run"]`)이고 desc="Always run" | ❌ "Always run" 표시 | ~50% |
**로그 증거** (04:25:59):
```
AUTO-APPROVE raw: cmd="Always run" desc="…\extension > npm.cmd run compile..." buttons=["Always run","Cancel"]
→ cmd="npm.cmd run compile 2>&1; npm.cmd version patch..." ✅ 성공
AUTO-APPROVE raw: cmd="Always run" desc="Always run" buttons=["Always run"]
→ cmd="Always run" ❌ 실패
```
> buttons=2인 경우("Always run" + "Cancel")는 Observer가 code 블록을 찾아 description에 포함.
> buttons=1인 경우는 code 블록이 DOM에서 아직 렌더링되지 않았거나 접근 불가.
---
## 4. 사용자 메시지 릴레이 상태
### 4.1 현재 상태: ✅ 작동 (v0.5.74+)
| 경로 | 상태 | 비고 |
|------|------|------|
| Observer DOM | ✅ | `.select-text.rounded-lg` 셀렉터로 캡처 (v0.5.74) |
| Step Probe (trajectory API) | ❌ | cascade 진행 중 step 조회 불가 |
| Step Probe (observer [USER-MSG]) | ❌ | `lastUserInputStepIndex`가 갱신되지 않음 |
### 4.2 해결 방안
1. **DOM 덤프에서 사용자 메시지 클래스 식별** → Observer 셀렉터 추가
2. **Cascade 완료 후** Step Probe HB-CAPTURE에서 `USER_INPUT` step 캡처 (지연 릴레이)
---
## 5. 파일/포트 매핑
| 항목 | 값 |
|------|-----|
| Observer 삽입 대상 | `workbench-jetski-agent.html` |
| HTTP Bridge 포트 | `getDeterministicPort('gravity_control')` = **18080** |
| Extension 로그 | `~/.gemini/antigravity/bridge/extension.log` |
| Pending 파일 | `~/.gemini/antigravity/bridge/pending/*.json` |
| Response 파일 | `~/.gemini/antigravity/bridge/response/*.json` |
| Chat Snapshot 파일 | `~/.gemini/antigravity/bridge/chat_snapshots/*.json` |
| Discord 채널 | `#ag-gravity_control` (ID: 1483082084540223663) |
| Discord Bot 토큰 | `.env``DISCORD_TOKEN` |
---
## 6. 디버깅 도구
| 도구 | 경로 | 용도 |
|------|------|------|
| Discord 메시지 읽기 | `extension/scratch/discord_read.js` | API로 채널 최근 메시지 조회 |
| Discord 채널 목록 | `extension/scratch/discord_channels.js` | 서버 채널 목록 조회 |
| Extension 로그 확인 | `Select-String -Path $logFile -Pattern "패턴"` | 실시간 로그 분석 |
| DOM 구조 덤프 | Observer 자동 (CV-CHILDREN 로그) | AG Native DOM 클래스 식별 |
---
## 7. 버전 히스토리 (v0.5.67~)
| 버전 | 변경 | 결과 |
|------|------|------|
| v0.5.67 | Observer DOM relay 비활성화, Step Probe RT-CAPTURE로 전환 | ❌ API가 진행중 step 미반환 |
| v0.5.68 | auto-approve enrichment 디버그 로그 추가, 조건 >10 → >3 완화 | Observer가 desc="Always run" 보냄 확인 |
| v0.5.69 | pending 파일 fallback으로 auto-approve 명령어 enrichment | 일부 개선 (Step Probe pending 있을 때만) |
| v0.5.70 | heartbeat 로깅 강화 | API step count 동결 확인 |
| v0.5.71 | heartbeat 3 poll마다 실행, HB-CAPTURE 추가 | API가 진행중 step 미반환 재확인 |
| v0.5.72 | Observer DOM relay 재활성화 | AG 재시작 필요 (Observer HTML 캐시) |
| v0.5.74 | 사용자 메시지 셀렉터 추가 (`.select-text.rounded-lg`) | ✅ 사용자 메시지 릴레이 작동 |
| v0.5.76 | DOM 탐색 depth 5→10, `pre.font-mono` 우선 탐색 | Observer HTML 업데이트 필요 |
| v0.5.77 | WS response 파일 작성 (pollResponseGroup용) | Retry 클릭 경로 추가 |
| v0.5.78 | `_from_ws` 마커로 processResponseFile 삭제 방지 | ✅ Retry auto-approve 작동 |
| v0.5.79 | sibling 탐색 추가 + thinking 블록 필터링 | ✅ 명령어 컨텍스트 부분 추출 |
---
## 8. 남은 작업 (TODO)
- [x] AG 재시작하여 Observer 반영 확인 — ✅ v0.5.72 작동 확인
- [x] Observer의 AI 응답 릴레이가 작동하는지 Discord에서 확인 — ✅ 작동
- [x] 사용자 메시지 셀렉터 추가 — ✅ v0.5.74
- [x] Retry auto-approve 흐름 복구 — ✅ v0.5.78 (_from_ws 마커)
- [x] 명령어 컨텍스트 sibling 탐색 — ✅ v0.5.79
- [x] Thinking 블록 필터링 — ✅ v0.5.79
- [ ] 명령어 컨텍스트 추출 타이밍 이슈 (DOM 렌더링 전 scan 시 추출 실패) #636
- [ ] Observer pollResponseGroup 미시작 케이스 (trigger-click 선점)
- [ ] AI 응답이 마지막 블록만 캡처되는 문제 개선 (전문 캡처)

View File

@@ -0,0 +1,99 @@
# Tech Stack
> AI 에이전트는 구현 전 이 문서를 확인하여 올바른 기술/버전을 사용합니다.
## 언어 & 런타임
| 항목 | 버전 | 경로/비고 |
|------|------|-----------|
| Python | 3.12 (miniforge3) | `C:\ProgramData\miniforge3\envs\gravity_control\python.exe` |
| Node.js | 시스템 설치 | `node`, `npm` (PowerShell에서 `cmd /c npm` 권장) |
| TypeScript | 5.3+ | `extension/src/*.ts``tsc``extension/out/*.js` |
> [!IMPORTANT]
> Python은 **반드시** 위 miniforge3 경로를 사용. WindowsApps의 python stub은 동작하지 않음.
## 프레임워크 & 라이브러리
### Python (서버)
| 패키지 | 버전 | 용도 |
|--------|------|------|
| discord.py | 2.x | Discord 봇 (슬래시 명령, 버튼 UI, 이벤트) |
| aiohttp | 3.x | Gateway HTTP 서버 + WebSocket endpoint |
| watchdog | - | Brain 디렉토리 파일시스템 감시 |
| python-dotenv | - | .env 파일 로드 |
| PyJWT | - | ❌ 미사용 (자체 HMAC-SHA256 구현) |
### TypeScript (Extension)
| 패키지 | 용도 |
|--------|------|
| @types/vscode | VS Code Extension API 타입 |
| @types/node | Node.js 타입 |
| typescript | 컴파일러 |
| ws | WebSocket Hub 연결 (`.vscodeignore``!node_modules/ws/**` 필수) |
| antigravity-sdk | AG RPC 호출 (로컬 임베드 `sdk/`) |
## 패키지 관리
| 측 | 도구 | 파일 |
|----|------|------|
| Python | pip | `requirements.txt` |
| Extension | npm | `extension/package.json` |
## 개발 도구 & 명령어
| 작업 | 명령어 |
|------|--------|
| **봇 실행** | `C:\ProgramData\miniforge3\envs\gravity_control\python.exe main.py` |
| **봇 실행 (gateway)** | `.env`에서 `BOT_MODE=gateway` 설정 후 위 명령 |
| **Extension 구문 검사** | `cd extension && npx tsc --noEmit` |
| **Extension 컴파일** | `cd extension && cmd /c npm run compile` |
| **Extension VSIX** | `cd extension && npx @vscode/vsce package --no-dependencies` |
| **Python 구문 검사** | `python -c "import ast; [ast.parse(open(f).read()) for f in ['bot.py','hub.py',...]]"` |
| **Hub WS 테스트** | `python tests/test_ws_hub.py` (서버 기동 상태에서) |
## 환경 변수 (.env)
### 필수
| 변수명 | 용도 | 기본값 |
|--------|------|--------|
| DISCORD_TOKEN | Discord 봇 토큰 | (필수) |
| DISCORD_GUILD_ID | Discord 서버 ID | (필수) |
### 선택
| 변수명 | 용도 | 기본값 |
|--------|------|--------|
| BRAIN_PATH | AG 브레인 경로 | `~/.gemini/antigravity/brain` |
| BOT_MODE | `local` / `gateway` | `local` |
| DEBOUNCE_SECONDS | Watcher 디바운스 간격 | `5` |
| PROJECT_NAME | 프로젝트 이름 | `gravity_control` |
### Gateway 모드 전용
| 변수명 | 용도 | 기본값 |
|--------|------|--------|
| GATEWAY_PORT | Gateway HTTP/WS 포트 | `8585` |
| GATEWAY_API_KEY | REST API 인증 키 | (미설정 시 인증 미사용) |
| GRAVITY_HUB_SECRET | WS Hub JWT 서명 시크릿 (64char hex) | (미설정 시 인증 생략) |
| GRAVITY_REGISTRATION_CODE | Extension 등록 코드 (32char hex) | (미설정 시 인증 생략) |
## Extension VS Code 설정
| 설정 키 | 용도 |
|---------|------|
| `gravityBridge.bridgePath` | Bridge 디렉토리 경로 |
| `gravityBridge.projectName` | 프로젝트 이름 (기본: git remote) |
| `gravityBridge.hubUrl` | Hub WS URL (예: `ws://localhost:8585/ws`) |
| `gravityBridge.registrationCode` | Hub 등록 코드 |
## 빌드 산출물
| 항목 | 경로 | 설명 |
|------|------|------|
| VSIX | `extension/gravity-bridge-{ver}.vsix` | VS Code 확장 패키지 |
| JS 출력 | `extension/out/*.js` | TypeScript 컴파일 결과물 |
| SDK 복사 | `extension/out/sdk/` | compile 시 자동 복사 |

View File

@@ -0,0 +1,40 @@
---
description: Gitea API로 저장소 커밋/이슈/PR 현황을 조회하는 워크플로우
---
# Gitea 저장소 현황 조회
서비스 정보는 `.agents/workflows/services.md` 참조.
// turbo-all
## 절차
1. 최근 커밋 조회 (최신 10개):
```powershell
$h = @{Authorization="token 3a01b4b15a39921572e64c413353e870d4d2161b"}
$commits = Invoke-RestMethod -Uri "https://git.variet.net/api/v1/repos/Variet/gravity_control/commits?limit=10&sha=main" -Headers $h
$commits | ForEach-Object { Write-Host "$($_.sha.Substring(0,7)) $($_.commit.message.Split("`n")[0])" }
```
2. 열린 이슈 조회:
```powershell
$h = @{Authorization="token 3a01b4b15a39921572e64c413353e870d4d2161b"}
$issues = Invoke-RestMethod -Uri "https://git.variet.net/api/v1/repos/Variet/gravity_control/issues?state=open&type=issues" -Headers $h
$issues | ForEach-Object { Write-Host "#$($_.number) $($_.title)" }
```
3. Wiki 페이지 목록:
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\wiki_helper.py list
```
4. Wiki 페이지 읽기:
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\wiki_helper.py read "Architecture"
```
5. Wiki 페이지 업데이트:
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\wiki_helper.py update "페이지-제목" /tmp/wiki_content.md
```

View File

@@ -0,0 +1,41 @@
---
description: Vikunja API로 프로젝트 태스크 현황을 조회하는 워크플로우
---
# Vikunja 태스크 현황 조회
서비스 정보는 `.agents/workflows/services.md` 참조.
// turbo-all
## 절차
1. 전체 목록:
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py list
```
2. TODO만:
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py list todo
```
3. DONE만:
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py list done
```
4. 태스크 완료 처리 (**⚠️ 반드시 이 방법 사용 — 직접 API 호출 금지**):
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py done {TASK_ID}
```
5. 새 태스크 생성:
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py create "제목" "설명" --labels Backend,Priority:High
```
> [!CAUTION]
> **절대로** `Invoke-RestMethod -Method Post -Body '{"done": true}'` 같은 직접 API 호출을 사용하지 마세요.
> Vikunja API는 POST 시 body에 포함되지 않은 필드를 빈값으로 덮어씁니다.
> `vikunja_helper.py`는 항상 GET → 기존 필드 보존 → POST 패턴을 사용합니다.

View File

@@ -0,0 +1,52 @@
---
description: 에러/버그 발생 시 체계적 디버깅 워크플로우 (에러, 안돼요, 왜 안돼, 버그, 디버그, 수정)
---
# Debug Workflow
> [!IMPORTANT]
> 추측으로 코드를 수정하지 마세요. 반드시 이 순서를 따릅니다.
## 1단계: 정보 수집 (추측 금지)
- [ ] 에러 메시지 **전문** 확인 (절대 잘라내지 않기)
- [ ] 관련 로그 파일 확인
- [ ] 환경 정보 확인 (OS, Node/Python 버전, 의존성 버전 등)
- [ ] 에러가 발생하는 **정확한 입력/조건** 파악
## 2단계: Known Issues 확인
`.agents/references/known-issues.md`를 읽고 동일하거나 유사한 문제가 있는지 확인합니다.
> [!CAUTION]
> **known-issues 확인 없이 해결 시도를 시작하지 마세요.**
> 이미 해결된 문제를 다시 삽질하는 것은 시간 낭비입니다.
## 3단계: 근본 원인 분석
- [ ] 에러가 발생하는 **정확한 코드 위치** 확인
- [ ] 가설을 세우고, 가설을 검증할 수 있는 **최소한의 테스트** 수행
- [ ] 가설이 틀렸다면 **즉시 다른 가설로 전환**
> [!WARNING]
> **동일한 접근을 2회 초과 시도하지 마세요.**
> 2회 실패 시 유저에게 보고하고 판단을 요청합니다.
> 보고 내용: 시도한 것 / 실패한 것 / 원인 가설 / 다음 제안
## 4단계: 수정 및 검증
- [ ] 수정 적용
- [ ] 동일 에러가 재현되지 않는지 확인
- [ ] 사이드 이펙트(다른 기능에 영향) 없는지 확인
## 5단계: 기록
- [ ] `known-issues.md`에 새 항목 추가 (아래 포맷 사용)
```markdown
### [날짜] [키워드] — 한줄 요약
- **증상**: 무엇이 잘못되었는가
- **원인**: 근본 원인
- **해결**: 올바른 해결 방법
- **주의**: 재발 방지를 위한 교훈
```

165
.agents/workflows/end.md Normal file
View File

@@ -0,0 +1,165 @@
---
description: 세션 종료 시 devlog 기록 + git commit + Vikunja 동기화 (끝, 마무리, 커밋해, 완료)
---
# 세션 종료 프로토콜
작업 완료, "끝", "마무리", "커밋해" 등 요청 시 이 워크플로우를 실행합니다.
// turbo-all
## 0. 학습 기록 (실패/시행착오 저장)
이번 세션에서 발생한 실패, 시행착오, 새로 알게 된 사실을 정리합니다:
- [ ] `.agents/references/known-issues.md`에 추가할 항목이 있는지 확인
- [ ] 있다면 아래 포맷으로 추가:
```markdown
### [날짜] [키워드] — 한줄 요약
- **증상**: ...
- **원인**: ...
- **해결**: ...
- **주의**: ...
```
## 1. Devlog 기록
### Index 업데이트 (필수 — 매 작업)
오늘 날짜의 index 파일에 완료된 작업 1줄을 추가합니다.
- **파일**: `docs/devlog/YYYY-MM-DD.md`
- **형식**:
```markdown
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
```
> [!TIP]
> - ✅ = 완료, 🔧 = 미완료 (다음 세션에서 이어받기)
> - 파일이 없으면 새로 생성 (테이블 헤더 포함)
### Entry 작성 (선택적 — 필요할 때만)
> [!IMPORTANT]
> Entry는 **git/Vikunja/wiki에 없는 정보**가 있을 때만 작성합니다.
**Entry 작성 기준:**
- ✅ 설계 결정이 있었을 때 (왜 A가 아닌 B를 선택했는지)
- ✅ 미완료 사항이 있을 때 (다음 세션이 이어받아야 할 맥락)
- ✅ 삽질/트러블슈팅이 있었을 때 (같은 실수 방지)
**Entry 불필요:**
- ❌ 단순 버그 픽스 (커밋 메시지로 충분)
- ❌ 문서 업데이트 (git diff로 충분)
- ❌ 이미 Vikunja 태스크에 상세 설명이 있는 경우
**Entry 파일**: `docs/devlog/entries/YYYYMMDD-NNN.md`
```markdown
# 작업 제목
- **시간**: YYYY-MM-DD HH:MM~HH:MM
- **Commit**: `해시`
- **Vikunja**: #태스크번호 → done/진행중
## 결정 사항
- 왜 이 방식을 선택했는지
## 미완료
- 남은 작업 (있을 경우)
```
---
## 2. Vikunja 동기화
> [!CAUTION]
> **반드시 `vikunja_helper.py` 사용.** 직접 API 호출 금지.
> Vikunja API는 POST 시 body에 없는 필드를 빈값으로 덮어씁니다.
### 2-1. 커밋 전수 검사
이번 세션의 **모든 커밋을 하나씩 검사**하고 Vikunja에 매핑합니다.
```powershell
git log --oneline -20
```
| 커밋 유형 | Vikunja 액션 |
|-----------|-------------|
| 기존 태스크 해당 작업 **완료** | `C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py done {ID}` |
| 신규 작업 완료 (기존 태스크 없음) | `C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py create "제목" "설명" --done --labels Backend,Priority:High` |
| 작업 중 발견된 **미완료 TODO** | `C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py create "제목" "설명" --labels Backend,Priority:Mid` |
> [!IMPORTANT]
> 모든 커밋이 기존 또는 신규 태스크에 매핑되었는지 확인.
### 2-2. 완료 처리
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py done {TASK_ID}
```
### 2-3. 신규 태스크 생성
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py create "제목" "설명" --labels Backend,Priority:High
```
### 라벨 규칙
**영역 (필수 1개 이상):** `Backend` / `Frontend` / `Engine` / `Infra` / `Test`
**우선순위 (필수 1개):** `Priority:High` / `Priority:Mid` / `Priority:Low`
---
## 3. Wiki 동기화 (해당 시에만)
| 코드 변경 | 대상 Wiki |
|-----------|----------|
| 서버 변경 | Architecture |
| 프론트엔드 변경 | Architecture |
| 인프라 변경 | Architecture |
| 새 모듈/패키지 추가 | Architecture |
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\wiki_helper.py update "Architecture" /tmp/wiki_content.md
```
---
## 4. Git Commit & Push
```powershell
git add -A
git status --short
```
```powershell
git commit -m "커밋 메시지"
```
```powershell
git push origin main
```
**커밋 메시지 컨벤션:**
```
<type>(<scope>): <description>
type: feat|fix|refactor|test|docs|chore|ci|infra
scope: (선택)
```
---
## 5. 최종 체크리스트
> [!WARNING]
> 아래 항목 중 하나라도 누락되면 세션 종료를 완료할 수 없습니다.
- [ ] known-issues 업데이트됨 (새 이슈가 있었다면)
- [ ] devlog index 업데이트됨
- [ ] devlog entry 작성됨 (필요한 경우만)
- [ ] Vikunja 태스크 생성/완료 처리됨 (커밋 전수 검사 기반)
- [ ] Wiki 동기화됨 (아키텍처 변경이 있었다면)
- [ ] git push 완료
- [ ] 사용자에게 완료 보고

View File

@@ -0,0 +1,160 @@
"""Analyze AG Native DOM structure to find AI response containers."""
import json, os, sys
def load_dump():
bridge = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge')
# Try deep-inspect result first, then dump_html
for fname in ['deep-inspect-result.json', 'dump_html.json']:
fpath = os.path.join(bridge, fname)
if os.path.exists(fpath):
print(f"Loading: {fname} ({os.path.getsize(fpath)} bytes)")
with open(fpath, 'r', encoding='utf-8-sig') as f:
return json.load(f), fname
return None, None
def find_text_containers(node, path="", depth=0, results=None):
"""Recursively find nodes with substantial text content (potential AI response containers)."""
if results is None:
results = []
if not isinstance(node, dict):
return results
tag = node.get('tag', '')
cls = node.get('cls', '')
text = node.get('text', '')
attrs = node.get('attrs', {})
children = node.get('children', [])
cur_path = f"{path}/{tag}"
if cls:
short_cls = cls[:60]
cur_path += f".{short_cls}"
# Look for nodes with long text (potential AI responses)
if text and len(text) > 50:
results.append({
'path': cur_path,
'depth': depth,
'tag': tag,
'cls': cls[:100],
'text_len': len(text),
'text_preview': text[:120],
'attrs': {k:v for k,v in attrs.items() if k not in ('style',)}
})
for child in children:
find_text_containers(child, cur_path, depth+1, results)
return results
def find_by_class_pattern(node, patterns, path="", depth=0, results=None):
"""Find nodes matching class patterns."""
if results is None:
results = []
if not isinstance(node, dict):
return results
tag = node.get('tag', '')
cls = node.get('cls', '')
attrs = node.get('attrs', {})
children = node.get('children', [])
text = node.get('text', '')
cur_path = f"{path}/{tag}"
for pattern in patterns:
if pattern.lower() in cls.lower() or pattern.lower() in str(attrs).lower():
child_count = len(children)
results.append({
'path': cur_path,
'depth': depth,
'tag': tag,
'cls': cls[:150],
'pattern': pattern,
'text_preview': text[:80] if text else '',
'child_count': child_count,
'attrs': {k:v[:50] for k,v in attrs.items() if k != 'style'}
})
for child in children:
find_by_class_pattern(child, patterns, cur_path, depth+1, results)
return results
def analyze_chat_structure(node, path="", depth=0):
"""Find the chat/conversation area by looking at the main layout."""
if not isinstance(node, dict):
return
tag = node.get('tag', '')
cls = node.get('cls', '')
children = node.get('children', [])
text = node.get('text', '')
attrs = node.get('attrs', {})
# Print interesting structural nodes at shallow depths
if depth <= 6:
child_count = len(children)
has_text = bool(text and len(text) > 10)
info = f"{' '*depth}{tag}"
if cls:
info += f" .{cls[:80]}"
if attrs:
attr_str = ' '.join(f'{k}={v[:30]}' for k,v in attrs.items() if k not in ('style','class'))
if attr_str:
info += f" [{attr_str}]"
info += f" children={child_count}"
if has_text:
info += f" text=\"{text[:50]}...\""
print(info)
for child in children:
analyze_chat_structure(child, f"{path}/{tag}", depth+1)
data, fname = load_dump()
if not data:
print("No dump file found!")
sys.exit(1)
# Handle both dump formats
body = data.get('body', data)
qi = data.get('quickInfo', {})
print("=" * 60)
print("QUICK INFO")
print("=" * 60)
if qi:
for k, v in qi.items():
if k == 'buttons':
print(f"buttons ({len(v)}):")
for b in v[:15]:
print(f" [{b.get('tag')}] \"{b.get('text','')[:50]}\" visible={b.get('visible')} cls={b.get('cls','')[:60]}")
elif k == 'dataAttrs':
print(f"dataAttrs: {v[:30]}")
else:
print(f"{k}: {v}")
print("\n" + "=" * 60)
print("CHAT-RELATED CLASS PATTERNS")
print("=" * 60)
patterns = ['chat', 'message', 'conversation', 'response', 'answer', 'reply',
'markdown', 'prose', 'content', 'panel', 'agent', 'assistant',
'planner', 'step', 'trajectory', 'bot', 'ai-', 'turn']
matches = find_by_class_pattern(body, patterns)
for m in matches:
print(f" [{m['tag']}] cls=\"{m['cls']}\" pattern={m['pattern']} children={m['child_count']} {m.get('attrs',{})}")
print("\n" + "=" * 60)
print("LONG TEXT NODES (potential AI responses)")
print("=" * 60)
texts = find_text_containers(body)
texts.sort(key=lambda x: x['text_len'], reverse=True)
for t in texts[:20]:
print(f" [{t['tag']}] depth={t['depth']} len={t['text_len']} cls=\"{t['cls'][:60]}\"")
print(f" text: \"{t['text_preview']}\"")
if t['attrs']:
print(f" attrs: {t['attrs']}")
print("\n" + "=" * 60)
print("DOM TREE (depth<=6)")
print("=" * 60)
analyze_chat_structure(body)

View File

@@ -0,0 +1,19 @@
import json, os, sys
dump_path = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge', 'dump_html.json')
with open(dump_path, 'r', encoding='utf-8') as f:
data = json.load(f)
qi = data.get('quickInfo', {})
print('=== Quick Info ===')
print('hasConversationView:', qi.get('hasConversationView'))
print('hasStepIndex:', qi.get('hasStepIndex'))
print('hasBotColor:', qi.get('hasBotColor'))
print('hasMarkdownBody:', qi.get('hasMarkdownBody'))
print('hasProse:', qi.get('hasProse'))
print('totalElements:', qi.get('totalElements'))
print('dataTestIds:', qi.get('dataTestIds'))
print('dataAttrs (first 20):', qi.get('dataAttrs', [])[:20])
print('buttons (first 10):')
for b in qi.get('buttons', [])[:10]:
print(f" [{b.get('tag')}] {b.get('text', '')[:60]} visible={b.get('visible')}")

View File

@@ -0,0 +1,83 @@
"""Search AG Native DOM dump for chat content and buttons."""
import json, os
fpath = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge', 'dump_html_5.json')
with open(fpath, 'r', encoding='utf-8-sig') as f:
data = json.load(f)
body = data.get('body', data.get('bodyTree', {}))
qi = data.get('quickInfo', {})
# Show all buttons
print('=== BUTTONS ===')
for b in qi.get('buttons', []):
print(f' [{b["tag"]}] "{b["text"][:60]}" visible={b["visible"]} cls={b.get("cls","")[:80]}')
# Data attrs
print('\n=== DATA ATTRS ===')
for attr in qi.get('dataAttrs', []):
print(f' {attr}')
# Recursive search for nodes by text
def find_nodes_by_text(node, target, path='', results=None, depth=0):
if results is None: results = []
if not isinstance(node, dict): return results
tag = node.get('tag','')
cls = node.get('cls','')
text = node.get('text','')
children = node.get('children', [])
cur = f'{path}/{tag}'
if target.lower() in text.lower():
results.append({'path': cur, 'depth': depth, 'cls': cls[:80], 'text': text[:80], 'children': len(children)})
for c in children:
find_nodes_by_text(c, target, cur, results, depth+1)
return results
print('\n=== NODES containing "Always run" ===')
matches = find_nodes_by_text(body, 'Always run')
for m in matches:
print(f' depth={m["depth"]} cls="{m["cls"]}" text="{m["text"]}" children={m["children"]}')
print('\n=== NODES containing "Always" ===')
matches = find_nodes_by_text(body, 'Always')
for m in matches:
print(f' depth={m["depth"]} cls="{m["cls"]}" text="{m["text"]}" children={m["children"]}')
# Find ALL text nodes with > 30 chars
def find_all_text(node, results=None, depth=0, path=''):
if results is None: results = []
if not isinstance(node, dict): return results
tag = node.get('tag','')
cls = node.get('cls','')
text = node.get('text','')
children = node.get('children', [])
if text and len(text) > 30:
results.append({'depth': depth, 'tag': tag, 'cls': cls[:80], 'text': text[:100], 'path': f'{path}/{tag}'})
for c in children:
find_all_text(c, results, depth+1, f'{path}/{tag}')
return results
print('\n=== LONG TEXT NODES (>30 chars) ===')
texts = find_all_text(body)
texts.sort(key=lambda x: len(x['text']), reverse=True)
for t in texts[:25]:
print(f' d={t["depth"]} [{t["tag"]}] cls="{t["cls"][:50]}" len={len(t["text"])} "{t["text"][:80]}"')
# Find nodes with many children (structural containers)
def find_containers(node, results=None, depth=0, path=''):
if results is None: results = []
if not isinstance(node, dict): return results
tag = node.get('tag','')
cls = node.get('cls','')
children = node.get('children', [])
if len(children) > 5:
results.append({'depth': depth, 'tag': tag, 'cls': cls[:100], 'children': len(children), 'path': f'{path}/{tag}'})
for c in children:
find_containers(c, results, depth+1, f'{path}/{tag}')
return results
print('\n=== CONTAINERS (>5 children) ===')
conts = find_containers(body)
conts.sort(key=lambda x: x['children'], reverse=True)
for c in conts[:20]:
print(f' d={c["depth"]} [{c["tag"]}] children={c["children"]} cls="{c["cls"][:70]}"')

View File

@@ -0,0 +1,109 @@
"""Trace the DOM path from body to AI response container."""
import json, os
fpath = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge', 'dump_html_5.json')
with open(fpath, 'r', encoding='utf-8-sig') as f:
data = json.load(f)
body = data.get('body', data.get('bodyTree', {}))
def find_path_to_class(node, target_cls, path=None, depth=0):
"""Find the DOM path down to a node with a matching class."""
if path is None: path = []
if not isinstance(node, dict): return []
tag = node.get('tag', '')
cls = node.get('cls', '')
children = node.get('children', [])
text = node.get('text', '')
attrs = node.get('attrs', {})
entry = {
'depth': depth,
'tag': tag,
'cls': cls[:120],
'children': len(children),
'text': text[:60] if text else '',
'attrs': {k:v[:40] for k,v in attrs.items() if k not in ('style',)}
}
if target_cls.lower() in cls.lower():
return path + [entry]
for i, child in enumerate(children):
result = find_path_to_class(child, target_cls, path + [entry], depth+1)
if result:
return result
return []
# Find path to the AI response container
print("=== PATH TO 'leading-relaxed select-text' ===")
path = find_path_to_class(body, 'leading-relaxed select-text')
for p in path:
indent = ' ' * p['depth']
print(f'{indent}[{p["tag"]}] cls="{p["cls"]}" children={p["children"]} {p["attrs"]}')
if p['text']:
print(f'{indent} text: "{p["text"]}"')
# Now get the full subtree of the AI response container
def get_subtree(node, target_cls, depth=0):
if not isinstance(node, dict): return None
cls = node.get('cls', '')
if target_cls.lower() in cls.lower():
return node
for child in node.get('children', []):
result = get_subtree(child, target_cls, depth+1)
if result:
return result
return None
print("\n=== AI RESPONSE CONTAINER SUBTREE ===")
container = get_subtree(body, 'leading-relaxed select-text')
if container:
def print_tree(node, depth=0, max_depth=4):
if not isinstance(node, dict) or depth > max_depth: return
tag = node.get('tag','')
cls = node.get('cls','')[:80]
text = node.get('text','')
children = node.get('children', [])
indent = ' ' * depth
line = f'{indent}[{tag}]'
if cls: line += f' cls="{cls}"'
line += f' children={len(children)}'
if text: line += f' text="{text[:60]}"'
print(line)
for c in children:
print_tree(c, depth+1, max_depth)
print_tree(container, 0, 3)
# Also search for the chat panel container - what wraps the entire conversation
print("\n=== SEARCH FOR CHAT PANEL WRAPPERS ===")
chat_patterns = ['chat', 'antigravity', 'gemini', 'panel', 'agentview', 'sidebar', 'conversation']
for pat in chat_patterns:
path = find_path_to_class(body, pat)
if path:
last = path[-1]
print(f' Pattern "{pat}" found at depth={last["depth"]} [{last["tag"]}] cls="{last["cls"]}" children={last["children"]}')
# Find the parent chain from body to the container - look by scanning ALL class names
print("\n=== ALL UNIQUE CLASS NAMES (depth <= 12) ===")
all_classes = set()
def collect_classes(node, depth=0, max_depth=12):
if not isinstance(node, dict) or depth > max_depth: return
cls = node.get('cls', '')
if cls:
for c in cls.split():
if len(c) > 3 and not c.startswith('{') and 'mtk' not in c:
all_classes.add(c)
for child in node.get('children', []):
collect_classes(child, depth+1, max_depth)
collect_classes(body)
# Print classes sorted, grouped by potential relevance
relevant = sorted([c for c in all_classes if any(k in c.lower() for k in
['chat', 'message', 'response', 'agent', 'gemini', 'turn', 'model', 'user', 'bot', 'conversation', 'markdown', 'prose', 'text-', 'content'])])
print("Relevant classes:")
for c in relevant:
print(f' {c}')

View File

@@ -0,0 +1,217 @@
"""Vikunja safe task updater — preserves existing fields when updating tasks.
Usage:
python vikunja_helper.py done 75 # Mark task #75 as done
python vikunja_helper.py done 71 77 78 # Mark multiple tasks done
python vikunja_helper.py undone 75 # Mark task #75 as not done
python vikunja_helper.py comment 75 "text" # Add comment to task #75
python vikunja_helper.py desc 75 "text" # Set description (appends if exists)
python vikunja_helper.py create "title" "desc" --labels Backend,Priority:High
python vikunja_helper.py create "title" "desc" --done --labels Frontend,Priority:Mid
python vikunja_helper.py label 75 Backend Priority:High # Add labels to task
python vikunja_helper.py list # List all tasks
python vikunja_helper.py list todo # List TODO only
python vikunja_helper.py list done # List DONE only
"""
import sys
import json
import urllib.request
import urllib.error
import io
# Fix Windows console encoding (cp949 → utf-8)
if sys.stdout.encoding != "utf-8":
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
# ============================================================
# ⚙️ CONFIGURATION — PROJECT_ID만 프로젝트별로 변경하세요
# ============================================================
API_BASE = "https://plan.variet.net/api/v1"
TOKEN = "tk_070f8e0b715e818bb7178c3815ed5389040eddca"
PROJECT_ID = 8 # gravity_control project
# ============================================================
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
}
# Label name → Vikunja label ID mapping
# Customize for your project's labels
LABEL_MAP = {
"Backend": 1, "Frontend": 2, "Engine": 3, "Infra": 4, "Test": 5,
"Priority:High": 6, "Priority:Mid": 7, "Priority:Low": 8,
"Agent": 17, "Tool": 18, "AI/LLM": 19,
}
def api_get(path: str):
req = urllib.request.Request(f"{API_BASE}{path}", headers=HEADERS)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode("utf-8"))
def api_post(path: str, data: dict):
body = json.dumps(data).encode("utf-8")
req = urllib.request.Request(f"{API_BASE}{path}", data=body, headers=HEADERS, method="POST")
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode("utf-8"))
def api_put(path: str, data: dict):
body = json.dumps(data).encode("utf-8")
req = urllib.request.Request(f"{API_BASE}{path}", data=body, headers=HEADERS, method="PUT")
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode("utf-8"))
def get_task(task_id: int) -> dict:
return api_get(f"/tasks/{task_id}")
def safe_update_task(task_id: int, updates: dict) -> dict:
task = get_task(task_id)
safe_body = {
"title": task.get("title", ""),
"description": task.get("description", ""),
"priority": task.get("priority", 0),
"done": task.get("done", False),
}
safe_body.update(updates)
return api_post(f"/tasks/{task_id}", safe_body)
def mark_done(task_ids: list):
for tid in task_ids:
result = safe_update_task(tid, {"done": True})
title = result.get("title", "?")
print(f" ✅ #{tid} → done=True [{title}]")
def mark_undone(task_ids: list):
for tid in task_ids:
result = safe_update_task(tid, {"done": False})
title = result.get("title", "?")
print(f" ⬜ #{tid} → done=False [{title}]")
def add_comment(task_id: int, comment: str):
result = api_put(f"/tasks/{task_id}/comments", {"comment": comment})
print(f" 💬 #{task_id} comment added (id={result.get('id', '?')})")
def set_description(task_id: int, desc: str, append: bool = True):
task = get_task(task_id)
existing = task.get("description", "") or ""
if append and existing:
new_desc = existing.rstrip() + "\n\n" + desc
else:
new_desc = desc
result = safe_update_task(task_id, {"description": new_desc})
print(f" 📝 #{task_id} description updated [{result.get('title', '?')}]")
def list_tasks(filter_: str = "all"):
all_tasks = []
page = 1
while True:
batch = api_get(f"/projects/{PROJECT_ID}/tasks?per_page=50&page={page}")
if not batch:
break
all_tasks.extend(batch)
if len(batch) < 50:
break
page += 1
if filter_ == "todo":
all_tasks = [t for t in all_tasks if not t["done"]]
elif filter_ == "done":
all_tasks = [t for t in all_tasks if t["done"]]
all_tasks.sort(key=lambda t: t["id"])
for t in all_tasks:
status = "" if t["done"] else ""
desc = (t.get("description") or "")[:50].replace("\n", " ")
labels = ", ".join(l["title"] for l in (t.get("labels") or []))
print(f" {status} #{t['id']:3d} {t['title'][:40]:<40} [{labels}] {desc}")
print(f"\n Total: {len(all_tasks)} tasks")
def add_labels(task_id: int, label_names: list):
for name in label_names:
label_id = LABEL_MAP.get(name)
if not label_id:
print(f" ⚠️ Unknown label '{name}'. Valid: {', '.join(LABEL_MAP.keys())}")
continue
try:
api_put(f"/tasks/{task_id}/labels", {"label_id": label_id})
print(f" 🏷️ #{task_id} + {name} (id={label_id})")
except Exception as e:
if "already" in str(e).lower() or "409" in str(e):
print(f" 🏷️ #{task_id} already has {name}")
else:
print(f" ⚠️ #{task_id} label {name} failed: {e}")
def create_task(title: str, description: str = "", done: bool = False, labels: list = None):
payload = {"title": title, "description": description}
result = api_put(f"/projects/{PROJECT_ID}/tasks", payload)
task_id = result["id"]
print(f" ✨ #{task_id} created: {result.get('title', '?')}")
if labels:
add_labels(task_id, labels)
if done:
result = safe_update_task(task_id, {"done": True})
print(f" ✅ #{task_id} → done=True")
return result
def main():
if len(sys.argv) < 2:
print(__doc__)
return
cmd = sys.argv[1].lower()
if cmd == "done":
ids = [int(x) for x in sys.argv[2:]]
mark_done(ids)
elif cmd == "undone":
ids = [int(x) for x in sys.argv[2:]]
mark_undone(ids)
elif cmd == "comment":
add_comment(int(sys.argv[2]), sys.argv[3])
elif cmd == "desc":
set_description(int(sys.argv[2]), sys.argv[3])
elif cmd == "list":
f = sys.argv[2] if len(sys.argv) > 2 else "all"
list_tasks(f)
elif cmd == "label":
if len(sys.argv) < 4:
print("Usage: vikunja_helper.py label TASK_ID Label1 Label2 ...")
return
add_labels(int(sys.argv[2]), sys.argv[3:])
elif cmd == "create":
title = sys.argv[2] if len(sys.argv) > 2 else ""
desc = sys.argv[3] if len(sys.argv) > 3 and not sys.argv[3].startswith("--") else ""
is_done = "--done" in sys.argv
labels = None
for i, arg in enumerate(sys.argv):
if arg == "--labels" and i + 1 < len(sys.argv):
labels = sys.argv[i + 1].split(",")
break
if not title:
print("Error: title is required")
return
create_task(title, desc, done=is_done, labels=labels)
else:
print(f"Unknown command: {cmd}")
print(__doc__)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,100 @@
"""Gitea Wiki helper: list, read, create, update wiki pages.
Usage:
wiki_helper.py list — list all pages
wiki_helper.py read <title> — read a page
wiki_helper.py create <title> <file> — create a page from file
wiki_helper.py update <title> <file> — update a page from file
"""
import sys, io, json, base64, urllib.request, urllib.error
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# ============================================================
# ⚙️ CONFIGURATION — GITEA_REPO만 프로젝트별로 변경하세요
# ============================================================
GITEA_BASE_URL = "https://git.variet.net"
GITEA_OWNER = "Variet"
GITEA_REPO = "gravity_control" # ← 프로젝트별 변경 필요
GITEA_TOKEN = "3a01b4b15a39921572e64c413353e870d4d2161b"
# ============================================================
BASE = f"{GITEA_BASE_URL}/api/v1/repos/{GITEA_OWNER}/{GITEA_REPO}/wiki"
HEADERS = {"Authorization": f"token {GITEA_TOKEN}", "Content-Type": "application/json"}
def _req(method, path, data=None):
url = f"{BASE}{path}"
body = json.dumps(data).encode() if data else None
req = urllib.request.Request(url, data=body, headers=HEADERS, method=method)
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
err = e.read().decode()
print(f" ⚠️ HTTP {e.code}: {err}")
return None
def _find_sub_url(title):
pages = _req("GET", "/pages")
if pages:
for p in pages:
if p.get("title", "").lower() == title.lower():
return p.get("sub_url", title)
return title
def list_pages():
pages = _req("GET", "/pages")
if pages:
print(f"=== {len(pages)} Wiki Pages ===")
for p in pages:
print(f" {p.get('title', '?')}")
return pages
def read_page(title):
sub = _find_sub_url(title)
page = _req("GET", f"/page/{sub}")
if page and page.get("content_base64"):
content = base64.b64decode(page["content_base64"]).decode("utf-8")
return content
return None
def create_page(title, content):
data = {
"title": title,
"content_base64": base64.b64encode(content.encode()).decode(),
}
result = _req("POST", "/new", data)
if result:
print(f" ✅ Created wiki page: {title}")
return result
def update_page(title, content):
sub = _find_sub_url(title)
data = {
"title": title,
"content_base64": base64.b64encode(content.encode()).decode(),
}
result = _req("PATCH", f"/page/{sub}", data)
if result:
print(f" ✅ Updated wiki page: {title}")
return result
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "list"
if cmd == "list":
list_pages()
elif cmd == "read" and len(sys.argv) > 2:
content = read_page(sys.argv[2])
if content:
print(content[:5000])
else:
print(f" Page '{sys.argv[2]}' not found")
elif cmd == "create" and len(sys.argv) > 3:
with open(sys.argv[3], "r", encoding="utf-8") as f:
create_page(sys.argv[2], f.read())
elif cmd == "update" and len(sys.argv) > 3:
with open(sys.argv[3], "r", encoding="utf-8") as f:
update_page(sys.argv[2], f.read())
else:
print("Usage: wiki_helper.py list|read <title>|create <title> <file>|update <title> <file>")

View File

@@ -0,0 +1,39 @@
---
description: 모든 구현 작업 전 실행하는 사전 점검 체크리스트 (pre-task, 준비, 시작 전, 계획, 구현)
---
# Pre-Task Checklist
> [!IMPORTANT]
> 코딩을 시작하기 전에 반드시 이 체크리스트를 순서대로 완료하세요.
> 체크리스트를 건너뛸 경우 불필요한 시행착오가 발생합니다.
## 1단계: 요구사항 정리
- [ ] 유저 요청을 구체적 작업 항목으로 분해
- [ ] 변경 범위(scope)를 명확히 정의 (영향받는 파일/모듈)
- [ ] 성공 기준(acceptance criteria) 확인
## 2단계: 레퍼런스 확인 (추측 금지)
- [ ] `.agents/references/architecture.md` — 현재 아키텍처 확인
- [ ] `.agents/references/tech-stack.md` — 기술 스택 및 버전 확인
- [ ] `.agents/references/conventions.md` — 코딩 컨벤션 확인
- [ ] `.agents/references/known-issues.md` — 과거 실패 패턴 확인
- [ ] 관련 기존 코드 최소 3개 파일 읽기
> [!CAUTION]
> 레퍼런스 문서가 존재하는 주제에 대해 추측하지 마세요.
> 문서가 없으면 유저에게 확인을 요청하세요.
## 3단계: 계획 수립
- [ ] 변경할 파일 목록 작성
- [ ] 의존성 순서 파악 (어떤 파일부터 수정해야 하는가?)
- [ ] 리스크 식별 (어디서 실패할 가능성이 높은가?)
- [ ] 테스트 방법 결정 (어떻게 검증할 것인가?)
## 4단계: 유저 확인
- [ ] 계획을 유저에게 보고하고 승인받기 (변경 파일 3개 이상인 경우)
- [ ] 작은 변경은 바로 실행하되, 변경 내용을 명확히 설명

View File

@@ -0,0 +1,128 @@
---
description: 프로젝트 연동 서비스 URL, API 키, 프로젝트 정보 참조
---
# 서비스 연동 정보
> [!CAUTION]
> 이 파일에는 API 토큰이 포함되어 있습니다. 프라이빗 레포에서만 사용하세요.
## 로컬 환경
| 항목 | 값 |
|------|-----|
| **Node.js** | 시스템 설치 (`node`, `npm`) |
| **Python** | `C:\ProgramData\miniforge3\envs\gravity_control\python.exe` (**항상 이 경로 사용**) |
| **Shell** | PowerShell (`curl` = `Invoke-WebRequest` 별칭이므로 반드시 **`curl.exe`** 사용) |
## Gitea (Git Repository)
| 항목 | 값 |
|------|-----|
| **Base URL** | `https://git.variet.net` |
| **API Base** | `https://git.variet.net/api/v1` |
| **Repo** | `Variet/gravity_control` |
| **Token** | `3a01b4b15a39921572e64c413353e870d4d2161b` |
| **Auth Header** | `-H "Authorization: token 3a01b4b15a39921572e64c413353e870d4d2161b"` |
## Vikunja (Task Management)
| 항목 | 값 |
|------|-----|
| **Base URL** | `https://plan.variet.net` |
| **API Base** | `https://plan.variet.net/api/v1` |
| **Project ID** | `8` |
| **Token** | `tk_070f8e0b715e818bb7178c3815ed5389040eddca` |
| **Auth Header** | `-H "Authorization: Bearer tk_070f8e0b715e818bb7178c3815ed5389040eddca"` |
## Vikunja 태스크 조회
> [!TIP]
> 태스크 목록은 항상 라이브 조회를 사용합니다. 하드코딩된 매핑은 유지하지 않습니다.
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py list todo
```
## 기타 서비스
| 서비스 | URL | 용도 |
|--------|-----|------|
| Uptime Kuma | `https://status.variet.net` | 서비스 모니터링 |
| Authentik | `https://auth.variet.net` | SSO 인증 |
## AI 작업 프로토콜
> [!IMPORTANT]
> 아래 규칙은 모든 작업에 자동 적용됩니다. 유저가 별도 지시하지 않아도 따릅니다.
### Vikunja = Single Source of Truth (SSOT)
- **Vikunja가 유일한 작업 현황 관리 도구**입니다.
- 로컬 `task.md`는 현재 대화 내 세부 체크리스트용으로만 사용합니다.
- 새 TODO 발견 시 → Vikunja에 태스크 생성 (로컬 파일에만 적는 것은 금지)
- 작업 완료 시 → Vikunja 태스크 완료 처리 (로컬 체크만 하는 것은 금지)
### Vikunja 태깅 규칙
태스크 생성 시 반드시 아래 라벨을 적절히 부여합니다:
**영역 라벨 (필수, 1개 이상):**
| ID | 라벨 | 적용 대상 |
|:--:|-------|-----------:|
| 1 | `Backend` | 서버, DB, API |
| 2 | `Frontend` | UI, 웹 프론트엔드 |
| 3 | `Engine` | 핵심 엔진/로직 |
| 4 | `Infra` | Docker, CI/CD, 모니터링 |
| 5 | `Test` | 테스트, E2E |
**우선순위 라벨 (필수, 1개):**
| ID | 라벨 | 기준 |
|:--:|-------|------:|
| 6 | `Priority:High` | 핵심 기능 미완성, 블로커 |
| 7 | `Priority:Mid` | 기능 개선, UX 향상, 리팩터링 |
| 8 | `Priority:Low` | nice-to-have, 문서, 코드 정리 |
**태스크 제목 규칙:**
- 한글 + 핵심 키워드 (예: `WebSocket 재연결 로직 구현`)
- 50자 이내
### 작업 시작 시
1. `git pull` 으로 최신 코드 동기화
2. Vikunja 태스크 조회 (`/check-vikunja`) → 관련 태스크 ID 확인
3. 관련 태스크가 있으면 Vikunja에서 진행중 표시
4. 관련 태스크가 없으면 Vikunja에 새 태스크 생성 (태깅 규칙 준수)
### 작업 중
5. 의미 있는 단위마다 자주 커밋 (대규모 변경을 한번에 커밋하지 않음)
6. 커밋 메시지 규칙:
- `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, `ci:` 접두사 사용
- 관련 Vikunja 태스크가 있으면 `#task-{ID}` 참조 포함
- 예: `feat(server): WebSocket 재연결 로직 #task-21`
### 작업 완료 시
7. 모든 변경사항 커밋 + `git push`
8. Vikunja 태스크 완료 처리 (**반드시 `vikunja_helper.py` 사용**):
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py done {TASK_ID}
```
> [!CAUTION]
> **직접 `Invoke-RestMethod -Body '{"done": true}'` 사용 금지!**
> Vikunja API는 POST 시 body에 없는 필드를 빈값으로 덮어씁니다.
9. 작업 중 발견된 새 TODO → Vikunja에 태스크 생성
### 멀티 AI 협업 시 추가 규칙
- 작업 전 `git pull` 필수 (다른 AI가 push한 변경 반영)
- 같은 파일을 동시에 수정하지 않음
- 공유 인터페이스 수정 시 즉시 commit + push
- 충돌 발생 시 유저에게 확인 요청
## PowerShell 주의사항
- `curl` → PowerShell에서 `Invoke-WebRequest`의 별칭. **반드시 `curl.exe`** 사용
- `npm` → PowerShell에서 실행 정책 문제 시 `cmd /c npm` 사용
- JSON 파이프 파싱 시 PowerShell 이스케이핑 문제 → `.py` 스크립트 파일로 만들어 실행 권장

View File

@@ -0,0 +1,66 @@
---
description: 세션 시작 시 프로젝트 맥락을 빠르게 복구하는 워크플로우 (시작, continue, 이어서, 작업 시작)
---
# 세션 시작 프로토콜
새 대화 시작, "continue", "이어서", "작업 시작" 등 요청 시 이 워크플로우를 실행합니다.
// turbo-all
## 절차
### 0. 에이전트 룰 & 맥락 로딩 (자동)
`.agents/AGENT.md`를 읽고 에이전트 행동 규칙을 로딩합니다.
`.agents/references/known-issues.md`를 읽어 최근 이슈를 파악합니다.
`.agents/workflows/services.md`**로컬 환경** 섹션을 읽고 Python 경로 등 환경 설정을 확인합니다.
### 1. Devlog 맥락 복구
오늘 + 어제 devlog index를 읽고 최근 작업 흐름을 파악합니다.
```powershell
$today = Get-Date -Format "yyyy-MM-dd"
$yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-dd")
if (Test-Path "docs\devlog\$today.md") {
Write-Host "=== Devlog: $today ==="
Get-Content "docs\devlog\$today.md"
} elseif (Test-Path "docs\devlog\$yesterday.md") {
Write-Host "=== Devlog: $yesterday (no entry for today yet) ==="
Get-Content "docs\devlog\$yesterday.md"
} else {
Write-Host "=== No recent devlog found ==="
}
```
미완료(🔧) 항목이 있으면 해당 entry 파일을 읽어 이어받기 맥락을 확보합니다:
- Entry 경로: `docs/devlog/entries/YYYYMMDD-NNN.md`
### 2. Git 상태 확인
```powershell
git status --short
```
```powershell
git log --oneline -5
```
### 3. Vikunja TODO 태스크
```powershell
C:\ProgramData\miniforge3\envs\gravity_control\python.exe .agents\workflows\helpers\vikunja_helper.py list todo
```
### 4. 종합 보고
결과를 종합하여 사용자에게 보고:
- 마지막 작업 맥락 + 미완료 항목 (devlog 🔧 기반)
- TODO 태스크 목록 (라벨 + 우선순위)
- 다음 작업 제안
**우선순위 판단 기준** (라벨만으로 판단 금지):
- P0: 최근 커밋에서 스키마/모델/인터페이스 변경 → 연쇄 영향 점검
- P1: 서버 기동/API 응답 장애
- P2: 기능 미완성/UX 개선
- P3: 정확도 향상, 신규 기능, CI/CD, 문서 정리

1
.deps_installed Normal file
View File

@@ -0,0 +1 @@

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
__pycache__/
*.pyc
.env
*.vsix
extension/node_modules/
extension/out/
.deps_installed
gravity_control.log
*.tar.gz

View File

@@ -1,17 +1,31 @@
# Discord Bot Token # Discord Bot Token (필수)
DISCORD_TOKEN=your_discord_bot_token_here DISCORD_TOKEN=your_discord_bot_token_here
# Discord Guild (서버) ID — 봇이 채널을 생성할 서버 # Discord Guild (서버) ID (필수) — 봇이 채널을 생성할 서버
DISCORD_GUILD_ID= DISCORD_GUILD_ID=
# Antigravity Brain Path # Bridge 디렉토리 (기본값: ~/.gemini/antigravity/bridge)
BRAIN_PATH=C:\Users\Certes\.gemini\antigravity\brain # 보통 수정 불필요 — Extension과 동일 경로 사용
BRIDGE_PATH=
# Antigravity Brain Path (Watcher용)
BRAIN_PATH=
# 세션 활성 판단: 마지막 파일 변경으로부터 이 시간(초) 이내면 활성 # 세션 활성 판단: 마지막 파일 변경으로부터 이 시간(초) 이내면 활성
ACTIVE_TIMEOUT_SECONDS=300 ACTIVE_TIMEOUT_SECONDS=300
# Project name (used for Discord channel: AG-{PROJECT_NAME})
PROJECT_NAME=gravity_control
# Watcher Settings # Watcher Settings
DEBOUNCE_SECONDS=2 DEBOUNCE_SECONDS=2
# Bot mode: 'local' (default, file-based) or 'gateway' (서버 Docker + WS Hub)
BOT_MODE=local
# Gateway API Key (보안)
# 서버와 Collector에 동일한 키를 설정하세요
# 생성: python -c "import secrets; print(secrets.token_urlsafe(32))"
GATEWAY_API_KEY=
# Hub WebSocket 인증 (선택 — 미설정 시 인증 생략)
# 생성: python -c "import secrets; print(secrets.token_hex(32))"
GRAVITY_HUB_SECRET=
GRAVITY_REGISTRATION_CODE=

2
.gitignore vendored
View File

@@ -11,8 +11,6 @@ build/
.venv/ .venv/
venv/ venv/
# Agents (contains tokens)
.agents/
# IDE # IDE
.vscode/ .vscode/

BIN
.gitlog.txt Normal file

Binary file not shown.

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM python:3.12-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt aiohttp>=3.9.0
# Copy application code (all Python modules)
COPY *.py ./
# Default environment (can be overridden via docker-compose)
ENV BOT_MODE=gateway
ENV GATEWAY_PORT=8585
EXPOSE 8585
CMD ["python", "main.py"]

127
auth.py Normal file
View File

@@ -0,0 +1,127 @@
"""Authentication module — JWT token management for WebSocket Hub.
Two-stage auth:
1. Extension connects with a registration code (built into .vsix at build time)
2. Hub validates the code and issues a short-lived JWT session token
3. Subsequent reconnections use the JWT token directly
The master secret is stored server-side only (GRAVITY_HUB_SECRET env var).
"""
import hashlib
import hmac
import json
import logging
import os
import time
from base64 import urlsafe_b64decode, urlsafe_b64encode
logger = logging.getLogger(__name__)
# Defaults
DEFAULT_TOKEN_TTL = 86400 # 24 hours
DEFAULT_REGISTRATION_CODE = "" # Set via GRAVITY_REGISTRATION_CODE env var
class TokenManager:
"""Manages JWT-like token creation and verification.
Uses HMAC-SHA256 for signing. Tokens contain:
- project: project name scope
- pc: PC identifier (hostname or custom name)
- iat: issued at (unix timestamp)
- exp: expiration (unix timestamp)
"""
def __init__(self, secret: str = "", registration_code: str = ""):
self.secret = secret or os.getenv("GRAVITY_HUB_SECRET", "")
self.registration_code = registration_code or os.getenv(
"GRAVITY_REGISTRATION_CODE", DEFAULT_REGISTRATION_CODE
)
if not self.secret:
# Auto-generate a secret if not set (ephemeral — tokens invalid after restart)
self.secret = hashlib.sha256(os.urandom(32)).hexdigest()
logger.warning(
"[AUTH] No GRAVITY_HUB_SECRET set — generated ephemeral secret. "
"Tokens will be invalid after server restart."
)
def validate_registration_code(self, code: str) -> bool:
"""Check if the provided registration code matches."""
if not self.registration_code:
# No registration code configured → allow all connections
logger.warning("[AUTH] No registration code configured — accepting all")
return True
return hmac.compare_digest(code, self.registration_code)
def create_token(
self, project: str, pc_name: str, ttl: int = DEFAULT_TOKEN_TTL
) -> str:
"""Create a signed token for a specific project and PC.
Returns a base64-encoded string: {header}.{payload}.{signature}
"""
now = int(time.time())
payload = {
"project": project,
"pc": pc_name,
"iat": now,
"exp": now + ttl,
}
payload_b64 = _b64_encode(json.dumps(payload))
signature = self._sign(payload_b64)
return f"{payload_b64}.{signature}"
def verify_token(self, token: str) -> dict | None:
"""Verify and decode a token.
Returns the payload dict if valid, None if invalid or expired.
"""
try:
parts = token.split(".")
if len(parts) != 2:
return None
payload_b64, signature = parts
expected_sig = self._sign(payload_b64)
if not hmac.compare_digest(signature, expected_sig):
logger.warning("[AUTH] Invalid token signature")
return None
payload = json.loads(_b64_decode(payload_b64))
# Check expiration
if payload.get("exp", 0) < time.time():
logger.info(f"[AUTH] Token expired for {payload.get('pc', '?')}")
return None
return payload
except (json.JSONDecodeError, ValueError, KeyError) as e:
logger.warning(f"[AUTH] Token decode error: {e}")
return None
def _sign(self, data: str) -> str:
"""HMAC-SHA256 sign and return base64."""
sig = hmac.new(
self.secret.encode("utf-8"),
data.encode("utf-8"),
hashlib.sha256,
).digest()
return _b64_encode_bytes(sig)
def _b64_encode(data: str) -> str:
"""URL-safe base64 encode a string, no padding."""
return urlsafe_b64encode(data.encode("utf-8")).rstrip(b"=").decode("ascii")
def _b64_encode_bytes(data: bytes) -> str:
"""URL-safe base64 encode bytes, no padding."""
return urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def _b64_decode(data: str) -> str:
"""URL-safe base64 decode, handles missing padding."""
padded = data + "=" * (4 - len(data) % 4)
return urlsafe_b64decode(padded).decode("utf-8")

815
bot.py
View File

@@ -5,12 +5,19 @@ Multi-project channel architecture:
- Each conversation maps to a project via conv_to_project dict - Each conversation maps to a project via conv_to_project dict
- Extension registers projects via bridge/pending/ files - Extension registers projects via bridge/pending/ files
- Commands include project_name for routing to correct IDE window - Commands include project_name for routing to correct IDE window
Multi-PC UX:
- When multiple AG instances are active, messages get instance numbers (PC #1, #2)
- Users can target specific instances with !N <message> (e.g. !2 hello)
- When only one instance is active, natural conversation without numbers
""" """
import asyncio import asyncio
import re
import json import json
import logging import logging
import time import time
from collections import deque
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@@ -23,8 +30,7 @@ from parser import (
md_to_discord_text, md_to_discord_text,
format_task_embed_text, format_task_embed_text,
) )
from watcher import BrainEvent, EventType from models import BrainEvent, EventType, ApprovalRequest, UserResponse
from bridge import BridgeProtocol, ApprovalRequest, UserResponse
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -32,23 +38,98 @@ logger = logging.getLogger(__name__)
# ─── Discord UI Components ────────────────────────────────────────── # ─── Discord UI Components ──────────────────────────────────────────
class ApprovalView(discord.ui.View): class ApprovalView(discord.ui.View):
"""Discord buttons for approving/rejecting Antigravity actions.""" """Discord buttons for approving/rejecting Antigravity actions.
def __init__(self, bridge: BridgeProtocol, request: ApprovalRequest): Supports two modes:
super().__init__(timeout=300) 1. Legacy: ✅ 승인 / ❌ 거부 (when no buttons array)
self.bridge = bridge 2. Multi-choice: dynamic buttons from pending's buttons array
(e.g., ✅ Allow Once / ✅ Allow This Conversation / ❌ Deny)
"""
def __init__(self, request: ApprovalRequest,
buttons: list[dict] | None = None, hub=None):
super().__init__(timeout=1800) # 30 minutes
self.hub = hub # WSHub instance for WS response routing
self.request = request self.request = request
self.responded = False self.responded = False
self.buttons_data = buttons
if buttons and len(buttons) > 1:
# Multi-choice mode: remove the default decorated buttons first
# (they are added by @discord.ui.button at class definition time)
self.clear_items()
# Add a Discord button for each option
for btn_info in buttons:
btn_text = btn_info.get("text", "?")
btn_index = btn_info.get("index", 0)
is_reject = btn_text.lower() in ("deny", "reject", "cancel",
"reject all", "decline",
"dismiss", "stop")
style = discord.ButtonStyle.red if is_reject else discord.ButtonStyle.green
emoji = "" if is_reject else ""
button = discord.ui.Button(
label=f"{emoji} {btn_text}",
style=style,
custom_id=f"choice_{request.request_id}_{btn_index}",
)
# Bind the callback with closure over btn_index and btn_text
button.callback = self._make_choice_callback(btn_index, btn_text,
is_reject)
self.add_item(button)
# else: use the default @discord.ui.button decorated methods below
def _make_choice_callback(self, btn_index: int, btn_text: str,
is_reject: bool):
async def callback(interaction: discord.Interaction):
if self.responded:
await interaction.response.send_message("이미 응답됨",
ephemeral=True)
return
self.responded = True
response_data = {
"request_id": self.request.request_id,
"approved": not is_reject,
"button_index": btn_index,
"step_type": getattr(self.request, 'step_type', ''),
"project_name": getattr(self.request, 'project_name', ''),
}
# Hub WS route (primary — reaches remote Extensions)
delivered = False
if self.hub:
await self.hub.send_response_to_pending_owner(self.request.request_id, {
"type": "response", "data": response_data,
})
embed = interaction.message.embeds[0] if interaction.message.embeds else None
if embed:
color = discord.Color.red() if is_reject else discord.Color.green()
embed.color = color
emoji = "" if is_reject else ""
embed.set_footer(
text=f"{emoji} {btn_text} by {interaction.user.display_name}"
)
await interaction.response.edit_message(embed=embed, view=None)
return callback
@discord.ui.button(label="✅ 승인", style=discord.ButtonStyle.green) @discord.ui.button(label="✅ 승인", style=discord.ButtonStyle.green)
async def approve(self, interaction: discord.Interaction, button: discord.ui.Button): async def approve(self, interaction: discord.Interaction, button: discord.ui.Button):
# Only active in legacy mode (no buttons array)
if self.buttons_data and len(self.buttons_data) > 1:
return # multi-choice mode handles via dynamic buttons
if self.responded: if self.responded:
await interaction.response.send_message("이미 응답됨", ephemeral=True) await interaction.response.send_message("이미 응답됨", ephemeral=True)
return return
self.responded = True self.responded = True
self.bridge.write_response(UserResponse( response_data = {
request_id=self.request.request_id, approved=True, "request_id": self.request.request_id, "approved": True,
)) "step_type": getattr(self.request, 'step_type', ''),
"project_name": getattr(self.request, 'project_name', ''),
}
if self.hub:
await self.hub.send_response_to_pending_owner(self.request.request_id, {
"type": "response", "data": response_data,
})
embed = interaction.message.embeds[0] if interaction.message.embeds else None embed = interaction.message.embeds[0] if interaction.message.embeds else None
if embed: if embed:
embed.color = discord.Color.green() embed.color = discord.Color.green()
@@ -57,13 +138,22 @@ class ApprovalView(discord.ui.View):
@discord.ui.button(label="❌ 거부", style=discord.ButtonStyle.red) @discord.ui.button(label="❌ 거부", style=discord.ButtonStyle.red)
async def reject(self, interaction: discord.Interaction, button: discord.ui.Button): async def reject(self, interaction: discord.Interaction, button: discord.ui.Button):
# Only active in legacy mode (no buttons array)
if self.buttons_data and len(self.buttons_data) > 1:
return # multi-choice mode handles via dynamic buttons
if self.responded: if self.responded:
await interaction.response.send_message("이미 응답됨", ephemeral=True) await interaction.response.send_message("이미 응답됨", ephemeral=True)
return return
self.responded = True self.responded = True
self.bridge.write_response(UserResponse( response_data = {
request_id=self.request.request_id, approved=False, "request_id": self.request.request_id, "approved": False,
)) "step_type": getattr(self.request, 'step_type', ''),
"project_name": getattr(self.request, 'project_name', ''),
}
if self.hub:
await self.hub.send_response_to_pending_owner(self.request.request_id, {
"type": "response", "data": response_data,
})
embed = interaction.message.embeds[0] if interaction.message.embeds else None embed = interaction.message.embeds[0] if interaction.message.embeds else None
if embed: if embed:
embed.color = discord.Color.red() embed.color = discord.Color.red()
@@ -71,10 +161,14 @@ class ApprovalView(discord.ui.View):
await interaction.response.edit_message(embed=embed, view=None) await interaction.response.edit_message(embed=embed, view=None)
async def on_timeout(self): async def on_timeout(self):
if not self.responded: if not self.responded and self.hub:
self.bridge.write_response(UserResponse( await self.hub.send_response_to_pending_owner(self.request.request_id, {
request_id=self.request.request_id, approved=False, "type": "response", "data": {
)) "request_id": self.request.request_id, "approved": False,
"step_type": getattr(self.request, 'step_type', ''),
"project_name": getattr(self.request, 'project_name', ''),
}
})
# ─── Bot ───────────────────────────────────────────────────────────── # ─── Bot ─────────────────────────────────────────────────────────────
@@ -99,12 +193,56 @@ class GravityBot(commands.Bot):
self.conv_to_project: dict[str, str] = {} # conv_id → project self.conv_to_project: dict[str, str] = {} # conv_id → project
self.channel_to_project: dict[int, str] = {} # channel.id → project self.channel_to_project: dict[int, str] = {} # channel.id → project
self.session_status_messages: dict[str, int] = {} # conv_id → msg_id self.session_status_messages: dict[str, int] = {} # conv_id → msg_id
self._sent_approval_ids: set[str] = set() self._sent_approval_ids: dict[str, bool] = {} # request_id → bool
self._deferred_ids: dict[str, int] = {} # request_id → defer count
self._sent_commands: dict[str, str] = {} # request_id → command text (for MERGE edit detection)
self._ready_event = asyncio.Event() self._ready_event = asyncio.Event()
self._channel_lock = asyncio.Lock() self._channel_lock = asyncio.Lock()
self.bridge = BridgeProtocol()
self.session_category: discord.CategoryChannel | None = None self.session_category: discord.CategoryChannel | None = None
self.guild: discord.Guild | None = None self.guild: discord.Guild | None = None
self.auto_approve_projects: set[str] = set() # projects with auto-approve enabled
self._processed_message_ids: deque[int] = deque(maxlen=200) # dedup for Gateway event replay
self._approval_messages: dict[str, int] = {} # FIX #4: request_id → discord message_id (for auto_resolved lookup)
self._last_auto_toggle: dict[str, float] = {} # project → timestamp (dedup for !auto embed)
self.gateway = None # Set by main.py in gateway mode
self.hub = None # Set by main.py in gateway mode (WSHub instance)
def _write_command(self, project: str, text: str, *,
target_instance: int | None = None, **kwargs):
"""Write command to Extension via Hub WS (primary) or file bridge (fallback).
When Hub is connected, ONLY use WS to prevent duplicate delivery.
File bridge + Gateway are legacy fallbacks for when Hub is unavailable.
Args:
target_instance: If set, send only to this instance number (via Hub).
If None, broadcast to all instances.
"""
cmd_data = {
"text": text,
"project_name": kwargs.get('project_name', project),
}
# Hub route (primary)
if self.hub:
import time as _time
cmd_data["id"] = str(int(_time.time() * 1000))
msg = {"type": "command", "data": cmd_data}
if target_instance is not None:
asyncio.create_task(
self.hub.send_to_instance(project, target_instance, msg)
)
else:
asyncio.create_task(
self.hub.broadcast_to_project(project, msg)
)
def _cap_dict(self, d: dict, max_size: int = 5000):
"""Prevent memory leaks by capping dictionary sizes using insertion order (oldest first)."""
if len(d) >= max_size:
to_remove = len(d) - max_size + max_size // 10 # remove 10%
for k in list(d.keys())[:to_remove]:
d.pop(k, None)
@staticmethod @staticmethod
def _make_channel_name(project_name: str) -> str: def _make_channel_name(project_name: str) -> str:
@@ -113,9 +251,9 @@ class GravityBot(commands.Bot):
async def setup_hook(self): async def setup_hook(self):
self.loop.create_task(self._process_events()) self.loop.create_task(self._process_events())
self.pending_approval_scanner.start()
self.chat_snapshot_scanner.start()
self._register_slash_commands() self._register_slash_commands()
# Register Hub handlers (if Hub is available, set after setup_hook by main.py)
asyncio.get_event_loop().call_soon(self._register_hub_handlers)
logger.info("Bot setup complete") logger.info("Bot setup complete")
def _register_slash_commands(self): def _register_slash_commands(self):
@@ -127,7 +265,7 @@ class GravityBot(commands.Bot):
if not project: if not project:
await interaction.response.send_message("⚠️ 프로젝트 채널이 아닙니다.", ephemeral=True) await interaction.response.send_message("⚠️ 프로젝트 채널이 아닙니다.", ephemeral=True)
return return
self.bridge.write_command(project, "!stop", project_name=project) self._write_command(project, "!stop", project_name=project)
await interaction.response.send_message( await interaction.response.send_message(
embed=discord.Embed( embed=discord.Embed(
title="⏹️ AI 작업 중지", title="⏹️ AI 작업 중지",
@@ -137,13 +275,19 @@ class GravityBot(commands.Bot):
) )
@self.tree.command(name="auto", description="자동 승인 토글") @self.tree.command(name="auto", description="자동 승인 토글")
async def slash_auto(interaction: discord.Interaction, mode: str): async def slash_auto(interaction: discord.Interaction):
project = self.channel_to_project.get(interaction.channel_id) project = self.channel_to_project.get(interaction.channel_id)
if not project: if not project:
await interaction.response.send_message("⚠️ 프로젝트 채널이 아닙니다.", ephemeral=True) await interaction.response.send_message("⚠️ 프로젝트 채널이 아닙니다.", ephemeral=True)
return return
enabled = mode.lower() in ("on", "true", "1") # Toggle
self.bridge.write_command(project, f"!auto {'on' if enabled else 'off'}", project_name=project) if project in self.auto_approve_projects:
self.auto_approve_projects.discard(project)
enabled = False
else:
self.auto_approve_projects.add(project)
enabled = True
self._write_command(project, f"!auto {'on' if enabled else 'off'}", project_name=project)
emoji = "🟢" if enabled else "🔴" emoji = "🟢" if enabled else "🔴"
await interaction.response.send_message( await interaction.response.send_message(
embed=discord.Embed( embed=discord.Embed(
@@ -159,7 +303,7 @@ class GravityBot(commands.Bot):
if not project: if not project:
await interaction.response.send_message("⚠️ 프로젝트 채널이 아닙니다.", ephemeral=True) await interaction.response.send_message("⚠️ 프로젝트 채널이 아닙니다.", ephemeral=True)
return return
self.bridge.write_command(project, message, project_name=project) self._write_command(project, message, project_name=project)
await interaction.response.send_message( await interaction.response.send_message(
embed=discord.Embed( embed=discord.Embed(
description=f"📨 → **{project}** IDE에 전달됨\n`{message[:100]}`", description=f"📨 → **{project}** IDE에 전달됨\n`{message[:100]}`",
@@ -189,57 +333,12 @@ class GravityBot(commands.Bot):
logger.error("No permission to create category!") logger.error("No permission to create category!")
return return
# Discover existing project channels # Start WS Hub processors by ensuring ready gate is open
await self._discover_channels()
# Load conversation → project registrations from Extension
self._load_registrations()
# Sync slash commands to guild
try:
self.tree.copy_global_to(guild=self.guild)
synced = await self.tree.sync(guild=self.guild)
logger.info(f"Synced {len(synced)} slash commands to guild")
except Exception as e:
logger.warning(f"Slash command sync failed: {e}")
# Open the gate
self._ready_event.set() self._ready_event.set()
logger.info("Ready gate opened — event processing enabled") logger.info("Ready gate opened — event processing enabled")
# Start scanner loops
if not self.pending_approval_scanner.is_running():
self.pending_approval_scanner.start()
if not self.chat_snapshot_scanner.is_running():
self.chat_snapshot_scanner.start()
logger.info("Scanner loops started")
# ─── Channel Management ────────────────────────────────────────── # ─── Channel Management ──────────────────────────────────────────
def _load_registrations(self):
"""Read bridge/register/ to learn conversation → project mappings."""
register_dir = self.bridge.bridge_dir / "register"
if not register_dir.exists():
return
count = 0
for f in register_dir.glob("*.json"):
try:
data = json.loads(f.read_text(encoding="utf-8-sig"))
conv_id = data.get("conversation_id", "")
project = data.get("project_name", "")
if conv_id and project:
self.conv_to_project[conv_id] = project
count += 1
except (json.JSONDecodeError, OSError):
pass
# Only log when count changes
prev = getattr(self, '_last_reg_count', -1)
if count != prev:
self._last_reg_count = count
if count:
logger.info(f"Loaded {count} conversation→project registrations")
# ─── Channel Management ────────────────────────────────────────── # ─── Channel Management ──────────────────────────────────────────
@@ -260,32 +359,38 @@ class GravityBot(commands.Bot):
logger.info(f"Discovered {len(self.project_channels)} project channels") logger.info(f"Discovered {len(self.project_channels)} project channels")
async def _get_channel(self, project_name: str) -> discord.TextChannel: async def _get_channel(self, project_name: str) -> discord.TextChannel:
"""Get or create a channel for a project. Lock-protected.""" """Get or create a channel for a project.
Uses guild.channels cache first (NO API call), only locks + creates
if channel truly doesn't exist. This prevents O(N) fetch_channels()
API calls when multiple projects arrive simultaneously.
"""
if project_name in self.project_channels: if project_name in self.project_channels:
return self.project_channels[project_name] return self.project_channels[project_name]
if not self.session_category:
logger.error(f"[CHANNEL] session_category is None — cannot get channel for project={project_name}")
return None
channel_name = self._make_channel_name(project_name)
# 1. Check guild channel cache (NO API call — instant)
existing = discord.utils.get(
self.guild.channels, name=channel_name,
category_id=self.session_category.id,
)
if existing and isinstance(existing, discord.TextChannel):
self.project_channels[project_name] = existing
self.channel_to_project[existing.id] = project_name
logger.info(f"Found channel (cache): #{channel_name}")
return existing
# 2. Only lock + API call if truly creating new channel
async with self._channel_lock: async with self._channel_lock:
# Double-check after lock # Double-check after lock (another coroutine may have created it)
if project_name in self.project_channels: if project_name in self.project_channels:
return self.project_channels[project_name] return self.project_channels[project_name]
channel_name = self._make_channel_name(project_name)
# Search existing channels FIRST (prevents duplicates)
try:
all_channels = await self.guild.fetch_channels()
for ch in all_channels:
if (isinstance(ch, discord.TextChannel)
and ch.name == channel_name
and ch.category_id == self.session_category.id):
self.project_channels[project_name] = ch
self.channel_to_project[ch.id] = project_name
logger.info(f"Found existing channel: #{channel_name}")
return ch
except Exception as e:
logger.warning(f"fetch_channels failed: {e}")
# No existing channel — create new
try: try:
ch = await self.guild.create_text_channel( ch = await self.guild.create_text_channel(
name=channel_name, name=channel_name,
@@ -307,6 +412,9 @@ class GravityBot(commands.Bot):
except discord.errors.Forbidden: except discord.errors.Forbidden:
logger.error(f"No permission to create channel: {channel_name}") logger.error(f"No permission to create channel: {channel_name}")
return None return None
except Exception as e:
logger.error(f"[CHANNEL] Failed to create channel #{channel_name}: {e}")
return None
def _resolve_project(self, conversation_id: str) -> str: def _resolve_project(self, conversation_id: str) -> str:
"""Get project name for a conversation. Falls back to default.""" """Get project name for a conversation. Falls back to default."""
@@ -398,110 +506,93 @@ class GravityBot(commands.Bot):
event_label = "생성" if event.event_type == EventType.FILE_CREATED else "업데이트" event_label = "생성" if event.event_type == EventType.FILE_CREATED else "업데이트"
full_content = event.content.strip() full_content = event.content.strip()
CHUNK_SIZE = 4000 # Discord embed desc limit is 4096 if not full_content:
full_content = "(빈 파일)"
# Split into chunks for long content FILE_ATTACH_THRESHOLD = 4000 # Above this, send as file attachment
chunks = []
while full_content:
chunks.append(full_content[:CHUNK_SIZE])
full_content = full_content[CHUNK_SIZE:]
if not chunks: if len(full_content) > FILE_ATTACH_THRESHOLD:
chunks = ["(빈 파일)"] # Long content → summary embed + file attachment
# Extract first meaningful paragraph for summary
summary_lines = []
for line in full_content.split('\n'):
if line.strip():
summary_lines.append(line.strip())
if len('\n'.join(summary_lines)) > 300:
break
summary = '\n'.join(summary_lines[:5])
if len(summary) > 500:
summary = summary[:500] + '...'
# First chunk with title
embed = discord.Embed(
title=f"{label} ({event_label}됨)",
description=chunks[0],
color=discord.Color.blue(),
timestamp=datetime.now(timezone.utc),
)
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
await channel.send(embed=embed)
# Additional chunks if content is long
for i, chunk in enumerate(chunks[1:], 2):
embed = discord.Embed( embed = discord.Embed(
title=f"{label} (계속 {i}/{len(chunks)})", title=f"{label} ({event_label})",
description=chunk, description=f"{summary}\n\n📎 *전체 내용은 첨부 파일을 확인하세요* ({len(full_content):,}자)",
color=discord.Color.blue(), color=discord.Color.blue(),
timestamp=datetime.now(timezone.utc),
) )
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
# Create in-memory file attachment
import io
file_bytes = full_content.encode('utf-8')
discord_file = discord.File(
io.BytesIO(file_bytes),
filename=event.file_name,
)
await channel.send(embed=embed, file=discord_file)
else:
# Short content → inline embed (original behavior)
embed = discord.Embed(
title=f"{label} ({event_label}됨)",
description=full_content,
color=discord.Color.blue(),
timestamp=datetime.now(timezone.utc),
)
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
await channel.send(embed=embed) await channel.send(embed=embed)
# ─── Approval Scanner ──────────────────────────────────────────── # ─── Approval Scanner ────────────────────────────────────────────
@tasks.loop(seconds=3)
async def pending_approval_scanner(self):
"""Scan bridge/pending/ for new approval requests + reload registrations."""
try:
# Reload conv→project registrations each cycle
self._load_registrations()
# Ensure channels exist for all registered projects
for project in set(self.conv_to_project.values()):
if project not in self.project_channels:
await self._get_channel(project)
logger.info(f"Auto-created channel for registered project: {project}")
requests = self.bridge.get_pending_requests()
for req in requests:
if req.request_id in self._sent_approval_ids:
continue
if req.discord_message_id != 0:
continue
# Learn project mapping from pending approval # ─── Discord → IDE Text Relay + Multi-PC UX ───────────────────────────
project = req.project_name or Config.PROJECT_NAME
if req.conversation_id and req.conversation_id != '__global__':
self.conv_to_project[req.conversation_id] = project
channel = await self._get_channel(project) def _get_instance_header(self, project: str, instance_number: int) -> str:
if channel: """Format instance header based on active count.
self._sent_approval_ids.add(req.request_id)
await self._send_approval_request(channel, req)
except Exception as e:
logger.error(f"Error scanning approvals: {e}")
@pending_approval_scanner.before_loop Single instance: empty string (natural conversation)
async def before_scanner(self): Multiple instances: **[PC #N]** prefix
await self.wait_until_ready() """
if not self.hub:
return ""
active = self.hub.get_active_count(project)
if active <= 1:
return ""
return f"**[PC #{instance_number}]** "
async def _send_approval_request( def _parse_instance_target(self, text: str) -> tuple[int | None, str]:
self, channel: discord.TextChannel, request: ApprovalRequest """Parse !N prefix from message text.
):
embed = discord.Embed(
title="⚠️ 승인 요청",
description=(
f"**명령어:**\n```\n{request.command[:1000]}\n```\n"
f"{request.description[:500]}"
),
color=discord.Color.orange(),
timestamp=datetime.now(timezone.utc),
)
embed.set_footer(text=f"ID: {request.request_id}")
view = ApprovalView(self.bridge, request) Returns (target_instance, remaining_text).
msg = await channel.send(embed=embed, view=view) '!2 hello' -> (2, 'hello')
'hello' -> (None, 'hello')
pending_file = self.bridge.pending_dir / f"{request.request_id}.json" '!stop' -> (None, '!stop') # special commands not treated as targeting
if pending_file.exists(): """
try: match = re.match(r'^!(\d+)\s+(.+)', text, re.DOTALL)
data = json.loads(pending_file.read_text(encoding="utf-8-sig")) if match:
data["discord_message_id"] = msg.id return int(match.group(1)), match.group(2).strip()
pending_file.write_text( return None, text
json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
)
except (json.JSONDecodeError, OSError):
pass
logger.info(f"Sent approval request: {request.request_id[:12]}")
# ─── Discord → IDE Text Relay ─────────────────────────────────────
async def on_message(self, message: discord.Message): async def on_message(self, message: discord.Message):
if message.author == self.user: if message.author == self.user:
return return
# Dedup: Discord Gateway can deliver MESSAGE_CREATE twice on reconnection
if message.id in self._processed_message_ids:
return
self._processed_message_ids.append(message.id)
# Determine project from channel # Determine project from channel
project = self.channel_to_project.get(message.channel.id) project = self.channel_to_project.get(message.channel.id)
if not project: if not project:
@@ -510,92 +601,362 @@ class GravityBot(commands.Bot):
text = message.content.strip() text = message.content.strip()
# Parse !N instance targeting (before special commands)
target_instance, actual_text = self._parse_instance_target(text)
# Special command: !stop — cancel AI work # Special command: !stop — cancel AI work
if text == "!stop": if actual_text == "!stop":
self.bridge.write_command(project, "!stop", project_name=project) self._write_command(project, "!stop", target_instance=target_instance,
project_name=project)
target_label = f" (PC #{target_instance})" if target_instance else ""
embed = discord.Embed( embed = discord.Embed(
title="⏹️ AI 작업 중지", title="⏹️ AI 작업 중지",
description=f"프로젝트: **{project}**\n중지 요청을 Extension에 전달했습니다.", description=f"프로젝트: **{project}**{target_label}\n중지 요청을 Extension에 전달했습니다.",
color=discord.Color.orange(), color=discord.Color.orange(),
) )
await message.channel.send(embed=embed) await message.channel.send(embed=embed)
return return
# Special command: !auto on/off # Special command: !auto — toggle auto-approve
if text in ("!auto on", "!auto off"): if actual_text == "!auto":
self.bridge.write_command(project, text, project_name=project) # Dedup: skip if toggled within 5s for same project (Gateway event replay)
enabled = text == "!auto on" now = time.time()
last = self._last_auto_toggle.get(project, 0)
if now - last < 5.0:
logger.info(f"[AUTO] Dedup: skipping duplicate !auto for {project} ({now-last:.1f}s ago)")
return
self._last_auto_toggle[project] = now
# Toggle per-project auto-approve
if project in self.auto_approve_projects:
self.auto_approve_projects.discard(project)
enabled = False
else:
self.auto_approve_projects.add(project)
enabled = True
self._write_command(project, f"!auto {'on' if enabled else 'off'}",
target_instance=target_instance, project_name=project)
emoji = "🟢" if enabled else "🔴" emoji = "🟢" if enabled else "🔴"
mode = "자동 승인" if enabled else "수동 승인" mode = "자동 승인" if enabled else "수동 승인"
embed = discord.Embed( embed = discord.Embed(
title=f"{emoji} {mode} 모드", title=f"{emoji} {mode} 모드",
description=f"프로젝트: **{project}**\n" description=f"프로젝트: **{project}**\n"
f"`chat.tools.autoApprove = {enabled}`\n" f"모든 승인 요청이 {'자동으로 승인됩니다' if enabled else '수동 확인이 필요합니다'}",
f"`chat.agent.autoApprove = {enabled}`",
color=discord.Color.green() if enabled else discord.Color.red(), color=discord.Color.green() if enabled else discord.Color.red(),
) )
await message.channel.send(embed=embed) await message.channel.send(embed=embed)
return return
# General text relay — routed by project # General text relay — routed by project (+ optional instance targeting)
if text: if actual_text:
self.bridge.write_command(project, text, project_name=project) self._write_command(project, actual_text, target_instance=target_instance,
project_name=project)
await message.add_reaction("📨") await message.add_reaction("📨")
target_label = f" PC #{target_instance}" if target_instance else ""
embed = discord.Embed( embed = discord.Embed(
description=f"📨 → **{project}** IDE에 전달됨\n`{text[:100]}`", description=f"📨 → **{project}**{target_label} IDE에 전달됨\n`{actual_text[:100]}`",
color=discord.Color.blurple(), color=discord.Color.blurple(),
) )
await message.channel.send(embed=embed, delete_after=10) await message.channel.send(embed=embed, delete_after=10)
await self.process_commands(message) await self.process_commands(message)
# ─── Chat Snapshot Scanner ───────────────────────────────────────── # ─── Hub Event Handlers ──────────────────────────────────────────
@tasks.loop(seconds=5) def _register_hub_handlers(self):
async def chat_snapshot_scanner(self): """Register callbacks on the Hub for Extension->Bot messages."""
"""Scan bridge/chat_snapshots/ for AI response dumps.""" if not self.hub:
return
self.hub.set_bot_handlers(
on_pending=self._hub_on_pending,
on_chat=self._hub_on_chat,
on_register=self._hub_on_register,
on_auto_resolve=self._hub_on_auto_resolve,
on_brain_event=self._hub_on_brain_event,
)
logger.info("[BOT] Hub handlers registered")
async def _hub_on_pending(self, project: str, data: dict):
"""Handle pending approval from Hub (Extension->Hub->Bot)."""
try: try:
snapshot_dir = self.bridge.bridge_dir / "chat_snapshots" request_id = data.get("request_id", "")
if not snapshot_dir.exists(): if not request_id:
return return
for f in snapshot_dir.glob("*.json"): # Skip if already sent
try: if request_id in self._sent_approval_ids:
data = json.loads(f.read_text(encoding="utf-8-sig")) return
project = data.get("project_name", Config.PROJECT_NAME)
content = data.get("content", "")
if content: # Check auto_resolved / auto_approved status
channel = await self._get_channel(project) status = data.get("status", "pending")
if channel: if status in ("auto_resolved", "expired"):
# Split long content await self._handle_auto_resolved(request_id, status)
CHUNK = 4000 return
chunks = [content[i:i+CHUNK] for i in range(0, len(content), CHUNK)] if status == "auto_approved":
for i, chunk in enumerate(chunks): # Bridge-level auto-approve (e.g. "Always run") — show notification only
title = "💬 AI 대화 내용" if i == 0 else f"💬 (계속 {i+1}/{len(chunks)})" channel = await self._get_channel(project)
embed = discord.Embed( if channel:
title=title, cmd_text = data.get("command", "")[:200]
description=chunk, desc_text = data.get("description", "")[:300]
color=discord.Color.purple(), embed = discord.Embed(
timestamp=datetime.now(timezone.utc), title="🤖 자동 승인됨 (Always run)",
) description=f"✅ **{cmd_text}**" + (f"\n```\n{desc_text}\n```" if desc_text and len(desc_text) > 3 else ""),
try: color=discord.Color.green(),
await channel.send(embed=embed) )
except discord.NotFound: embed.set_footer(text=f"auto-approve | {request_id[:12]}")
# Channel was deleted — invalidate cache and retry once await channel.send(embed=embed)
logger.warning(f"Channel deleted for {project}, re-creating...") self._cap_dict(self._sent_approval_ids)
self.project_channels.pop(project, None) self._sent_approval_ids[request_id] = True
channel = await self._get_channel(project) logger.info(f"[HUB-PENDING] Auto-approved (Always run): {request_id[:12]} project={project}")
if channel: return
await channel.send(embed=embed)
break instance_number = data.get("_instance_number", 0)
pc_name = data.get("_pc_name", "")
header = self._get_instance_header(project, instance_number)
# Build approval request
request = ApprovalRequest(
request_id=request_id,
conversation_id=data.get("conversation_id", ""),
command=data.get("command", ""),
description=data.get("description", ""),
timestamp=data.get("timestamp", time.time()),
project_name=project,
step_type=data.get("step_type", ""),
status=status,
)
# Auto-approve check
if project in self.auto_approve_projects:
await self._auto_approve_via_hub(request)
return
# Send to Discord
channel = await self._get_channel(project)
if not channel:
logger.warning(f"[HUB-PENDING] No channel for project={project}")
return
buttons = data.get("buttons", [])
desc_parts = []
if header:
desc_parts.append(header)
# Clean command text (remove "Running2" artifacts → "Running 2")
cmd_text = request.command[:200]
import re
cmd_text = re.sub(r'Running(\d)', r'Running \1', cmd_text)
desc_parts.append(f"**명령:** `{cmd_text}`")
if buttons:
btn_names = [b.get("text", "?") for b in buttons]
desc_parts.append(f"**선택지:** {' / '.join(btn_names)}")
# Clean description: strip noise headers and garbage
desc_raw = request.description or ""
# Remove old-style headers
desc_raw = re.sub(r'\[AI 본문 요약\]\s*', '', desc_raw)
desc_raw = re.sub(r'\[결행 명령\]\s*', '', desc_raw)
# Remove lines that are clearly noise
desc_lines = desc_raw.split('\n')
clean_desc_lines = []
for dline in desc_lines:
dline_stripped = dline.strip()
if not dline_stripped:
continue
# Skip UI artifacts
if dline_stripped in ('chevron_right', 'chevron_left', 'close', 'check',
'content_copy', 'expand_more', 'expand_less',
'Show more', 'Show less', 'Copy', 'Edit', 'Copied!'):
continue
# Skip "Thought for Xs"
if re.match(r'^Thought for \d+', dline_stripped):
continue
# Skip TypeScript declarations and file paths
if re.match(r'^(declare|import|export)\s+(class|function|interface|type|enum|const)', dline_stripped):
continue
if re.search(r'\.ts:\d+:', dline_stripped):
continue
if re.search(r'extension.*src.*sdk', dline_stripped, re.IGNORECASE):
continue
clean_desc_lines.append(dline_stripped)
clean_desc = '\n'.join(clean_desc_lines).strip()
if clean_desc and len(clean_desc) > 3:
# Truncate and wrap in code block for readability
if len(clean_desc) > 300:
clean_desc = clean_desc[:300] + ''
desc_parts.append(f"```\n{clean_desc}\n```")
embed = discord.Embed(
title=f"⚠️ 승인 요청 — {request.step_type or 'action'}",
description="\n".join(desc_parts),
color=discord.Color.orange(),
timestamp=datetime.now(timezone.utc),
)
embed.set_footer(text=f"ID: {request_id}")
view = ApprovalView(request, buttons=buttons, hub=self.hub)
msg = await channel.send(
content=f"🔔 **새로운 승인 요청이 도착했습니다** (ID: {request_id[:8]})",
embed=embed,
view=view
)
self._cap_dict(self._sent_approval_ids)
self._sent_approval_ids[request_id] = True
self._cap_dict(self._approval_messages)
self._approval_messages[request_id] = msg.id
logger.info(f"[HUB-PENDING] Sent approval: {request_id[:12]} project={project} | URL: {msg.jump_url}")
f.unlink() # Cleanup
except (json.JSONDecodeError, OSError) as e:
logger.warning(f"Bad chat snapshot {f.name}: {e}")
except Exception as e: except Exception as e:
logger.error(f"Error scanning chat snapshots: {e}") logger.error(f"[HUB-PENDING] Error: {e}")
async def _auto_approve_via_hub(self, request: ApprovalRequest):
"""Auto-approve a pending request via Hub."""
self._cap_dict(self._sent_approval_ids)
self._sent_approval_ids[request.request_id] = True
if self.hub:
await self.hub.send_response_to_pending_owner(request.request_id, {
"type": "response",
"data": {
"request_id": request.request_id,
"approved": True,
"button_index": 0,
"step_type": request.step_type,
"project_name": request.project_name,
},
})
# Send compact auto-approved embed to Discord (was missing — caused silent approvals)
channel = await self._get_channel(request.project_name)
if channel:
try:
embed = discord.Embed(
title="🤖 자동 승인됨",
description=f"✅ **{request.command}**\n\n```\n{request.description[:2000]}\n```" if getattr(request, "description", "") else f"✅ **{request.command}**",
color=discord.Color.green(),
)
embed.set_footer(text=f"auto-approve | {request.request_id[:12]}")
await channel.send(embed=embed)
except Exception as e:
logger.error(f"[HUB-AUTO] Discord send failed: {e}")
logger.info(f"[HUB-AUTO] Auto-approved: {request.request_id[:12]} project={request.project_name}")
async def _hub_on_chat(self, project: str, data: dict):
"""Handle chat snapshot from Hub (Extension->Hub->Bot->Discord)."""
try:
content = data.get("content", "")
attached_files = data.get("attached_files", [])
if not content and not attached_files:
return
instance_number = data.get("_instance_number", 0)
header = self._get_instance_header(project, instance_number)
channel = await self._get_channel(project)
if not channel:
return
import io as _io
discord_files = []
for af in attached_files:
af_name = af.get("name", "document.md")
af_content = af.get("content", "")
if af_content:
discord_files.append(discord.File(
_io.BytesIO(af_content.encode("utf-8")),
filename=af_name,
))
display_content = f"{header}{content}" if header else content
FILE_ATTACH_THRESHOLD = 4000
if len(display_content) > FILE_ATTACH_THRESHOLD:
summary = display_content[:500].rsplit('\n', 1)[0]
embed = discord.Embed(
title="💬 AI 대화 내용",
description=f"{summary}\n\n📎 *전체 내용은 첨부 파일 참조* ({len(content):,}자)",
color=discord.Color.purple(),
timestamp=datetime.now(timezone.utc),
)
discord_files.append(discord.File(
_io.BytesIO(content.encode("utf-8")),
filename="chat_message.md",
))
await channel.send(embed=embed, files=discord_files)
else:
embed = discord.Embed(
title="💬 AI 대화 내용",
description=display_content,
color=discord.Color.purple(),
timestamp=datetime.now(timezone.utc),
)
await channel.send(
embed=embed,
files=discord_files if discord_files else discord.utils.MISSING,
)
logger.info(f"[HUB-CHAT] Sent to #{channel.name} ({len(content)} chars)")
except Exception as e:
logger.error(f"[HUB-CHAT] Error: {e}")
async def _hub_on_register(self, data: dict):
"""Handle session registration from Hub."""
conv_id = data.get("conversation_id", "")
project = data.get("project_name", "")
if conv_id and project:
self.conv_to_project[conv_id] = project
logger.info(f"[HUB-REG] {conv_id[:8]}{project}")
async def _hub_on_auto_resolve(self, project: str, data: dict):
"""Handle auto_resolve notification from Hub."""
request_id = data.get("request_id", "")
if request_id:
await self._handle_auto_resolved(request_id, "auto_resolved")
async def _hub_on_brain_event(self, project: str, data: dict):
"""Handle brain event from Hub (Extension->Hub->Bot->Discord)."""
try:
from models import BrainEvent, EventType
event = BrainEvent(
event_type=EventType(data.get("event_type", "file_changed")),
conversation_id=data.get("conversation_id", ""),
file_name=data.get("file_name", ""),
file_path=None,
content=data.get("content", ""),
timestamp=data.get("timestamp", time.time()),
)
await self.event_queue.put(event)
except Exception as e:
logger.error(f"[HUB-EVENT] Error: {e}")
async def _handle_auto_resolved(self, request_id: str, status: str):
"""Edit Discord message to show auto-resolved/expired status."""
msg_id = self._approval_messages.get(request_id)
if not msg_id:
return
# Find the channel containing this message
for channel in self.project_channels.values():
try:
msg = await channel.fetch_message(msg_id)
embed = msg.embeds[0] if msg.embeds else None
if embed:
if status == "auto_resolved":
embed.color = discord.Color.green()
embed.set_footer(text="✅ 자동 해결됨")
else:
embed.color = discord.Color.greyple()
embed.set_footer(text="⏰ 만료됨")
await msg.edit(embed=embed, view=None)
self._approval_messages.pop(request_id, None)
break
except (discord.NotFound, discord.Forbidden):
continue
except Exception:
break
# ─── Chat Snapshot Scanner ─────────────────────────────────────────
@chat_snapshot_scanner.before_loop
async def before_chat_scanner(self):
await self.wait_until_ready()

132
bridge.py
View File

@@ -1,132 +0,0 @@
"""Bridge protocol — file-based communication between Discord bot and Antigravity.
Bridge directory: ~/.gemini/antigravity/bridge/
Structure:
bridge/
pending/ ← Bot writes approval requests for Discord
response/ ← Bot writes user responses from Discord
commands/ ← Bot writes user text input from Discord
Protocol:
1. VS Code Extension detects pending approval → writes JSON to pending/
2. Bot reads pending/ → sends Discord message with ✅/❌ buttons
3. User clicks button → Bot writes JSON to response/
4. VS Code Extension reads response/ → executes action
"""
import json
import time
import logging
from pathlib import Path
from dataclasses import dataclass, asdict
from enum import Enum
from config import Config
logger = logging.getLogger(__name__)
class ApprovalStatus(Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
TIMEOUT = "timeout"
@dataclass
class ApprovalRequest:
"""An approval request from Antigravity."""
request_id: str
conversation_id: str
command: str # The command/action needing approval
description: str # Human-readable description
timestamp: float
status: str = "pending"
discord_message_id: int = 0
project_name: str = "" # Project routing key
@dataclass
class UserResponse:
"""A user response from Discord."""
request_id: str
approved: bool
user_input: str = ""
timestamp: float = 0
class BridgeProtocol:
"""Manages the file-based bridge protocol."""
def __init__(self):
self.bridge_dir = Config.BRAIN_PATH.parent / "bridge"
self.pending_dir = self.bridge_dir / "pending"
self.response_dir = self.bridge_dir / "response"
self.commands_dir = self.bridge_dir / "commands"
# Create directories
for d in [self.pending_dir, self.response_dir, self.commands_dir]:
d.mkdir(parents=True, exist_ok=True)
logger.info(f"Bridge protocol initialized: {self.bridge_dir}")
def get_pending_requests(self) -> list[ApprovalRequest]:
"""Read all pending approval requests."""
requests = []
fields = {f.name for f in ApprovalRequest.__dataclass_fields__.values()}
for f in self.pending_dir.glob("*.json"):
try:
data = json.loads(f.read_text(encoding="utf-8-sig"))
if data.get("status") == "pending":
# Filter to known fields only
filtered = {k: v for k, v in data.items() if k in fields}
requests.append(ApprovalRequest(**filtered))
except (json.JSONDecodeError, TypeError, OSError) as e:
logger.warning(f"Bad pending request {f.name}: {e}")
return requests
def write_response(self, response: UserResponse):
"""Write a user response to the response directory."""
response.timestamp = time.time()
filename = f"{response.request_id}.json"
filepath = self.response_dir / filename
filepath.write_text(
json.dumps(asdict(response), ensure_ascii=False, indent=2),
encoding="utf-8"
)
logger.info(f"Response written: {filename} (approved={response.approved})")
# Mark pending request as processed
pending_file = self.pending_dir / filename
if pending_file.exists():
try:
data = json.loads(pending_file.read_text(encoding="utf-8"))
data["status"] = "approved" if response.approved else "rejected"
pending_file.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8"
)
except (json.JSONDecodeError, OSError):
pass
def write_command(self, conversation_id: str, text: str, *, project_name: str = ""):
"""Write a user text command for Antigravity to consume."""
cmd_id = f"{int(time.time() * 1000)}"
filepath = self.commands_dir / f"{cmd_id}.json"
data = {
"id": cmd_id,
"conversation_id": conversation_id,
"project_name": project_name,
"text": text,
"timestamp": time.time(),
"consumed": False,
}
filepath.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8"
)
logger.info(f"Command written: {cmd_id} → project={project_name}")
return cmd_id

View File

@@ -16,10 +16,11 @@ class Config:
DISCORD_GUILD_ID: int = int(os.getenv("DISCORD_GUILD_ID") or "0") DISCORD_GUILD_ID: int = int(os.getenv("DISCORD_GUILD_ID") or "0")
# Antigravity Brain path # Antigravity Brain path
BRAIN_PATH: Path = Path(os.getenv( # NOTE: os.getenv returns "" (not None) when .env has BRAIN_PATH= (empty value).
"BRAIN_PATH", # Path("") resolves to "." (CWD), which is WRONG. Use `or` to handle both None and "".
os.path.expanduser("~/.gemini/antigravity/brain") BRAIN_PATH: Path = Path(
)) os.getenv("BRAIN_PATH") or os.path.expanduser("~/.gemini/antigravity/brain")
)
# Watcher settings # Watcher settings
DEBOUNCE_SECONDS: float = float(os.getenv("DEBOUNCE_SECONDS", "5")) DEBOUNCE_SECONDS: float = float(os.getenv("DEBOUNCE_SECONDS", "5"))
@@ -31,6 +32,9 @@ class Config:
"walkthrough.md", "walkthrough.md",
} }
# Extension-based monitoring: any file with these extensions in brain/{conv}/ is watched
WATCHED_EXTENSIONS: set = {".md"}
# Discord message limits # Discord message limits
DISCORD_MSG_LIMIT: int = 2000 DISCORD_MSG_LIMIT: int = 2000
DISCORD_EMBED_DESC_LIMIT: int = 4096 DISCORD_EMBED_DESC_LIMIT: int = 4096
@@ -39,6 +43,14 @@ class Config:
CHANNEL_PREFIX: str = "AG" CHANNEL_PREFIX: str = "AG"
PROJECT_NAME: str = os.getenv("PROJECT_NAME", "gravity_control") PROJECT_NAME: str = os.getenv("PROJECT_NAME", "gravity_control")
# Bot mode: 'local' (file-based bridge) or 'gateway' (WS Hub + HTTP API)
BOT_MODE: str = os.getenv("BOT_MODE", "local")
GATEWAY_API_KEY: str = os.getenv("GATEWAY_API_KEY", "")
# WebSocket Hub
GRAVITY_HUB_SECRET: str = os.getenv("GRAVITY_HUB_SECRET", "") # JWT signing secret
GRAVITY_REGISTRATION_CODE: str = os.getenv("GRAVITY_REGISTRATION_CODE", "") # Extension auth
@classmethod @classmethod
def validate(cls) -> list[str]: def validate(cls) -> list[str]:
"""Return list of configuration errors.""" """Return list of configuration errors."""
@@ -47,6 +59,7 @@ class Config:
errors.append("DISCORD_TOKEN is not set") errors.append("DISCORD_TOKEN is not set")
if not cls.DISCORD_GUILD_ID: if not cls.DISCORD_GUILD_ID:
errors.append("DISCORD_GUILD_ID is not set") errors.append("DISCORD_GUILD_ID is not set")
if not cls.BRAIN_PATH.exists(): # Gateway mode doesn't need local BRAIN_PATH
if cls.BOT_MODE != 'gateway' and not cls.BRAIN_PATH.exists():
errors.append(f"BRAIN_PATH does not exist: {cls.BRAIN_PATH}") errors.append(f"BRAIN_PATH does not exist: {cls.BRAIN_PATH}")
return errors return errors

0
diag_output.txt Normal file
View File

30
docker-compose.yml Normal file
View File

@@ -0,0 +1,30 @@
services:
gateway:
build: .
container_name: gravity-gateway
restart: unless-stopped
ports:
- "127.0.0.1:8585:8585"
env_file:
- .env
environment:
- BOT_MODE=gateway
- GATEWAY_PORT=8585
- BRAIN_PATH=/app/data/brain
volumes:
- gateway-data:/app/data
networks:
- default
- proxy-net
logging:
driver: json-file
options:
max-size: 10m
max-file: 3
volumes:
gateway-data:
networks:
proxy-net:
external: true

30
docker-compose_server.yml Normal file
View File

@@ -0,0 +1,30 @@
services:
gateway:
build: .
container_name: gravity-gateway
restart: unless-stopped
ports:
- "127.0.0.1:8585:8585"
env_file:
- .env
environment:
- BOT_MODE=gateway
- GATEWAY_PORT=8585
- BRAIN_PATH=/app/data/brain
volumes:
- gateway-data:/app/data
networks:
- default
- proxy-net
logging:
driver: json-file
options:
max-size: 10m
max-file: 3
volumes:
gateway-data:
networks:
proxy-net:
external: true

185
docs/approval-flow.md Normal file
View File

@@ -0,0 +1,185 @@
# Gravity Bridge — 승인 시스템 완전 Flow 가이드
> **Last Updated**: 2026-03-16 (v0.3.12)
> **SSOT**: 이 문서는 승인 시스템의 전체 데이터 플로우와 상태 관리를 설명합니다.
> **수정 시**: known-issues.md와 동기화 필수
---
## 1. 시스템 아키텍처 개요
```
AG IDE (Antigravity)
├── Extension (extension.ts) ← Bridge 핵심
│ ├── setupMonitor() ← 5초 폴링 (GetAllCascadeTrajectories)
│ ├── step_probe ← WAITING step 감지 (GetCascadeTrajectorySteps)
│ ├── DOM Observer ← 렌더러 스크립트 (버튼 감지)
│ ├── processResponseFile() ← Discord 응답 처리
│ ├── writePendingApproval() ← pending 파일 생성 (dedup 포함)
│ └── tryApprovalStrategies() ← RPC 실행
├── bridge/ (파일 시스템)
│ ├── pending/*.json ← 승인 대기 목록
│ ├── response/*.json ← Discord 응답
│ ├── snapshot/*.json ← 채팅 릴레이
│ └── register/*.json ← 세션-프로젝트 매핑
└── Bot (bot.py) ← Discord 통신
├── pending_approval_scanner ← 3초 폴링
├── auto_approve_scanner ← !auto 토글
└── snapshot_scanner ← 채팅 릴레이
```
---
## 2. 데이터 플로우: 승인 요청 → 응답
### 2.1 Pending 생성 경로 (2개)
#### 경로 A: Step Probe → `writePendingApproval()`
```
1. setupMonitor() 5초 폴링 → GetAllCascadeTrajectories
2. RUNNING + delta=0 + modTime 미변경 → stall 감지
3. consecutiveIdleCount >= 1 && !stallProbed
4. GetCascadeTrajectorySteps → WAITING step 발견
5. si !== lastPendingStepIndex 확인 (dedup)
6. writePendingApproval() 호출
├── recentPendingSteps 메모리 dedup 체크 (60초 TTL)
├── 기존 pending 파일 dedup 체크 (15초 윈도우)
└── pending 파일 생성 + recentPendingSteps에 기록
7. stallProbed = true, lastPendingStepIndex = si
```
#### 경로 B: DOM Observer → HTTP POST `/pending`
```
1. 렌더러 MutationObserver → 버튼 감지 (Run, Accept, Allow 등)
2. FALSE_POSITIVE_RE 필터 (Proceed, Continue, Deny 등 차단)
3. "Run"은 sessionStalled=true && lastPendingStepIndex < 0 일 때만 통과
4. HTTP POST /pending → Extension HTTP 핸들러 (L738-812)
5. 파일 직접 생성 (writePendingApproval() 우회!)
⚠️ recentPendingSteps 메모리 dedup 미적용
```
> **주의**: 경로 B는 `writePendingApproval()`의 메모리 dedup을 우회합니다. 하지만 `lastPendingStepIndex >= 0`일 때 "Run" 필터(L757)와 15초 파일 기반 dedup이 방어합니다.
### 2.2 Response 처리 경로
```
1. Bot pending_approval_scanner → pending 파일 발견
2. auto-approve OR Discord 버튼 → write_response() 호출
├── response/*.json 생성
└── pending/*.json 삭제 (!)
3. Extension response watcher (fs.watch + 3초 폴링 fallback)
→ processResponseFile() (300ms 딜레이)
4. processResponseFile():
├── 파일 존재 확인 (HTTP handler가 먼저 삭제했을 수 있음)
├── stale timeout 필터 (2분)
├── auto_resolved/expired 상태 skip
├── project_name 필터
└── tryApprovalStrategies() → RPC 실행
5. sawRunningAfterPending = true (v0.3.12 핵심 수정)
6. response 파일 삭제 (DOM observer 경로는 HTTP handler에 위임)
```
---
## 3. 상태 변수 완전 참조
### 3.1 모듈 레벨 변수 (extension.ts)
| 변수 | 위치 | 역할 | 설정 | 리셋 |
|------|------|------|------|------|
| `lastPendingStepIndex` | L707 | 마지막으로 pending을 생성한 step index | step_probe(L2047,2108), error_probe(L2178) | delta>0(L1972), session change(L1841) |
| `stallProbed` | L708 | 현재 stall에서 probe 완료 여부 | step_probe(L2046,2107,2177) | delta>0(L1980), modTime changed(L1986), session change(L1842) |
| `sawRunningAfterPending` | L709 | pending 후 delta>0 발생 여부 (auto_resolve gate) | delta>0(L1979), **processResponseFile(L2622)** | step_probe(L2049,2110) |
| `sessionStalled` | L706 | AG가 stall 상태인지 | idle count≥1(L1993) | delta>0(L1937), not WAITING(L2135) |
| `recentPendingSteps` | L54 | 메모리 기반 pending dedup Map | writePendingApproval(L2787,2837) | delta>0(L1974), TTL 60초 |
### 3.2 setupMonitor() 로컬 변수
| 변수 | 역할 |
|------|------|
| `consecutiveIdleCount` | 연속 idle poll 수 (stall 감지 debounce) |
| `lastPendingTime` | 마지막 pending 생성 시간 |
| `lastModTime` | 마지막 modifiedTime (thinking vs approval 구분) |
| `wasRunning` | RUNNING→IDLE 전이 추적 |
| `lastResponseCaptureStep` | 응답 캡처 dedup |
---
## 4. 핵심 상태 전이 다이어그램
```
[IDLE] ──step진행(delta>0)──→ [RUNNING]
delta=0 + modTime 변동 → [THINKING] (stall 카운터 리셋)
delta=0 + modTime 고정 → [STALLED]
!stallProbed → step_probe 실행
WAITING 발견 → [PENDING_CREATED]
(stallProbed=true, lastPendingStepIndex=si)
┌──────────────────────────────────┤
▼ ▼
[DISCORD_APPROVED] [AG_LOCAL_APPROVED]
processResponseFile() delta > 0 + !sawRunningAfterPending
sawRunningAfterPending=true → auto_resolve → Discord 알림
│ │
└──────────────────────────────────┘
[STEP_PROGRESSED]
delta > 0 → 전체 리셋
lastPendingStepIndex = -1
stallProbed = false
sawRunningAfterPending = true
recentPendingSteps 클리어
```
---
## 5. v0.3.12 수정 — 왜 `sawRunningAfterPending = true`인가
### 5.1 이전 문제: 무한 루프 (v0.3.11 이전)
processResponseFile이 `lastPendingStepIndex = -1`로 리셋 → step_probe가 같은 WAITING step 재감지 → 새 pending → auto-approve → response → 다시 리셋 → **무한 루프**
### 5.2 v0.3.11 시도: 모든 리셋 제거
`lastPendingStepIndex``stallProbed` 리셋을 완전 제거 → **무한 루프 해소**, 하지만:
- known-issues L479 회귀: Discord 승인 후 AG 진행 시 `sawRunningAfterPending=false` + `lastPendingStepIndex>=0` → auto_resolve 중복 알림
- `stallProbed` 영구 잠금 우려 (실제로는 delta>0에서 자연 리셋)
### 5.3 v0.3.12 해결: `sawRunningAfterPending = true`
Discord 승인 response 처리 후 `sawRunningAfterPending = true`만 설정:
1. ✅ 무한 루프 방지: `lastPendingStepIndex` 유지 → dedup 작동
2. ✅ auto_resolve 중복 방지: `sawRunningAfterPending = true` → L1939 조건 FALSE
3. ✅ stallProbed 자연 리셋: delta>0에서 L1980
4. ✅ 신호 수집 무영향: step_probe, GetAllCascadeTrajectories 코드 미변경
---
## 6. 위험 지점 목록 (수정 시 반드시 확인)
| 코드 위치 | 위험 | 확인 사항 |
|-----------|------|----------|
| processResponseFile 리셋 (L2607+) | 무한 루프 or auto_resolve 중복 | `sawRunningAfterPending = true`만 설정. `lastPendingStepIndex``stallProbed`는 건드리지 말 것 |
| HTTP POST /pending (L738-812) | DOM observer 경로가 writePendingApproval() 우회 | "Run" 필터(L757)와 파일 기반 dedup이 방어 |
| bridge.py write_response (L460-461) | pending 파일 삭제 | 메모리 dedup(recentPendingSteps)이 재생성 방지 |
| auto_resolve (L1939-1977) | 중복 알림 | `sawRunningAfterPending` gate 확인 |
| step_probe offset (L2025-2070) | 775-step 리밋 | stepOffset으로 최신 step 조회 |
| session change (L1832-1854) | 모든 상태 초기화 | lastPendingStepIndex, stallProbed, sawRunningAfterPending 모두 리셋 |
---
## 7. 과거 이슈 교차 참조
| 이슈 (known-issues.md) | 방어 코드 | 상태 |
|------------------------|----------|------|
| L252: 중복 승인 요청 | writePendingApproval dedup (15초 윈도우 + 메모리 dedup) | ✅ 해결 |
| L264: pending 무한 누적 | write_response()에서 삭제 + 5분 age filter | ✅ 해결 |
| L288: DOM observer ENOENT | isDomObserver 분기 삭제 (L2619) | ✅ 해결 |
| L384: 크로스 프로젝트 MERGE | project_name 가드 (L2774) | ✅ 해결 |
| L444: DEDUP 크로스 세션 | conversation_id 가드 (L2794) | ✅ 해결 |
| L474-479: auto_resolve 중복 | `sawRunningAfterPending = true` (v0.3.12) | ✅ 해결 |
| L493: Double-Fire auto-approve | Extension auto-approve 경로 제거, Bot 단일 경로 | ✅ 해결 |
| L499: Deny false positive | FALSE_POSITIVE_RE + Bot reject guard | ✅ 해결 |

View File

@@ -10,4 +10,7 @@
| 006 | 19:38~19:56 | V8 CachedData 진단 + 캐시 삭제 (renderer 미실행 근본 원인) | docs only | ✅ | | 006 | 19:38~19:56 | V8 CachedData 진단 + 캐시 삭제 (renderer 미실행 근본 원인) | docs only | ✅ |
| 007 | 20:04~20:28 | CSP script-src `'unsafe-inline'` 패치 (renderer 미실행 진짜 근본 원인) | `08077e8` | ✅ | | 007 | 20:04~20:28 | CSP script-src `'unsafe-inline'` 패치 (renderer 미실행 진짜 근본 원인) | `08077e8` | ✅ |
| 008 | 21:00~21:30 | **E2E 승인 플로우 성공 검증** — AG 재시작 후 renderer v3 실행 확인 + Discord 승인→명령 실행 | `520d36e` | ✅ | | 008 | 21:00~21:30 | **E2E 승인 플로우 성공 검증** — AG 재시작 후 renderer v3 실행 확인 + Discord 승인→명령 실행 | `520d36e` | ✅ |
| 009 | 21:33~22:28 | 승인 플로우 튜닝 — dedup + 텍스트 정제 + stall fallback 제거 + reject 안전화 | `18b3734` | 🔧 | | 009 | 21:33~22:28 | 승인 플로우 튜닝 — dedup + 텍스트 정제 + stall fallback 제거 + reject 안전화 | `18b3734` | |
| 010 | 22:38~23:10 | E2E 검증 + Retry/Dismiss/Reject all 버튼 패턴 추가 + V8 캐시 삭제 | `4ba65f9` | ✅ |
| 011 | 23:11~23:20 | agent_guide 템플릿 통합 — 워크플로우 교체 + 플레이스홀더 적용 + 중복 helper 정리 | `4ba65f9` | ✅ |
| 012 | 23:30~00:31 | 승인 플로우 안정화 — pending 누적/false positive/MERGE dedup/auto_resolve/timeout | `` | 🔧 |

19
docs/devlog/2026-03-10.md Normal file
View File

@@ -0,0 +1,19 @@
# 2026-03-10 Devlog
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|---|------|----------|------|------|
| 001 | 06:12~06:30 | Discord 승인 ENOENT race condition 수정 + 버튼 그룹화 (multi-choice) | `aab1cfb` | 🔧 |
| 002 | 21:00~02:30 | 승인 메시지 전문 표시 + 연속 승인 감지 + file_permission scope 라우팅 | `75a3482`~`c9b4fd4` | ✅ |
| 003 | 12:30~13:30 | 3버튼 UI + Run 중복 필터 + dedup + 인자 값 표시 | `14d2acf`~`47dbd38` | ✅ |
| 004 | 13:30~13:55 | auto_resolved 동기화 + expired 카드 업데이트 + DOM step_index | `048ffd9` | ✅ |
| 005 | 13:55~14:10 | #253 전체 대화 릴레이 — 사용자 메시지 + 에러 알림 | `17dd665`~`b500120` | ✅ |
| 006 | 14:00~15:00 | Discord 에코필터 + 리로드 재전송 방지 + diff review 알림 | `82b727a`~`8fbf6bf` | ✅ |
| 007 | 15:00~15:55 | step_type 패스스루 체인 수정 + file_permission 자동감지 | `7982263`~`d1586c5` | ✅ |
| 008 | 16:45~17:20 | Single active project lock + stale REJECT 필터 + Vikunja 태스크 정리 | `186875a`~`95d4f85` | ✅ |
| 009 | 17:20~17:47 | v0.3.6 릴리스 — VSIX 빌드 + start_bot.bat 런처 | `bd46bea` | ✅ |
| 010 | 18:00~18:30 | v0.3.7 — file_permission 3-button 주입 + active_project.lock 제거 (멀티프로젝트) | `27deb2a` | ✅ |
| 011 | 18:50~19:29 | v0.3.8 — workspace URI 기반 세션 필터링 (멀티프로젝트 격리 완성) | `ae91134` | ✅ |
| 012 | 19:30~20:35 | 크로스 프로젝트 response watcher 우회 수정 + file_permission write 도구 3-button 매핑 | `3b834e0` | ✅ |
| 013 | 21:04~22:19 | Deriva 신호 진단 + RUNNING 세션 우선 선택 + IDLE 채널 자동 생성 제거 | `6179c4d` | ✅ |
| 014 | 22:23~22:47 | SDK LS 프로세스 대소문자 매칭 버그 수정 — variet-agent 신호 미도달 해결 | `21fd309` | ✅ |
| 015 | 23:46~23:57 | v0.3.9 — SDK JS 파일 VSIX 미포함 수정 + start_bot.bat Python 경로 우선순위 | `71aa80d` | ✅ |

14
docs/devlog/2026-03-11.md Normal file
View File

@@ -0,0 +1,14 @@
# 2026-03-11 Devlog
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|---|------|----------|------|------|
| 001 | 00:00~00:20 | Discord 릴레이 미작동 진단 — config.py BRAIN_PATH 빈문자열 버그 수정 | `pending` | ✅ |
| 002 | 00:20~01:05 | 크로스 프로젝트 pending DEDUP MERGE 버그 진단 및 수정 (project_name 가드 3곳) | `pending` | ✅ |
| 003 | 09:25~09:33 | Auto-approve 기능 감사 (미구현 확인) + Vikunja 태스크 #304, #305 등록 | `pending` | ✅ |
| 004 | 10:00~10:35 | P1: `!auto` 토글 자동 승인 구현 (bot.py + extension.ts) | `pending` | ✅ |
| 005 | 10:35~10:45 | P2: BridgeTransport 추상화 (bridge.py 리팩토링 + config/main 모드 설정) | `pending` | ✅ |
| 006 | 10:43~10:55 | 사용 가이드 작성 (docs/usage-guide.md) + tech-stack.md Python 경로 기록 | `c130399` | ✅ |
| 007 | 19:28~19:35 | Gateway HTTP API + Docker (Dockerfile, docker-compose, Caddyfile) | `6dbbb57` | ✅ |
| 008 | 19:35~19:50 | Gateway 보안: API Key 인증 미들웨어 + Caddy HTTPS + .env.example | `95da3e9` | ✅ |
| 009 | 19:50~20:10 | RemoteTransport + CollectorBridge 구현 — Collector↔Gateway HTTP 통신 | `95c2905` | ✅ |
| 010 | 21:30~23:48 | 아키텍처 감사: aiohttp 전환 + 보안 + 기능 누락 수정 + 나노 검증 | `d7ed454` | ✅ |

View File

@@ -0,0 +1,9 @@
# 2026-03-12 Devlog
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|---|------|----------|------|------|
| 001 | 00:34~00:47 | 429 Rate Limit 무한 루프 디버깅 — 지수 백오프 + rate limit 완화 + Collector 폴링 보호 | `d9b36cf` | ✅ |
| 002 | 16:45~17:04 | workbench.html 0-byte 파괴 복구 — 멀티 인스턴스 race condition 방지 안전 가드 추가 | `a9feee6` | ✅ |
| 003 | 17:10~17:55 | workbench.html 크로스 복원 CSS 깨짐 수정 — pre-patch backup + requiredMarker 구조 검증 + .orig 자동 복원 | `6d8c6f1` | ✅ |
| 004 | 19:46~21:13 | Collector 멀티 프로젝트 command 폴링 버그 수정 + rate limit burst throttle | `ae51d28` `bcc29f9` | ✅ |
| 005 | 22:12~22:58 | Rate limit 구조적 수정 — 점진적 백오프 + adaptive 폴링 + burst-friendly 윈도우 + stale pending 정리 | `56de714` | 🔧 |

View File

@@ -0,0 +1,6 @@
# Devlog — 2026-03-13
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 001 | 08:56 | Discord 아티팩트 알림 개선 — truncation 확대, 파일 첨부 전송, 동적 .md 감시 | `e5a05e3` | ✅ |
| 002 | 19:53 | Collector 성능 최적화 — mtime 프리체크, 프로젝트 캐시, re-forward 수정, 폴링 간격 조정 | `d4a2016` | ✅ |

13
docs/devlog/2026-03-15.md Normal file
View File

@@ -0,0 +1,13 @@
# 2026-03-15 Devlog
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 001 | 07:00~08:16 | 승인 신호 누락 진단 & 5건 버그 수정 (DEDUP collision, fs.watch fail, default 보호, auto 확인, msg dedup) | `40e3cd5` | ✅ |
| 002 | 08:25~08:31 | Extension v0.3.10 버전 범프 & VSIX 빌드 | `10caae1` | ✅ |
| 003 | 10:00~10:41 | 승인 라이프사이클 race condition 4건 수정 (HTML lock, pending status skip, auto_resolve Discord 알림, Bot approval_messages) | `f962036` | ✅ |
| 004 | 10:41~10:53 | 성능 최적화 3건 (pollResponseGroup 1500ms, renderer adaptive idle, Bot single-pass scanner) + VSIX 빌드 | `ae0509f` | ✅ |
| 005 | 15:17~17:09 | 크로스 프로젝트 신호 오염 진단 & 승인 플로우 아키텍처 수정 — DEDUP project_name 가드, double-fire auto-approve 제거, 실패 RPC 전략 30+개 삭제 (v0.3.11) | `6739f8f` | ✅ |
| 006 | 18:32~18:51 | Auto-approve 크래시 수정 — DOM Observer Deny false positive 필터 + Bot reject-word 가드 + AGENT.md 규칙 #10 추가 | `5e5f515` | ✅ |
| 007 | 22:00~22:52 | 시스템 전체 감사 + 5개 파일 버그 수정 (PATS Deny 트리거 제거, auto_resolved 채팅 병합, UUID 파일명 충돌방지, IP rate limit 누수, bot.py deque) + VSIX 빌드/배포 | `c9f44af` | ✅ |
| 008 | 23:18~23:27 | AGENT.md 로컬적 사고 방지 규칙 추가 — NEVER #10 강화(반증 의무), NEVER #11(기계적 적용 금지), ALWAYS #9(프로젝트 이력 교차 참조), Bug Report Protocol 분리 | `9b93ee9` | ✅ |

12
docs/devlog/2026-03-16.md Normal file
View File

@@ -0,0 +1,12 @@
# 2026-03-16 Devlog
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 001 | 07:30~11:10 | 승인 상태 관리 근본 원인 분석 + v0.3.12 수정 (sawRunningAfterPending gate) + approval-flow.md 시스템 Flow 문서 + known-issues 2건 추가 | `2d9fe96` | ✅ |
| 002 | 13:25~14:20 | diff_review 핸들러 2-strategy 리팩토링 + 배포 불일치 발견/수정 + pending 순서 8초 지연 + 1차 테스트 (버튼 OK, RPC 미배포→재배포) + known-issues 2건 | `f302984` | ✅ |
| 003 | 15:18~16:55 | diff_review steps=[] 근본 원인 분석 + 인메모리 캐시 (v0.3.13) + 3차 E2E (RPC SUCCESS but no-op) + 4가지 파라미터 실험 배포 | `00b9491` | ✅ |
| 004 | 17:05~18:00 | AG 소스 역분석 — `AcknowledgeCascadeCodeEdit``acknowledgeCodeActionStep` 메서드명 오류 발견 + v0.3.14 3단계 전략 배포 + known-issues 2건 업데이트 | `5a1d4f0` | ✅ |
| 005 | 18:13~18:43 | v0.3.14 E2E 테스트 → RPC 3개 전략 모두 실패 확인 + v0.3.15 agentAcceptAllInFile 전환 배포 + known-issues 업데이트 | `0fdf668` | ✅ |
| 006 | 18:47~19:09 | v0.3.15 diff_review E2E 2회 성공 + 이중 승인 수정 + IDLE 종료 알림 + !auto 이중 메시지 수정 (v0.3.16) + known-issues 2건 | `3cd7122` | ✅ |
| 007 | 19:17~20:38 | Discord 알림 누락 디버깅 — Bot snapshot 로깅 추가 + 병렬 WAITING step break 제거 + 서버 Docker 재배포 3회 + known-issues 2건 | `7f079a5` | ✅ |
| 008 | 20:50~23:06 | 크로스 프로젝트 알림 폭주 + pending 139개 누적 + diff_review brain/ 거짓양성 — 근본 원인 6건 분석 + Watcher 프로젝트 필터 + Collector stale 정리 + Extension brain/ 제외 + known-issues 3건 | `e3f8fb9` | ✅ |

28
docs/devlog/2026-03-17.md Normal file
View File

@@ -0,0 +1,28 @@
# Devlog — 2026-03-17
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 009 | 00:00~06:38 | Extension 모듈 분리 + Hub 통합 테스트 + VSIX v0.4.0 빌드 | `5f795b9` | ✅ |
| 010 | 06:50~07:39 | 문서 전면 재작성 + 서버 배포 + WS 호환 수정 | `6ea3211` | ✅ |
| 011 | 07:44~08:18 | VSIX v0.4.0 E2E 사전 검증 + WS 프록시 수정 | — | 🔧 |
| 012 | 09:00~17:44 | VSIX E2E: workspaceUri, 이중발송, ApprovalRequest, ApprovalView WS, 응답 라우팅 | `2eea5fa` | ✅ |
| 013 | 18:05~18:45 | Extension 모듈 분리 #398: http-bridge, html-patcher, command-handler 추출 (1296→650줄) | `6640d42` | ✅ |
| 014 | 18:45~20:35 | WS+File dual-delivery 수정 + 에코 릴레이 수정 + VSIX v0.4.4 빌드 | `0da6291` | ✅ |
| 015 | 20:45~21:00 | Accept All WS regression 수정 + auto_approve 이중쓰기 수정 + VSIX v0.4.5 | `47cc838` | ✅ |
| 016 | 21:00~21:27 | 통신 아키텍처 나노단위 감사: writeRegistration 이중쓰기 + ApprovalView fallback + scanner 최적화 | — | ✅ |
| 017 | 21:35~21:53 | Hub pending_owners 생명주기 수정: WS 재연결 시 승인 응답 소실 방지 (reconnect reassign + fallback routing) | `9ccfa83` | ✅ |
### #010 상세
- **문서**: architecture.md(250줄), tech-stack.md(100줄), conventions.md(100줄) 전면 재작성 + Wiki 동기화
- **태스크 정리**: #296 폐기, #396~#400 신규 5건 등록
- **서버 배포**: docker-compose.yml 서버 실제 구성 반영, Caddyfile 제거, ag.variet.net 도메인 확인
- **WS 호환**: ws-client.ts 브라우저 WebSocket API 호환 (.onopen/.onmessage) 수정
- **Known issue**: VS Code 캐시로 Extension 코드 반영 지연 — 완전 재시작 필요
### #011 상세
- **WS 프록시 수정**: NPM(openresty)에서 WebSocket Support 활성화 → 101 Switching Protocols 확인
- **WS 인증 검증**: `wss://ag.variet.net/ws` → auth_ok, conn_id 발급, instance=#1 확인
- **VSIX 설치**: v0.4.0 설치 확인, v0.3.16 제거, ws 모듈 수동 복사
- **AG 설정**: `settings.json`에 hubUrl + registrationCode 설정
- **ws 번들**: `.vscodeignore``!node_modules/ws/**` 추가, `package.json`에 ws dependency
- **미완료**: AG 재시작 후 Extension→Hub→Bot→Discord 실제 E2E 검증 필요

View File

@@ -0,0 +1,8 @@
# 2026-03-18 Devlog
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 1 | 11:00 | v0.5.0 Collector 제거 + dead code 정리 + HttpBridgeContext 버그 수정 | `e763117` | ✅ |
| 2 | 14:00 | bot.py unit tests 27건 — _write_command, _hub_on_pending, ApprovalView | `a41062b` | ✅ |
| 3 | 14:30 | step-probe.ts 모듈 분리 → approval-handler.ts (1597→1017+411줄) + dead code 제거 | `17978a7` | ✅ |
| 4 | 15:30 | 코드베이스 건강도 분석 + 통신 레이어 전수 감사 (8파일/7메시지타입) → 수정 필요 0건 | — | ✅ |

View File

@@ -0,0 +1,6 @@
# 2026-03-19 Devlog
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 1 | 07:30 | v0.5.1 browser_subagent Allow RPC 매핑 수정 + .env 정리 | `549af6d` | ✅ |
| 2 | 10:35 | v0.5.2 Idle→Resume 신호 소실 3중 버그 수정: auth_fail 재연결, pending_owners 보존, step-probe 리셋 | `5aad82c` | ✅ |

View File

@@ -0,0 +1,6 @@
# 2026-03-21 Devlog
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 1 | 17:48 | v0.5.3~v0.5.4 신호 감지 3중 버그 수정: 세션 전환 즉시 probe (20-25s→5s), reviewAbsoluteUris 필드 수정, stepIndex=-1 uint32 에러 수정 + permission 매핑 | `0fb33a9` | ✅ |
| 2 | 21:14 | v0.5.5 wrong-LS 자동 복구: Deriva RPC "input not registered" 근본 원인 분석 → fixLSConnection export + single-LS 조기종료 제거 + approval-handler 자동 LS 재연결 + 1회 retry | `6234301` | ✅ |

View File

@@ -0,0 +1,5 @@
# 2026-03-22 Devlog
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 1 | 01:22 | VSIX v0.5.5 빌드 — package.json 버전 범프 + vsce package | `b81135d` | ✅ |

View File

@@ -0,0 +1,6 @@
# 2026-03-23 Devlog
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
|-----|-------|----------|-----------|-----------|
| 001 | 21:09 | WebSocket 좀비 커넥션 해결 및 통신망 메모리 누수 패치 | `ecebec3` | ✅ |
| 002 | 22:45 | Cross-Project DOM Observer Leakage 패치 및 포트 동적 디스커버리 적용 | `TBD` | ✅ |

View File

@@ -0,0 +1,7 @@
# 2026-03-24 Devlog
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
|-----|-------|----------|-----------|-----------|
| 001 | 07:05 | v0.5.6 좀비 커넥션 패치 회귀 오류 해결 (False Positive 끊김 방지를 위한 타임스탬프 검증 도입 v0.5.8) | `f13bcc8` | ✅ |
| 002 | 13:00 | DOM Observer VS Code 네이티브 알림 UI 캡처 블라인드 스팟 해결 (v0.5.9) | `7b6cd59` | ✅ |
| 003 | 18:14 | DOM Observer /trigger-click 렌더링 순서 오작동 및 False Positive 프리징 해결 (v0.5.10) | `101ec20` | ✅ |

View File

@@ -0,0 +1,5 @@
# 2026-03-25 Devlog
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
|-----|-------|----------|-----------|-----------|
| 001 | 07:15 | ws-client reconnect pacing 및 http-bridge 정규식 필터 완화로 Signal Drop 해결 (v0.5.10) | `pending` | ✅ |

View File

@@ -0,0 +1,5 @@
# Devlog — 2026-03-28
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 001 | 09:12 | guitar_score step-probe UTF-8 무한루프 수정 + approval stepIndex 보정 (v0.5.11) | `7bbd874` | ✅ #539 |

View File

@@ -0,0 +1,5 @@
# 2026-04-01 Devlog
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
|-------|-------|-----------|-------------|--------------|
| 001 | 18:22 | `step-probe` 10-Item Truncation/DoS 우회 (vsix v0.5.14) | `TBD` | ✅ |

View File

@@ -0,0 +1,8 @@
# 2026-04-08
| NNN | HH:MM | 작업 설명 | `커밋해시` | 상태 |
|---|---|---|---|---|
| 004 | 14:00 | SafeToAutoRun 알림 누락 복구 (v0.5.18) | `8f2a1b3` | ✅ |
| 005 | 16:30 | SafeToAutoRun pending skip으로 인한 데드락 원인 파악 및 롤백 | `13f13ee` | ✅ |
| 006 | 07:30 | SafeToAutoRun 데드락 완전 해결을 위한 Agnostic Bridge 도입 및 프리징 방어 (v0.5.20) | `임시해시` | ✅ |
| 007 | 17:57 | Gravity Bridge 안정화: 중복 알림(SafeToAutoRun) 제거 설계 확정 및 Discord 봇 캐시/로컬 LS 크로스매칭 증상 디버깅 완료 | \-\ | ✅ |

View File

@@ -0,0 +1,9 @@
# 2026-04-09
| NNN | HH:MM | 작업 설명 | `커밋해시` | 상태 |
|---|---|---|---|---|
| 001 | 21:55 | Agent UI Tailwind/Native 마이그레이션 대응 (DOM 옵저버 구조 개편) | `HEAD` | ✅ |
| 002 | 22:30 | Agent UI 버튼 무시 버그 긴급수정 (CodeLens 필터교정) | `HEAD` | ✅ |
| 003 | 23:15 | Native UI 아이콘 글루잉 대응 스캐너 픽스 (DOM Regex 매칭 강화) | `HEAD` | ✅ |
| 004 | 00:10 | Discord Signal Relay & Auto-Approve Body Null 버그 수정 (False Positive 차단) | "HEAD" | ✅ |
| 005 | 23:00 | fix: Resolve empty Discord embed body by populating detailed step-probe payload | \\ | ? |

View File

@@ -0,0 +1,4 @@
| NNN | 시간 | 작업 설명 | 커밋해시 | 완료여부 |
|---|---|---|---|---|
| 001 | 17:11 | step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스 | COMMITTING | ✅ |
| #613 | 21:10 | Bridge Relay AI Chat Body DOM extraction and template literal Regex fix | TBD | ✅ |

View File

@@ -0,0 +1,6 @@
# 2026-04-11
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
|-------|-------|----------|-----------|-----------|
| 001 | 13:04 | Pure 웹소켓 게이트웨이 완전 전환 및 레거시 파일 브릿지 통신코드 삭제 | `072f83b` | ✅ |
| 002 | 17:25 | Antigravity Observer 컨텍스트 추출 범위 제한 및 노이즈(UI/TypeScript 코드) 필터링, Discord 임베드 개선 | `70dc301` | ✅ |

View File

@@ -0,0 +1,7 @@
# 2026-04-12
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완/미 |
|-------|-------|----------|-----------|-----------|
| 001 | 06:12 | AG Native DOM 파싱 v7 전면 재설계 — data-testid/data-step-index 기반 step-aware 파서, UI 노이즈 차단 | `a4d7286` | 🔧 |
| 002 | 07:03 | AG Native 번들 역공학 분석 + V8 CachedData 삭제 — plannerResponse→Whi 렌더러 구조 확인, bot-color가 NUX용임 발견 | — | 🔧 |
| 003 | 07:37 | Observer v8 전면 개편 — conversation-view 의존 제거, body 전체 무조건 덤프(depth 15), 5s/15s/60s 자동 덤프, VSIX v0.5.37 설치 | `0e03b3a` | 🔧 |

View File

@@ -0,0 +1,7 @@
# 2026-04-13
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료여부 |
|-------|-------|----------|-----------|----------|
| 001 | 09:50 | Observer v8 검증 — Extension POLL 확인, HTML 패치 확인, V8 캐시 삭제(24MB), BEACON 미수신(AG 재시작 필요) | 없음 | 🔧 |
| 002 | 12:34 | DOM Observer 데이터 품질 검증 + UTF-8 인코딩 수정 + noise 필터 강화 (v0.5.39) | `pending` | ✅ |
| 003 | 19:26 | Observer v9: "Running N commands" 오인 수정 + DOM-climbing 컨텍스트 추출 + http-bridge 필터 완화 (v0.5.40) | `pending` | 🔧 |

View File

@@ -0,0 +1,5 @@
# 2026-04-14
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료여부 |
|-------|-------|----------|-----------|----------|
| 001 | 07:29 | Observer v9 E2E 검증 — BEACON/ping 동작 확인, pending POST 수신 확인, 컨텍스트 추출 실패 진단 (ctx="Always run"), v10-diag 진단 로깅 추가 (observer + http-bridge), V8 캐시 삭제(523MB) | `c1e61d8` | 🔧 |

View File

@@ -0,0 +1,6 @@
# 2026-04-15
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료? |
|-------|-------|----------|-----------|----------|
| 001 | 09:12 | PROMPT_ONLY_RE 근본원인 분석 및 수정 — Observer regex 이스케이핑(4중→2중 backslash) + http-bridge ellipsis prefix 지원, 16개 테스트 전체 통과, VSIX v0.5.45 빌드/배포 | `01539e9` | ✅ |
| 002 | 10:35 | Observer fallback 컨텍스트 추출 수정 — v0.5.45 VSIX 설치 누락 발견/수정 + v13 `_promptOnlySkipped` 플래그로 채팅/UI 텍스트 추출 차단 + bridge generic button 무조건 필터 (v0.5.46) | `b8cda27` | 🔧 |

11
docs/devlog/2026-04-16.md Normal file
View File

@@ -0,0 +1,11 @@
# 2026-04-16
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료? |
|-------|-------|----------|-----------|----------|
| 001 | 04:52 | v16 터미널 출력 필터 + v15 stale LS 자동복구 + heartbeat probe — stdout가 enrichedCmd로 채택되는 버그 수정, VSIX v0.5.50 빌드/배포 | `7ade31e` | 🔧 |
| 002 | 05:28 | AG Native AI 응답 Discord 미전달 근본원인 분석 + Observer v15 scanChatBodies 이중전략 (#conversation + .leading-relaxed.select-text) 구현, v0.5.51 배포 | `729875f` | 🔧 |
| 003 | 17:13 | Observer v15 E2E 코드 검증 — CSP/체크섬/문법/핸들러 전수 확인 OK. BEACON=0 원인: AG 미재시작(렌더러 HTML 캐시). v0.5.50/out에 v15 JS 직접 배포 | — | ✅ |
| 004 | 21:07 | Observer v16 CSS 추출 버그 수정 — `<style>` 태그 textContent가 AI 응답으로 Discord 전달. extractCleanStepText()에 style/script strip 추가, v0.5.52 배포 | `62ee081` | ✅ |
| 005 | 22:07 | Observer v17 Always run 자동승인 + Retry 릴레이 — "Always run" 브릿지 레벨 자동승인, Retry 버튼 Discord 전달, v0.5.53 배포 | `7dbf73a` | ✅ |
| 006 | 21:28 | AG Native <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Markdown <20><><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>Ʈ/<2F><>) <20><><EFBFBD><EFBFBD> <20><> User <20><>û <20>̼<EFBFBD><CCBC><EFBFBD> <20>м<EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ȹ<EFBFBD><C8B9> <20>ۼ<EFBFBD> (v0.5.54 <20><><EFBFBD><EFBFBD> <20><>) | ? | ?? |

View File

@@ -0,0 +1,5 @@
# 2026-04-17
| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료 |
|-------|-------|----------|-----------|----------|
| 001 | 08:05 | v18 Observer DOM->Markdown 파서 개선(<a> 파싱 포함) 및 노이즈 필터 부작용(코드 블럭 잘림 방지) 해결, User 구문 추출 연동, v0.5.56 배포 | `미정` | ✅ |

View File

@@ -0,0 +1,5 @@
# Devlog — 2026-04-18
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|---|------|----------|------|------|
| 001 | 09:20~23:50 | Retry auto-approve 흐름 복구 — WS response 파일 보존 (`_from_ws`), Observer 형제 탐색(sibling), thinking 블록 필터링 | `pending` | ✅ |

26
docs/devlog/2026-04-19.md Normal file
View File

@@ -0,0 +1,26 @@
# Devlog 2026-04-19
## 작업 인덱스
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 001 | 21:22 | v30-32 Observer 명령어 추출 안정화 (터미널 프롬프트 조기감지) | `bd5a7ca` | ✅ |
| 002 | 23:16 | v33 Accept all 자동승인 — diff review auto-approve | `6aea48e` | ✅ |
| 003 | 00:18 | v34 Accept all 이중 보장 — agentAcceptAllInFile 직접 호출 | `cf1352e` | ✅ |
| 004 | 00:34 | v35 code_edit 자동 Accept — step-probe 경로 | `2bf1eb4` | ✅ |
| 005 | 04:26 | v36 Accept all span 감지 — 근본 원인 발견 (button→span) | `e95e779` | ✅ |
| 006 | 04:34 | v37 openReviewChanges 선호출 — agentAcceptAllInFile 보조 | `3cc3442` | ✅ |
| 007 | 04:43 | v38 _from_ws 마커 추가 — Observer polling 실패 근본 수정 | `7c8891b` | ✅ |
## v0.5.103 — Accept all (Diff Review) 자동 승인 복구
### 근본 원인 (2가지)
1. **Observer 감지 실패**: AG UI가 "Accept all"을 `<button>`이 아닌 `<span class="cursor-pointer">`로 렌더링. Observer의 `allBtns` 선택자가 `button`만 스캔하여 미감지.
2. **Response 파일 race condition**: auto-approve response 파일에 `_from_ws: true` 마커 없음 → `processResponseFile`이 Observer보다 먼저 파일 삭제 → Observer polling 무한 실패.
### 검증 결과
- Observer ACCEPT-SCAN: `tag=SPAN cls=cursor-pointer txt=Accept all`
- `DETECTED diff_review: Accept all`
- `response served to renderer: ...approved=true` (이전 0건 → 7건) ✅
- Discord "자동 승인됨 Accept all" 표시 ✅
- 화면에서 "Accept all" 버튼 자동 소멸 확인 ✅

View File

@@ -0,0 +1,16 @@
# 승인 플로우 안정화 — pending 누적/MERGE dedup/false positive/auto_resolve
- **시간**: 2026-03-09 23:30 ~ 2026-03-10 00:31
- **Commit**: `72d718f`
- **Vikunja**: #276 → 미완료 (검증 필요)
## 결정 사항
- **MERGE vs. Skip**: step_probe가 DOM observer pending에 상세 명령어를 MERGE (이전: skip). DOM이 먼저 "Run" pending 생성 → step_probe가 10초 후 상세 정보로 UPDATE
- **봇 Deferred Sending**: 짧은 명령어(≤15자) 4 cycles(12s) 대기. 매 cycle re-read하여 MERGE 즉시 전송. MERGE 타이밍(~10s)보다 defer를 길게 설정
- **False Positive 필터**: renderer HTML 인라인 스크립트는 extension.js 배포로 안 바뀜 → HTTP POST /pending 핸들러에 서버사이드 필터 추가. `sessionStalled` 플래그로 "Run" 버튼 게이팅
- **auto_resolve**: delta>0 발생 시 pending을 "auto_resolved"로 마킹 → 봇이 Discord 메시지를 "✅ AG에서 직접 승인됨"으로 업데이트
## 미완료
- AG 재시작 후 E2E 검증 필요 (Vikunja #276)
- error recovery (Retry) 감지 실제 동작 확인 필요
- "Accept all" diff review bar 버튼 Discord 릴레이 미확인

View File

@@ -0,0 +1,14 @@
# Discord 승인 ENOENT 수정 + 버튼 그룹화 (multi-choice)
- **시간**: 2026-03-10 06:12 ~ 06:30
- **Commit**: `aab1cfb`
- **Vikunja**: #276 → 진행중 (E2E 검증 대기)
## 결정 사항
- **ENOENT 근본 원인**: extension.log에서 `[RESPONSE] error: ENOENT` 확인. `processResponseFile()`이 DOM observer response를 renderer가 polling하기 전에 삭제. DOM observer 경로만 response 보존, HTTP handler에서 삭제하도록 변경
- **버튼 그룹화**: `findButtonContainer()` + `collectSiblingButtons()`로 같은 컨테이너의 관련 버튼을 하나의 pending으로 묶음. `buttons` 배열 + `button_index` response로 Discord에서 원하는 버튼만 정확히 클릭
## 미완료
- AG 재시작 + V8 CachedData 삭제 후 E2E 검증 필요
- Run 승인 반복 테스트 (5회 이상)
- Allow Once / Allow This Conversation / Deny 3개 선택지 동시 출현 확인

View File

@@ -0,0 +1,30 @@
---
id: "20260310-002"
date: "2026-03-10"
session_start: "07:55"
session_end: "10:40"
tags: [approval-flow, discord-relay, proto-rpc, step-offset]
---
# 승인 플로우 완성 + Discord 실시간 릴레이
## 요약
Proto RPC 기반 승인 플로우의 치명적 버그 2건 수정 + Discord 실시간 AI 응답 릴레이 구현.
## 주요 성과
### 1. 자동 취소 버그 수정 (`2361aa7`)
- `ResolveOutstandingSteps` RPC 비활성화 (reject 시 AG 작업 전체 취소 방지)
### 2. 775-step API limit 해결 (`628b5ae`)
- `GetCascadeTrajectorySteps` proto에서 `step_offset` 파라미터 발견/활용
- 장시간 세션(step 527+)에서도 최신 WAITING step 감지
### 3. Discord 실시간 AI 응답 릴레이 (`e586bb6` ~ `2958bdc`)
- `plannerResponse.modifiedResponse` + `verbosity: 1` (DEBUG)
- RUNNING 중 `delta > 0` 마다 RT 캡쳐 → 중간 메시지도 Discord에 표시
- notify_user/task_boundary 스냅샷 동작 확인
## 다음 단계
- Allow Once / Allow This Conversation 파일 접근 승인
- Retry / 코드 편집 승인 테스트

View File

@@ -0,0 +1,44 @@
---
date: "2026-03-10"
seq: 3
title: "Discord 승인 플로우 개선 — 파일 권한 3버튼 UI + 경로 표시"
tags: [bridge, discord, approval, file-permission]
---
# Discord Bridge 승인 플로우 개선 (cont.)
## 요약
Discord 메시지 릴레이 및 승인 UX를 8개 커밋으로 개선.
## 변경사항
### 승인 메시지 텍스트 개선
- `verbosity=1` (DEBUG) 추가로 `argumentsJson` 포함 → 전체 명령어 표시
- 명령어 길이 제한 150→1500자로 확대
- EPHEMERAL 시스템 메시지 필터링 (PLANNER_RESPONSE만 릴레이)
### 연속 승인 감지
- `stallProbed`를 모듈 스코프로 이동, 승인 후 즉시 리셋
- `lastPendingStepIndex` 리셋으로 다음 WAITING step 즉시 감지
### 파일 권한 3버튼 UI
- DOM Observer에서 file_permission 감지 시 `buttons` 배열 자동 주입
- Discord에 3개 버튼 표시: Allow Once / Allow This Conversation / Deny
- `button_index` → scope 매핑: 0=Allow Once(1), 1=Conversation(2)
- 10초 dedup 윈도우로 동일 대화상자 중복 pending 방지
- 설명 텍스트에서 버튼 라벨 제거 (DenyAllow → 정리)
### 기타
- DOM Observer "Run" 중복 pending 필터링 (step_probe 존재 시 차단)
- 인자 값 표시: `DirectoryPath` 키 이름 → 실제 경로 값
- `TargetFile` 전체 경로 표시 (basename → fullpath)
## Commits
- `75a3482` 명령어 길이 확대 + EPHEMERAL 필터
- `857e101` step_probe verbosity=DEBUG
- `c612c37` stallProbed 모듈 스코프 + 리셋
- `c9b4fd4` file_permission scope 라우팅
- `14d2acf` 3버튼 UI
- `bec38f9` DOM Observer Run 중복 필터
- `e107b70` file_permission 10s dedup + 텍스트 정리
- `47dbd38` 실제 인자 값 표시

View File

@@ -0,0 +1,24 @@
# Diff Review Relay + step_type 패스스루 + file_permission 자동감지
- **시간**: 2026-03-10 14:00~15:55
- **Commit**: `c15b0f6`~`d1586c5`
## 결정 사항
### Diff Review 감지 방식: IDLE-time → 누적 추적
- 처음에는 RUNNING→IDLE 전환 시 마지막 10 step을 조회했으나, AI가 파일 수정 후 compile/deploy/commit 등을 계속 진행해서 IDLE 시점에 파일 수정 step이 범위 밖
- **해결**: 실시간 step scan에서 `TargetFile` in `argumentsJson`를 감지해 `pendingModifiedFiles[]`에 누적, IDLE 시 전체 목록으로 알림 생성 후 리셋
### step_type 패스스루 체인 (3중 버그)
- **1차**: `UserResponse` 모델만 step_type 추가, `ApprovalRequest`에는 없음 → bot의 known-fields 필터가 strip
- **2차**: bot의 기본 승인/거부 콜백에 step_type 누락 (multi-choice callback에만 있었음)
- **3차**: step_probe가 toolName을 그대로 step_type으로 설정 (view_file → 일반 승인)
- **해결**: `ApprovalRequest` + `UserResponse` 양쪽에 step_type 추가, 3개 기본 콜백에도 추가, step_probe에서 file-related tools 자동 감지
### Accept all 원격 실행 한계
- `agentAcceptAllInFile`은 AG의 prioritized 명령어 → Extension Host에서 실행 OK하나 UI 포커스 조건 불충족으로 효과 없음
- **보류**: 알림만 유지, 실제 Accept는 AG에서 직접 클릭
## 미완료
- Accept all/Reject all 원격 실행 — AG prioritized 명령어 제약
- Error retry 원격 실행 — 실제 에러 발생 시 테스트 예정

View File

@@ -0,0 +1,18 @@
# SDK LS 프로세스 대소문자 매칭 버그 수정
- **시간**: 2026-03-10 22:23 ~ 22:47
- **Commit**: `21fd309`
- **Vikunja**: 신규 생성 → done
## 결정 사항
- SDK `_findLSProcess()`의 버그를 SDK 자체 수정 대신 **extension 단에서 우회(fixLSConnection)**하기로 결정. SDK는 빌드된 패키지이므로 직접 수정 불가. `sdk.ls.setConnection()` API로 재연결.
- 각 AG 창이 **별도 LS 프로세스**를 가진다는 사실 확인 (`--workspace_id`로 구분). 기존에 "모든 AG가 하나의 LS를 공유한다"던 가정은 틀림.
## 근본 원인
- SDK hint: `desktop_variet_agent` (`.toLowerCase()` 적용)
- LS command line: `file_c_3A_Users_Certes_Desktop_variet_agent` (원본 대소문자)
- `String.includes()` 대소문자 구분 → `desktop``Desktop` → 매치 실패
- SDK fallback: `lines[0]` (첫 번째 LS = gravity_control) → wrong LS 연결
## 미완료
- AG 풀 재시작 후 E2E 검증 필요 (`[LS-FIX] ✅ Reconnected` 로그 확인)

View File

@@ -0,0 +1,31 @@
# 승인 신호 누락 진단 & 5건 버그 수정
- **시간**: 2026-03-15 07:00~08:16
- **Commit**: `40e3cd5`
## 결정 사항
### DEDUP conversation_id 가드
- `step_index` 만으로 중복 판정 → 크로스 세션 충돌 빈번
- `project_name`만으로 불충분 (같은 Extension이 여러 세션 관찰 가능)
- **`conversation_id`까지 비교**가 정확한 DEDUP 조건
### fs.watch 대신 polling
- Windows에서 `fs.watch` silent fail 확인 (실측 테스트)
- response watcher도 같은 이슈 있음 (known-issue [2026-03-11])
- **모든 watcher에 polling fallback 병행** 원칙 확립
### stallProbed 시간 기반 리셋 — 불채택
- 유저 의견: fix #1로 DEDUP 해결되면 자연스럽게 delta>0 → stallProbed 리셋
- 30초 리셋은 LS stale 시 불필요한 RPC 호출만 증가
- LS stale은 AG 내부 문제 → AG 재시작이 올바른 해결
## 5건 수정 요약
| # | 파일 | 수정 |
|---|------|------|
| 1 | extension.ts | DEDUP `conversation_id` 가드 |
| 2 | extension.ts | `projectName=default` pending 억제 |
| 3 | extension.ts | commands dir 폴링 fallback |
| 4 | extension.ts | auto 확인 chat_snapshot |
| 5 | bot.py | `on_message` 메시지 ID 중복 방지 |

View File

@@ -0,0 +1,41 @@
# DOM Observer 분석 + diff_review 수정 + 배포
- **시간**: 2026-03-16 13:25~14:20
- **Commit**: `f302984`
- **Vikunja**: #384 diff_review 원격 승인 테스트 → 진행중
## 분석 결과
extension.ts 3,166줄 전체를 5개 기능 영역으로 분류:
1. AG 정보 추출 (SDK, 세션 모니터, step probe) ~700줄
2. 승인 실행 (RPC) ~200줄
3. 파일 기반 IPC (리팩토링 대상) ~500줄
4. DOM Observer 인프라 (제거 검토) ~1,100줄
5. 기타 유틸 ~200줄
**DOM Observer는 제거 가능하다** — RPC가 모든 승인을 커버하며, diff_review도 VS Code 명령으로 처리 가능.
단, diff_review 원격 실행이 먼저 동작해야 DOM 제거에 동의 가능 (사용자 요구).
## 결정 사항
- write 도구를 `code_edit` step_type으로 분리 (기존: `file_permission`에 혼재)
- diff_review 핸들러를 2-strategy 방식으로 리팩토링:
1. `AcknowledgeCascadeCodeEdit` RPC (UI 조작 불필요)
2. `openReviewChanges` + 파일 포커스 + `agentAcceptAllInFile` (fallback)
- pending에 `modified_files`(전체경로)와 `edit_step_indices`(step 번호) 포함
- diff_review pending 생성을 8초 지연 (AI 응답이 먼저 Discord 도착)
## 테스트 결과 (1차)
- ✅ Discord에 Accept all / Reject all 버튼 표시 확인
- ❌ AcknowledgeCascadeCodeEdit RPC 미실행 — 소스 수정 후 컴파일+배포 누락 발견
- ❌ AI 응답이 승인 버튼보다 늦게 Discord에 도착
## 수정 및 배포
- Extension 재컴파일+배포 (2-strategy diff_review 핸들러 포함)
- diff_review pending 8초 지연 (`setTimeout`) 추가
- known-issues 2건 추가 (isDirty 실패, pending 순서)
## 미완료
- **AG 풀 재시작 후 2차 E2E 테스트** 필요
- AcknowledgeCascadeCodeEdit RPC 성공 확인
- AI 응답 → 승인 버튼 순서 확인
- DOM Observer 제거 리팩토링 → diff_review 동작 확인 후 진행

View File

@@ -0,0 +1,43 @@
# diff_review steps=[] 근본 원인 분석 + v0.3.13 수정
- **시간**: 2026-03-16 15:18~16:55
- **Commit**: `00b9491` → (후속 커밋 예정)
- **Vikunja**: #384 diff_review 원격 승인 테스트 → 🔧 진행중
## 분석 결과
2차 E2E 테스트에서 `AcknowledgeCascadeCodeEdit(steps=[])``SUCCESS: {}` 반환 확인.
RPC 자체는 성공하지만 빈 step 배열로 호출하면 no-op — AG의 diff review 바 유지됨.
**근본 원인 체인:**
1. Extension이 `writePendingApproval()`로 diff_review pending 생성 (edit_step_indices=[86] 포함)
2. Collector가 Gateway에 전달 후 로컬 pending 파일을 삭제 (collector.py L259)
3. Discord Accept 클릭 → Gateway → Collector가 response 파일 작성
4. Extension의 `processResponseFile`이 pending 파일에서 `edit_step_indices` 읽으려 함 → **파일 이미 없음**
5. `trackedSteps=[]``AcknowledgeCascadeCodeEdit(steps=[])` → no-op
## 수정 (v0.3.13)
- `diffReviewMetadata` 인메모리 Map 추가 (extension.ts L57)
- `writePendingApproval`에서 diff_review pending 생성 시 메타데이터를 메모리에 캐시
- `processResponseFile`에서 메모리 먼저 조회, pending 파일은 fallback
- `modifiedFiles` 변수를 Strategy 1/2 공유 스코프로 호이스팅
## 3차 E2E 테스트 (16:25~16:40)
- ✅ 인메모리 캐시 저장/로드 정상: `steps=[40]` → memory에서 로드
- ✅ RPC 호출 시 실제 step 인덱스 전달: `steps=[40]`, `steps=[101,114,123,126]`
-**RPC SUCCESS 반환하지만 AG diff review bar 미해제** — 실제 no-op
## 원인 분석 + 실험 (16:40~16:55)
RPC가 `SUCCESS: {}` 반환해도 unknown field를 무시하는 proto 동작일 가능성.
`stepIndices` 필드명이 AG 기대값과 다르거나, step index 값 자체가 불일치.
4가지 파라미터 형식 실험 코드 Extension에 배포:
- A: `stepIndices` (현재 방식)
- B: stepIndices 없이 (전체 accept)
- C: `steps` 키 (필드명 오류 가능성)
- D: `trajectoryId` 추가
→ AG 재시작 후 diff_review 트리거 시 각 형식의 결과 비교 예정

View File

@@ -0,0 +1,12 @@
# WebSocket 좀비 커넥션 해결 및 통신망 메모리 누수 구조 패치
- **시간**: 2026-03-23 21:09~21:20
- **Commit**: `ecebec3`
- **Vikunja**: #510 → done
## 결정 사항
- **ws-client.ts 핑퐁 와치독(Ping-Pong Watchdog)**: 단순 에러 캐치가 아니라 `ws.terminate()`를 통해 무반응 소켓을 강제 종료하여 자체 재연결 로직(`_onDisconnect`)을 활성화하도록 설계.
- **통신망 추적 변수 캡핑(Bounded Cap)**: `hub.py``pending_owners``bot.py``_sent_approval_ids` 등 무한히 쌓일 수 있는 파이썬 딕셔너리에 LRU(오래된 순 삭제) 로직을 추가. 비록 당장 OOM을 유발하진 않지만 이 구조적 메모리 누수(Leak)를 원천적으로 차단하여 시스템 안정성을 극대화함.
## 미완료
- 없음

View File

@@ -0,0 +1,16 @@
# Cross-Project DOM Observer Leakage 해결
- **시간**: 2026-03-23 22:00~22:45
- **Commit**: `TBD`
- **Vikunja**: #TBD → done
## 확인된 사실
- Discord 신호 누락이 아닌, 다중 원격 환경에서의 포트 덮어쓰기 문제로 인한 **교차 프로젝트 신호 오염(Leakage)**이었음.
## 삽질 / 트러블슈팅
- 처음에는 디스코드 봇(`bot.py`)이나 익스텐션의 `step_type` 매핑 로직 누락인 줄 알고 코드를 탐색했으나, 실제 DOM observer 스크립트에 하드코딩된 Port 변수가 문제의 원인임을 파악함.
- 다중 원격 컴퓨터 환경 중 포트 포워딩(`12345` 충돌 우회)으로 인한 이슈를 해결하기 위해 `vscode.env.asExternalUri`를 도입. 로컬에 매핑된 최종 확정 포트를 알아냄.
## 결정 사항
- DOM Status Bar(`tooltip`)를 일종의 단방향 IPC(Inter-Process Communication) 대용으로 사용하기로 결정함.
- Extension Host가 렌더러(DOM Observer)에게 안전하고 해당 창에만 격리(Window-isolated)된 방식으로 포트 번호를 전달할 수 있음. 전역 HTML 파일 패치의 한계를 우아하게 극복함.

View File

@@ -0,0 +1,12 @@
# v0.5.6 좀비 커넥션 패치 회귀 오류 해결 (v0.5.8 반영)
- **시간**: 2026-03-23 23:10 ~ 2026-03-24 07:05
- **Commit**: `TBD`
- **Vikunja**: 신규 추가 예정
## 결정 사항
- **False Positive 멈춤 현상 원인 규명**: v0.5.6에서 추가된 `pongTimeoutTimer` (10초 타임아웃)가 VS Code 확장 내부의 일시적인 Event Loop 블로킹 발생 시 네트워크 I/O(`pong` 응답)보다 먼저 소켓을 강제 종료하고 있었습니다. 이 때문에 멀쩡한 연결이 끊어지고 재연결 지연 페널티가 누적되어 최대 60초까지 응답 불가(멈춤) 상태에 빠지는 현상이 발견되었습니다.
- **해결 방안 선택 (타임스탬프 검증)**: 타이머 동시성 경합을 유발하는 `setTimeout` 방식을 전면 폐기하고, 기존의 `setInterval` (25초 주기) 하트비트 루프 내부에서 `ws.on('pong')`이 갱신하는 `lastPongTime`을 대조(`Date.now() - lastPongTime > 60000`)하는 방식으로 변경했습니다. 이를 통해 Event Loop가 지연되더라도 I/O 이벤트를 먼저 수확한 후에 안전하게 판독할 수 있어 오진단(False Positive)을 원천 차단하면서도 좀비 커넥션을 방지했습니다.
## 미완료
- 없음 (v0.5.8 VSIX 컴파일 성공 및 배포 완료)

View File

@@ -0,0 +1,18 @@
# DOM Observer VS Code 네이티브 알림 UI 캡처 블라인드 스팟 해결 (v0.5.9)
- **시간**: 2026-03-24 12:00~13:00
- **Commit**: `7b6cd59`
- **Vikunja**: #514
## 결정 사항
- **문제**: "Always Allow" 및 "Allow Alt+↵" (단축키 포함) 권한 알람이 Discord로 전송되지 않는 문제가 발생했습니다. (v0.5.8)
- **근본 원인 확인**:
- Regex 실패: `Always Allow``^Allow` 정규식을 통과하지 못합니다.
- CSS Selector 실패: `observer-script.ts`의 스캔 엔진이 오직 `document.querySelectorAll('button')`에만 의존하여 렌더링 노드를 찾고 있었습니다. VS Code 네이티브 권한 프롬프트(토스트 알림 및 채팅 패널)는 `<a role="button" class="monaco-text-button">` 또는 `<vscode-button>`을 활용하므로 애초에 찾지도 못하고 스킵되었습니다.
- **해결책**:
1. `observer-script.ts` 내의 모든 DOM 쿼리를 `button, [role="button"], vscode-button, .monaco-text-button` 으로 확장.
2. 허용 권한 토큰 관련 정규식을 `/^(?:Always )?Allow/i` 로 상향 패치.
3. `v0.5.9` 로 빌드 및 VSIX 설치 완료 후 정상 동작 검증 완료.
## 미완료
- 없음.

View File

@@ -0,0 +1,18 @@
# DOM Observer /trigger-click 렌더링 순서 오작동 및 False Positive 프리징 해결 (v0.5.10)
- **시간**: 2026-03-24 17:50~18:20
- **Commit**: `HEAD` (예정)
- **Vikunja**: #514 관련 디버깅/핫픽스
## 결정 사항
- **문제**: "0.5.9 패치한 이후 화면이 펜딩되서 움직여지지않아" 라는 증상 확인.
- v0.5.9에서 DOM 쿼리를 `[role="button"]` 등으로 확장했으나, 정규식이 `/^Run/i` 등으로 풀어진 상태여서 에디터 뷰의 "Run Test" 등 수많은 CodeLens 버튼들을 Agent의 트리거로 오인함.
- 결과적으로 아무 조작도 하지 않았는데 계속 터미널 실행 대기상태(Pending)로 무한 진입하여 UI 화면이 프리징(Freeze)됨.
- 특히 디스코드에서 `Approve` 명령을 내렸을 때도, DOM 트리상 상단에 우연히 "Run" CodeLens가 있으면 먼저 캡처되어 진짜 Agent 패널의 버튼을 클릭하지 못하고 엉뚱한 요소를 클릭하는 위험한 순위 불일치 버그까지 있었음.
- **해결책 (Structural Context Filtering)**:
1. 감지(Scan): 단순 정규식을 빡빡하게 변경하면 동적인 버튼 이름("Run script" 등)이 안 먹히는 부작용이 있으므로 느슨함을 유지하되, **발생 영역(DOM Context)**에 강제 필터를 부여.
- `isVSCodeMainWindow` 및 노드 루트가 `document.body`인지를 체크하여, 에디터 본문 영역 안에서는 "Run", "Approve", "Accept" 캡처를 전부 무시.
2. 제어(Trigger-click 우선순위): `observer-script.ts``deepFindButtons()` 내부 스캔 트리를 변경하여 `findPanel()`로 안티그래비티 패널을 1순위로 조회, 알림 Toast를 2순위, 본문 Document를 3순위로 탐색하게 강제하여 엉뚱한 버튼 클릭 사고를 100% 방지함.
## 미완료
- 없음 (빌드 및 검증 완료)

View File

@@ -0,0 +1,11 @@
# step-probe Pagination 10-Item Truncation vs LS DoS 오류 수정
- **시간**: 2026-04-01 13:00~18:22
- **Commit**: `TBD`
- **Vikunja**: #N/A (임시 버그 픽스)
## 결정 사항
- 기존 `v0.5.13`에서 `limit: 100`으로 Pagination Limit(기본 10개)을 우회하려 했으나, LS DB 스캔 및 거대한 JSON 파싱이 VS Code Event Loop 블로킹을 유발하여 UI 멈춤(DoS) 발생.
- 롤백 과정에서 `{}`(인자 없음)으로 원복하면서 필수적인 `descending: true` 파라미터까지 누락됨.
- 이로 인해 `guitar_score` 등의 최신 작성 세션이 LS 조회 리밋(10)에서 밀려나 승인 신호를 수신하지 못하는 이슈 재발.
- 이를 해결하기 위해 `limit: 30, descending: true`로 설정. 파싱해야 할 JSON 객체 수를 1/3로 줄임과 동시에, 정렬 보장을 통해 최근 10초 이내에 활성화된 세션은 언제나 Index 0번 최상단에 고정되게끔 메커니즘을 수정함.

View File

@@ -0,0 +1,20 @@
# 2026-04-08 (004) - SafeToAutoRun 명령어의 디스코드 알림 누락 복구
## 1. 이슈 개요
- 사용자가 `/start` 등 백그라운드 명령어(SafeToAutoRun)를 포함한 워크플로우를 실행하였으나, 디스코드로 아무런 메시지도 전송되지 않는 버그가 보고됨.
## 2. 원인 분석
- v0.5.16 배포 당시 Discord 중복 알림(Pending 파일) 이슈를 방지하는 과정에서, `step-probe.ts`에 있던 "⚡ 자동 실행됨" 원본 알림 코드(snapshot 생성 로직)까지 실수로 함께 삭제됨.
- `SafeToAutoRun` 구문에서 `writePendingApproval` 스킵 로직은 잘 동작하고 있었으나, 정작 사용자에게 알려야 할 기본적인 '자동 실행됨' 정보마저 소실되어 결과적으로 아무 알림도 가지 않는 침묵 상태가 됨.
## 3. 해결 및 적용 사항
1. `step-probe.ts` 복구
- `SafeToAutoRun` 판단 시 `autoRunSteps`를 마킹한 직후 `ctx.writeChatSnapshot()`을 호출하도록 코드를 추가 복원함.
- 출력 구조: `💬 **자동 실행됨** (step N)\n\n\`명령어내용\``
2. **v0.5.18 배포**
- 익스텐션의 `package.json` 버전을 `0.5.18`로 펌핑.
- 사전 스크립트가 적용된 `vsce package`를 통해 새로운 `gravity-bridge-0.5.18.vsix` 패키징을 완료함.
## 4. Next Step
- `extension/gravity-bridge-0.5.18.vsix` 파일을 VS Code에 수동 설치할 것 (Install from VSIX...).
- 설치 후 반드시 **Reload Window**하여 테스트 수행 요망.

View File

@@ -0,0 +1,20 @@
# 2026-04-08 (005) - SafeToAutoRun 로컬 자동 승인 누락 데드락(Freeze) 해결
## 1. 이슈 개요
- 사용자가 확인 결과 v0.5.15 이후 백그라운드 터미널 명령어 등 모든 AI 에이전트 작업이 '자동 실행됨' 스냅샷만 보내고 VS Code 내부적으로는 여전히 승인(Allow)을 대기하며 완전히 멈춰버림(Freeze).
- 신호가 전달조차 안되고 다음 단계로 진행하지 못하는 심각한 블로커 이슈가 발생함.
## 2. 원인 분석
- v0.5.16 버그 픽스("Discord 중복 알림 방지") 당시 `SafeToAutoRun` 상태일 때 `writePendingApproval()`을 수행하지 않도록 코드(`skip pending`)를 변경했음.
- 그러나 과거에는 이 Pending 파일이 생성되면 파이썬 백엔드(Bot)가 디스코드에 알림을 띄운 직후, 자동으로 `approve`(허용) 신호를 익스텐션 쪽에 보내어 다음 단계가 허가되었음.
- 즉, 익스텐션에서 Pending 파일 생성을 중단(skip)하자 봇으로부터 수락 신호가 아예 오지 않게 되었고, VS Code의 보안 시스템에 의해 명령어는 영원히 "Run(Auto)" 클릭 승인을 대기하는 상태의 데드락에 빠져버림.
## 3. 해결 및 적용 사항
1. `step-probe.ts` 로컬 자동 승인 복구
- `safeToAutoRun` 판단으로 Pending 파일 생성을 건너뛸 때, 익스텐션 스스로 백그라운드 승인을 트리거하도록 `tryApprovalStrategies(true, ...)` 함수 호출 코드를 명시적으로 추가함.
- 이를 통해 봇의 승인 신호를 기다릴 필요 없이 즉각적으로 승인(Accept)을 단행하여 막힘없이 스텝이 연속 진행되도록 고침.
2. **v0.5.19 배포**
- VSIX 버전을 `0.5.19`로 펌핑 후 `npx vsce package` 명령으로 익스텐션을 재빌드함.
## 4. Next Step
- `extension/gravity-bridge-0.5.19.vsix` 파일을 수동 재설치하고 VS Code Window를 Reload 한 뒤, `/start` 같은 자동 워크플로우를 재실행하여 신호 블로킹(Freeze) 버그가 해결되었는지 최종 확인.

View File

@@ -0,0 +1,17 @@
# SafeToAutoRun 데드락 및 익스텐션 프리징 완벽 해결 (v0.5.20)
- **시간**: 2026-04-08 07:15~07:30
- **Commit**: `임시해시`
- **Vikunja**: #589 → done
## 발생 문제
1. **Deadlock**: 이전 버전(v0.5.15)에서 디스코드 알림을 줄이려고 익스텐션의 `step-probe.ts``SafeToAutoRun` 발생 시 `pending` 파일 생성 자체를 건너뛰도록 구현함. 하지만 AG 엔진은 CORTEX_STEP_STATUS_WAITING 상태에서 누군가가 해결해주기를 영원히 기다리게 되어, 파이프라인 전체가 데드락(UI 멈춤)에 빠지는 치명적인 부작용 발생.
2. **이벤트루프 Freeze**: `extension.ts``detectProjectName` 내부에서 동기식 `cp.execSync('git remote get-url origin')`를 실행하여 윈도우 환경에서 VS Code 이벤트루프가 막히고 WebSocket 통신이 유실되는 현상 발생.
## 결정 사항
- **Agnostic Bridge 철학 준수 (단일 경로 원칙 복구)**
- 익스텐션(`step-probe.ts`)은 절대 자의적으로 승인 처리를 하거나 `pending` 파일 생성을 스킵해서는 안 됨. 오직 브릿지 중계자 역할에 충실하도록 롤백하고, 대신 메타데이터에 `safe_to_auto_run: true` 속성을 실어 보냄.
- 파이썬 서버(`bot.py`) 관제탑이 이를 확인하면 디스코드에 알림(`Embed`)을 보내는 단계만 슬쩍 생략하고 그 즉시 허가증(`response/`)을 발급. 이를 통해 데드락 해제와 무소음 승인을 동시에 만족함.
- **비동기화 및 빌드 파이프라인 강제**
- 동기식 git 명령어 대신 비동기식 `.git/config` 파일 읽기로 교체.
- `package.json``vscode:prepublish` 스크립트를 부활시켜 낡은 소스코드가 VSIX에 패키징되는 문제 원천 차단.

View File

@@ -0,0 +1,18 @@
# Gravity Bridge 알림 최적화 및 연동 디버깅 완료
- **시간**: 2026-04-08 17:00~17:55
- **Commit**: `pending`
- **Vikunja**: 대상 작업 맵핑 예정
## 결정 사항
- `SafeToAutoRun``step-probe.ts`에서 날리던 **"⚡ 자동 실행됨"** 알림은 과감하게 완전히 제거하였습니다. 파이썬 봇이 이미 "🤖 자동 승인됨" Embed를 송출하고 있으므로 디자인 철학(익스텐션은 중립적인 릴레이 역할만 수행하고, 비즈니스 판정 알림은 중앙 봇이 담당)에 부합합니다.
- `extension.ts``writeChatSnapshot` 의존성을 줄여 트래픽 낭비와 중복 노이즈를 해소했습니다.
## 핵심 디버깅 (Troubleshooting)
- **`variet-llm` 프로젝트 연동 실패 이슈:**
1. `variet-llm` 창을 열었으나 채팅 패널을 열지 않아 안티그래비티 전용 언어 서버(LS)가 띄워져 있지 않은 상태에서 브릿지를 켬. 브릿지가 엉뚱하게 `gravity_control` LS에 바인딩 됨.
2. 사용자가 Discord에서 `variet-llm` 채널을 삭제해 버렸는데, 파이썬 봇(`bot.py`)은 캐시를 가지고 있어서 자신이 파괴된 채널을 대상으로 계속 통신을 시도하며 새 채널을 파지 않음 (HTTP 404).
3. 로컬 윈도우 재시작 및 도커(`docker-compose restart`) 컨테이너 재가동을 통해 **봇 프로세스 캐시 초기화** → 채널 자동 재생성, 완벽 디버깅에 성공했습니다.
## 미완료
- 없음. 모두 성공적으로 동작 중.

View File

@@ -0,0 +1,18 @@
# Agent UI Tailwind/Native 마이그레이션 대응 (DOM 옵저버 구조 개편)
- **시간**: 2026-04-09 19:40~21:55
- **Commit**: `[임시해시]`
- **Vikunja**: 신규 생성 후 완료 처리
## 트러블슈팅 및 결정 사항
최근 UI 업데이트 후 Discord 릴레이 신호(Run, Accept) 단절.
deep-inspect 덤프 분석 결과 Webview/Iframe 환경이 사라지고 Native DOM(VS Code 본문)에 напрямую 그려짐, 기존 시맨틱 클래스가 Tailwind로 변경.
1. 기존 `findPanel`이 패널을 못 찾자 `isBodyRoot` 모드로 스캔
2. 과거에 추가된 CodeLens 방어 로직(`if (isVSCodeMainWindow && isBodyRoot && PATS[p].type !== 'diff_review') continue;`)에 의해 모든 버튼 스캔이 **버려지고 있었음**.
**결정**:
엄격한 Panel Class Whitelist 기반 방어를 해제하고, 버튼이 `.monaco-editor` 내부에 있는 경우만 무시하도록 Blacklist 기반 방어로 선회.
UI 텍스트 글루잉(아이콘 통합) 대응 위해 패터닝 정규식을 `/^(?:Always\s*)?Run/i` 등으로 완화.
## 미완료
- 없음

View File

@@ -0,0 +1,15 @@
# Agent UI 버튼 무시(Discard) 버그 핫픽스
- **시간**: 2026-04-09 22:10~22:35
- **Commit**: `pending`
- **Vikunja**: 새로 생성 후 완료 예정
## 트러블슈팅 및 결정 사항
- **이슈**: Native UI 마이그레이션 직후 버튼을 눌러도 브릿지로 신호가 전혀 가지 않는 버그 접수
- **원인 분석**:
1. `extension.log` 확인 결과 `[HTTP] pending` 자체가 생성되지 않음 (브릿지 자체에 도달하지 않음).
2. DOM observer가 수집한 버튼이 `b.closest('.monaco-editor')` 필터 조건에 무조건 걸려서 버려지는 것이었음. Native 전환 후 채팅창이 에디터 탭 내부에 렌더링되면서 `.monaco-editor` 내부 자식이 됨.
- **결정**: 기존의 `b.closest('.monaco-editor')` 방어 로직을 폐기하고 실제 CodeLens 버튼 고유의 클래스 `.codelens-decoration`를 명시하도록 변경하여 구조 변화에 강건해지도록 개선 완료. `0.5.22` VSIX 재배포.
## 미완료
- 없음 (검증은 유저 몫으로 인계)

View File

@@ -0,0 +1,17 @@
# Agent UI Native 버튼 아이콘 글루잉 무시 현상 수정
- **시간**: 2026-04-09 23:00~23:15
- **Commit**: `TBD`
- **Vikunja**: 신규 생성 (UI 텍스트 글루잉 버튼 버그) → done
## 문제 상황
- 0.5.22 패치(CodeLens 필터) 이후에도 `Run`, `Accept` 버튼 클릭 시 디스코드 브릿지로 아무런 펜딩 요청(POST /pending)이 전송되지 않는 현상 발생.
- 원인 규명: Native UI 마이그레이션 적용 후, Agent 패널 버튼들의 아이콘(``, `▶` 등)이 리액트/Tailwind 컴포넌트 렌더링을 거쳐 `element.textContent` 상단에 문자열로 직접 병합(Gluing)됨.
- 옵저버 스크립트 내부 정규식(`/^(?:Always\s*)?Run/i`)이 문자열의 맨 첫(^) 시작을 강제하기 때문에, 아이콘으로 시작하는 버튼들의 명령어를 전부 오탐으로 간주함.
## 결정 사항
- 버튼의 텍스트를 읽는 즉시, `txt.replace(/^[^a-zA-Z0-9]+/, '')`를 적용하여 첫 글자가 영어/숫자가 될 때까지, 선행하는 모든 특수문자, 아이콘, 폰트 공백 등을 강제 삭제하도록 스크립트 내부의 3가지 탐색 루프 (본문 스캔, Sibling 버튼 수집, Webview trigger-click 인젝션)에 일괄 업데이트.
- 기존 `.monaco-editor``.chat-body` 등 부모 컨테이너에 지나치게 의존하던 `findButtonContainer``chat`, `prose`, `markdown`를 추가 화이트리스팅 하되 Tailwind UI 구조 특성상 시맨틱 래퍼를 찾지 못할 경우 3단계 위 부모를 반환하여 안전하게 컨텍스트를 확보하도록 고도화. -> **구조 변경 시에도 유연하게(Graceful) 기능 동작 지원 보장.**
## 결과
- `v0.5.23` (코드상 0.5.22 유지) VSIX 빌드 및 테스트 준비.

View File

@@ -0,0 +1,15 @@
# Discord Signal Relay & Auto-Approve Body Null 버그 수정 (False Positive 차단)
- **시간**: 2026-04-10 00:00~00:10
- **Commit**: `HEAD`
- **Vikunja**: #태스크번호 → done
## 트러블슈팅 & 삽질
- **DOM 정규식의 반란**: Native UI 패치 이후 버튼의 text-gluing 제거 때문에 정규식을 광범위하게 바꿨으나, 하필 `Run\s*` 조건에 단어 경계(`\b`)를 누락하는 바람에 VS Code 하단의 시스템 상태 버튼인 `Running 1 command`까지 AI의 `Run` 버튼으로 인식해버림. 무한 PENDING 스팸을 만들어 브릿지 큐를 폭파시킨 주범.
- **Auto-Approve 본문 누락**: 봇에서 자동 승인 Embed 생성 시 `req.description` (실행될 본문 코드)을 아예 그리지 않고 `req.command` (단순 버튼 라벨)만 출력하도록 코딩되어 있었음. 사용자는 '자동 승인' 알림을 받지만 정작 무엇이 승인되었는지는 전혀 알 수 없어 '본문 표시 자체가 안 된다'고 오해할 수밖에 없었음.
- **첫 알림 메시지 무시**: `step-probe.ts`에서 세션 전환 시 `lastNotifyStepIndex`를 초기화할 때 `-1` 로 리셋하지 않아 새 세션의 첫 안내 메시지가 매번 씹히는 증상 발견.
## 결정 사항
- `bot.py`의 Auto-Approve Embed 구문에 수동 승인처럼 본문을 표출하도록 렌더링 로직 통일.
- `observer-script.ts``TerminalCommand` 정규식에 `\b`를 추가하여 시스템 버튼과의 혼선을 원천 차단함.
- `step-probe.ts` 의 index reset 초기값을 `-1` 로 명시화.

View File

@@ -0,0 +1,13 @@
# step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스
- **시간**: 2026-04-10 17:11
- **Commit**: `COMMITTING`
- **Vikunja**: #125 → done
## 결정 사항
- AI 응답이 비정상적으로 빠를 경우 `RUNNING` 상태의 2초 polling 창을 우회하여 `IDLE` / `WAITING`로 진입해버리는 버그가 있었습니다.
- 기존에는 `isRunning && currentCount > ...`로만 Real-time Capture가 동작하여 전부 스킵되는 증상 확인.
- `isRunning` 조건을 삭제하고, `delta > 0`인 경우 `GetCascadeTrajectorySteps`를 페치하여 `PLANNER_RESPONSE``WAITING` 스텝을 동시에 처리하도록 개선했습니다.
## 미완료
- 없음.

View File

@@ -0,0 +1,13 @@
# Gravity Bridge 빠른 응답(Fast Execution) 누락 오류 해결
- **시간**: 2026-04-10
- **Commit**: TBD
- **Vikunja**: #607 → done
## 문제 원인
- AI 생성이나 응답 작업이 폴링 간격(5초) 미만으로 끝났을 때, 익스텐션의 폴링 루프는 이전과 동일한 `IDLE` 상태만을 보게 됨.
- `lastResponseCaptureStep` 검사는 마련되어 있었으나, `wasRunning` 플래그 제약(`wasRunning && !isRunning`)으로 인하여 IDLE->IDLE 전이를 거치는 모든 단기응답이 `[RESPONSE-CAPTURE]`를 영구히 건너뛰고 통째로 누락됨.
## 해결 방법
- `wasRunning` 방어 조건을 해제하고, `!isRunning && currentCount > lastResponseCaptureStep` 조건으로 완화 (인덱스 전진 기반 감지로 수정).
- 오래된 하드코딩 파서를 버리고 방벽 파서 역할을 하는 `extractPlannerText`로 갈무리 블록의 AI 응답 추출 로직을 단일화하여 적용.

View File

@@ -0,0 +1,15 @@
# [Bridge] Disable DOM Observer Proactive Pending to Fix Empty Bots
- **시간**: 2026-04-10 16:12
- **Commit**: TBD
- **Vikunja**: TBD
## 문제 원인
- 디스코드에 Running 1 comman d 나 내용 없는 Allow 가 반복적으로 전송되는 문제.
- 원인은 v3 DOM Observer 스크립트가 Native UI에서 발생시키는 빈 껍데기 알림(POST /pending)들이, 정상적으로 본문 정보를 모두 추출해 기다리고 있는 step-probe.ts의 완벽한 Pending을 덮어씌우거나 먼저 처리되어버렸기 때문임.
- 단어 경계(\b) 정규식 필터조차 VS Code 렌더링 시 노드 줄바꿈 이슈 등으로 인해 완벽한 방어가 불가능했음.
## 해결 방법
- observer-script.ts에서 버튼 텍스트를 감지해 능동적으로 Pending을 생성하는 기능(PATS 배열)을 **전면 비활성화(배열 비움)**.
- 이로써, 오직 100% 신뢰 가능한 SDK RPC(step-probe.ts)만이 대기 상태(WAITING)와 명령어 상세 정보를 포착해 Pending을 생성함.
- DOM Observer는 브릿지가 보내는 /trigger-click 폴링 명령어를 받아 실제 물리 클릭만 수행하는 '수동적 렌더러' 역할로 격하됨.

View File

@@ -0,0 +1,14 @@
# GetAllCascadeTrajectories 10-Item Hard Limit Bypass
- **시간**: 2026-04-10
- **Commit**: TBD
- **Vikunja**: TBD
## 결정 사항
- `GetAllCascadeTrajectories` LS API의 `limit` 등 페이지네이션 파라미터가 백엔드에서 무시되어 최신 세션이 10개 제한에 잘려나가는 문제를 확인.
- `DOM observer`가 더 이상 작동하지 않는 상태(Empty 보디 이슈로 비활성화됨)에서, `step-probe.ts`마저 이 10개 한도 밖으로 밀려난 현재 세션(`activeSessionId`)을 발견하지 못해, 발생한 모든 채팅 이벤트 파일이 작성되지 않는 문제("단 한글자도 안 날아옴")의 근본 원인을 특정함.
- `GetDiagnostics` API를 사용하여 내부적으로 저장된 `recentTrajectories` 덤프 전체를 불러와, 기존 `GetAllCascadeTrajectories`의 결과를 병합/보완하도록 변경.
- 이를 통해 아무리 많은 수의 세션이 열려 있어도 현재 사용 중인 세션 ID를 식별 가능.
## 미완료
- 없음.

View File

@@ -0,0 +1,15 @@
# Fix gravity bridge Discord Relay AI Chat Body by patching DOM extraction and Regex literals
- **시간**: 2026-04-10 20:30~21:10
- **Vikunja**: #613 → done
## 트러블슈팅: Typescript 백틱 안의 정규식 리터럴 파괴 현상
- **증상**: JSDOM 가상 모의 환경에서 테스트를 돌려보니, 렌더링 화면이나 타겟 Text가 정확히 매치됨에도 정규식이 조건문에서 `false`를 내뱉으며 Button Matching을 건너뛰는 현상 발생.
- **원인**: `observer-script.ts``.js`로 변환할 때, Typescript 컴파일러가 `return \`...\`` 템플릿 리터럴 내부의 `/^(?:Always\s*)?Allow\b/i` 구문을 해석하면서, `\s`를 일반 문자 `s`로, `\b`를 아스키 특수문자 `Backspace(0x08)`로 직렬화하여 클라이언트에 꽂아버리는 문제가 있었음. 이로 인해 정규식 자체가 오염되어 어떠한 버튼도 매칭하지 못하고 있었음.
- **해결**: `observer-script` 내부의 정규식 리터럴 내부의 이스케이프 문자(`\s`, `\b` 등)를 전부 이중 백슬래시(`\\s`, `\\b`)로 패치하여 브라우저에서 스크립트가 실행될 때 올바른 정규식 파서가 열리도록 수정 보완함.
## 결정 사항: 웹뷰 내 로컬 fetch CSP 패치 통과
- `html-patcher.ts`에서 웹뷰 렌더링 시점에 CSP를 조작하여 `default-src 'none'` 방어막을 뚫고 `connect-src``http://127.0.0.1:* wss://127.0.0.1:*`를 주입하도록 강제 적용함. 이를 통해 Bridge 서버로의 로컬 HTTP 통신이 활성화됨.
## 완료 상태
VSCode VSIX (0.5.27) 빌드 완료 및 릴리스 커밋 패키징 수행.

View File

@@ -0,0 +1,13 @@
# Pure 웹소켓 게이트웨이 전환 (Legacy 파일 브릿지 통신 완전히 제거)
- **시간**: 2026-04-11 11:00~13:00
- **Commit**: `(To be updated)`
- **Vikunja**: #N/A
## 결정 사항
- 기존 VS Code 익스텐션과 로컬 Discord Bot 간에 이루어지던 `.gemini/antigravity/bridge/` 기반 파일 공유 통신 체계를 100% 제거하였습니다.
- 파이썬 봇 서버(`bot.py`) 내부에서 동작하던 물리적인 폴링 디렉토리 스캐너(`pending_approval_scanner``chat_snapshot_scanner`) 파일 디펜던시 루프를 완전히 삭제하고 `Hub` WS 핸들러로 대체했습니다. 봇 패키지에 남아있던 `bridge.py``watcher.py` 또한 사용할 이유가 없어져 레포지토리에서 영구적으로 폐기 구별을 내렸습니다.
## 새로 알게된 사실 혹은 트러블슈팅
- 익스텐션에서 `activeSessionId` 변경 시 `watcher.py` 대신 Node.js 네이티브 `fs.watch` 기반으로 자체적인 `BrainWatcher`를 인하우스로 구현해 `step-probe.ts`에 주입함으로써 파이썬 의존도를 완전히 분리할 수 있었습니다.
- 권한 팝업 중복 처리 역시 폴더 스캔 대신 단순히 인메모리 `lastFilePermissionTime` 단일 변수로 최적화되었습니다.

View 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 응답 전달 확인

View File

@@ -0,0 +1,22 @@
# AG Native 번들 역공학 + V8 캐시 정리 + Observer 미작동 원인 규명
- **시간**: 2026-04-12 06:28~07:03
- **Commit**: — (분석/조사 세션, 코드 변경 없음)
## 결정 사항
- `text-ide-message-block-bot-color`는 AI 응답 컨테이너가 **아닌** NUX tooltip 전용 클래스로 확인 → observer 셀렉터에서 제거 필요
- `markdown-body` 클래스도 AG Native에 존재하지 않음 → 폴백 셀렉터 변경 필요
- AI 응답 텍스트는 `plannerResponse` step → `Whi` 렌더러 → `div.px-2.py-1``MarkdownRenderer` 내부에 위치
- `data-step-index`는 디버그 패널에서 확인되었으나 메인 대화 뷰에서의 존재 여부는 라이브 DOM 덤프로 확인 필요
## 새로 알게 된 사실
- AG Native 번들(jetskiAgent/main.js 10.8MB): 전체 step.case→renderer 매핑 확보 (pan 객체)
- Allow/Deny는 `lHr` 컴포넌트, `border-t border-gray-500/25` 클래스
- Observer v7이 HTML에 삽입되었지만 V8 CachedData(50MB) 때문에 실제 렌더러에서 로드되지 않았음
- CachedData 삭제 완료 → AG 리로드 후 observer 작동 예상
## 미완료
- AG 리로드 후 observer 작동 확인
- deep-inspect로 실제 DOM 구조 캡처
- observer-script 셀렉터 미세조정 (bot-color 제거, MarkdownRenderer 타겟팅)
- Discord 릴레이 E2E 검증

View File

@@ -0,0 +1,28 @@
# Observer v8 Electron 실행 보장 + 진단 beacon 추가
- **시간**: 2026-04-12 21:00~21:30
- **Commit**: (이전 세션 크래시 복구 커밋)
## 배경
- 이전 세션(f9491880)에서 Observer v8이 렌더러에서 실행되지 않는 문제 디버깅 중 크래시 발생
- deep-inspect 엔드포인트가 `timeout` 반환 — 인라인 스크립트가 Electron에서 실행 안 됨
- 원인: 인라인 스크립트가 `</html>` 뒤에 삽입되어 Electron이 무시하는 것으로 추정
## 변경 사항
### observer-script.ts
- DIAGNOSTIC BEACON 추가: 스크립트 로드 즉시 `/ping?beacon=1`으로 fetch → 실행 여부 확인 가능
### html-patcher.ts
- 인라인 스크립트 삽입 위치를 `</html>` 앞에서 **`</body>` 앞**으로 변경
- 기존 잘못된 위치의 인라인 블록을 제거 후 재삽입하는 로직 추가
- 이전 패칭에서 발생한 중복 `</html>` 태그 정리 로직 추가
### http-bridge.ts
- 진단용 HTTP 요청 로깅 추가 (폴링 엔드포인트 제외)
## 미완료
- AG 리로드 후 observer 작동 확인 (beacon ping 수신 확인)
- deep-inspect로 실제 DOM 구조 캡처
- observer-script 셀렉터 미세조정 (bot-color 제거, MarkdownRenderer 타겟팅)
- Discord 릴레이 E2E 검증

View File

@@ -0,0 +1,25 @@
# Observer v8 검증 — V8 캐시 차단 재확인
- **시간**: 2026-04-13 09:50~11:00
- **Commit**: 없음 (진단/검증 세션)
- **Vikunja**: #619, #620 (진행 중)
## 진단 결과
1. **Extension POLL**: ✅ 정상 — 세션 `a91b5318` 추적 중, WS 명령 수신 정상
2. **bridge/ 위치**: `~/.gemini/antigravity/bridge/` (프로젝트 루트 아님)
3. **HTML 패치**: ✅ workbench.html + workbench-jetski-agent.html 모두 인라인 스크립트 + BEACON 삽입, `</body>` 앞 위치 정상
4. **Observer 실행**: ❌ BEACON 핑 0건, dom_dumps 디렉토리 없음
5. **V8 CachedData**: 24.42 MB 존재 → 삭제 완료
6. **Discord 릴레이**: ❌ AI 응답 텍스트 추출 불가
## 결정 사항
- Observer 스크립트 미실행의 원인은 V8 CachedData가 패치된 HTML 로드를 차단하는 known-issue와 동일 패턴
- AG가 09:41에 재시작되었지만, 이전 세션에서 삭제한 V8 캐시가 AG 시작과 함께 다시 생성됨
- 해결: V8 캐시 삭제 → AG 재시작 순서 필수 (이번 세션에서 캐시 삭제 완료)
## 미완료
- AG 재시작 후 BEACON 핑 수신 확인
- DOM 덤프 분석: 실제 DOM 구조에서 AI 응답 셀렉터 검증
- 셀렉터 미세조정: bot-color 제거, MarkdownRenderer 타겟팅
- Discord 릴레이 E2E 검증

View File

@@ -0,0 +1,27 @@
# DOM Observer 데이터 품질 검증 + UTF-8/noise 수정
- **시간**: 2026-04-13 12:34~12:52
- **Commit**: `pending`
- **Vikunja**: #619, #620 (진행 중)
## 검증 결과
- DOM Observer v8 **동작 확인**: `pending/`에 45개 시그널 생성됨 (`source: "dom_observer"`)
- 버튼 분류 정상: `command`(30), `permission`(15)
- 명령어/conversation_id/버튼(Allow/Deny/Cancel) 추출 정상
- **한글 인코딩 깨짐** 발견: description 필드에 `[AI 본문 요약]``[AI <20> <20>]`
## 변경 사항 (v0.5.39)
### http-bridge.ts
- 모든 POST 핸들러에 `req.setEncoding('utf8')` 추가
- Node.js HTTP 서버의 Buffer→string latin1 기본 인코딩으로 인한 multi-byte UTF-8 손실 수정
### observer-script.ts
- `cleanLines()`에 인라인 pre-strip 추가: Material 아이콘명 18종을 regex로 `\n`으로 치환
- `Thought for Xs` 패턴 인라인 제거 추가
- `codeText` 추출에 `cleanLines()` 적용 (이전 미적용)
## 미완료
- AG 재시작 후 v0.5.39 적용 검증 (한글 정상 출력 확인)
- DOM dump 추출 검증
- Discord 릴레이 E2E 검증

Some files were not shown because too many files have changed in this diff Show More