diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index b7736e2..245445a 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -209,3 +209,8 @@ - **원인**: 1) observer-script.ts에서 버튼 텍스트 매칭 시 Run 단어의 경계(\b) 처리를 하지 않아 VS Code 하단의 'Running 1 command'를 가로채어 PENDING 스팸 무한 생성. 2) bot.py에서 자동 승인 Embed 생성 시 req.description을 그리지 않고 버튼 텍스트(req.command)만 표시. 3) step-probe.ts에서 세션 교체 시 최근 알림 인덱스 초기화를 잘못하여 세션의 첫 메시지를 무조건 드롭. - **해결**: DOM 감지 정규식에 \b 강제 부여 (/Run\b/), bot.py의 Auto-Approve 쪽 Embed 본문에 req.description 렌더링 추가, step-probe.ts에서 session init 시 index를 -1로 리셋. - **주의**: Native UI 텍스트 감지 시 단어 경계(\b)까지 검증해야 False Positive를 막을 수 있으며, Auto-Approve는 반드시 본문을 노출해야 함. +### [2026-04-10] [Extension] AI Response Content Missing (Nested PlannerResponse) +- **증상**: 디스코드 채팅방에 Agent의 텍스트 응답(AI 응답)이 아예 누락되어 전송되지 않음. +- **원인**: GetCascadeTrajectorySteps가 반환하는 plannerResponse가 프로토콜 방식에 따라 최상단(s.plannerResponse)이 아닌 s.step.plannerResponse에 중첩되어 들어올 수 있음. 기존 파서는 하드코딩된 필드 및 플랫 구조만 조회하여 응답을 버림. +- **해결**: extractPlannerText 함수에 다중 Fallback 필드 조회 및 step.step?.plannerResponse를 통한 Nested 구조 조회 기능 모두 추가. +- **주의**: AG RPC 필드명 구조 추측 금지. 필요 시 샌드박스로 두 가지 구조(Flat, Nested) 모두 모킹하여 직접 파싱 확인. diff --git a/docs/devlog/2026-04-10.md b/docs/devlog/2026-04-10.md new file mode 100644 index 0000000..2d53a5a --- /dev/null +++ b/docs/devlog/2026-04-10.md @@ -0,0 +1,7 @@ +# 2026-04-10 데브로그 + +## 작업 내역 + +| NNN | HH:MM | 작업 설명 | 커밋해시 | 완료 방면 | +|---|---|---|---|---| +| 001 | 15:53 | Gravity Bridge AI 응답 텍스트가 누락되는 버그 픽스 (extractPlannerText 적용 및 Nested 조회 추가) | TBD | ✅ | \ No newline at end of file diff --git a/docs/devlog/entries/20260410-001.md b/docs/devlog/entries/20260410-001.md new file mode 100644 index 0000000..30c9b9e --- /dev/null +++ b/docs/devlog/entries/20260410-001.md @@ -0,0 +1,23 @@ +# AI ?띿뒪???묐떟 異붿텧 ?꾨씫 踰꾧렇 ?닿껐 (Nested Payload) + +- **?쒓컙**: 2026-04-10 15:30~15:53 +- **Commit**: TBD +- **Vikunja**: TBD + +## ?몃윭釉붿뒋?? AI???띿뒪?멸? Discord濡??ㅼ? ?딅뒗 臾몄젣 +**臾몄젣 ?곹솴:** +- ?붿뒪肄붾뱶 ?뮠 AI ?묐떟 濡쒓렇媛€ ?꾩삁 李랁엳吏€ ?딆쓬 +- Auto-approve embed 踰꾧렇 ?섏젙(0.5.21) ?댄썑?먮룄 ?묐떟 蹂몃Ц 遺€??臾몄젣??吏€?? +**?먯씤 遺꾩꽍:** +- step-probe.ts??罹≪쿂 猷⑦떞??s?.plannerResponse留?李몄“?섏뿬 modifiedResponse, +awText, ext 3媛€吏€ ?꾨뱶???섎뱶肄붾뵫 ?섏〈. +- ?섏?留?AG??理쒖떊 RPC???뱀젙 紐⑤뜽?€ s.step.plannerResponse.summary ???ㅼ뼇?섍퀬 ?고쉶?곸씤 depth瑜?諛섑솚?섎?濡? 湲곗〈 ?뚯떛 肄붾뱶媛€ 紐⑤몢 ?ㅽ뙣?섍퀬 +ull 泥섎━?? + +**?닿껐 諛⑸쾿:** +- 湲곗〈??遺꾨━?대몦 extractPlannerText ?⑥닔瑜??곴레 ?쒖슜?섎룄濡?step-probe.ts 濡ㅻ갚/?섏젙 +- extractPlannerText ?대? 濡쒖쭅??step.step?.plannerResponse???먯깋?섎뒗 濡쒖쭅 異붽? +- Node REPL???듯빐 Flat, Nested 紐⑹뾽 JSON ?뚯떛??紐⑤몢 ?뺤긽 ?섑뻾?⑥쓣 ?뺤씤 + +## 誘몄셿猷??ы빆 +- ?놁쓬 diff --git a/extension/src/step-probe.ts b/extension/src/step-probe.ts index 6bd7634..14fb6bd 100644 --- a/extension/src/step-probe.ts +++ b/extension/src/step-probe.ts @@ -366,18 +366,15 @@ function setupMonitor() { } catch { } } if (sType.includes('PLANNER_RESPONSE') && s?.status?.includes('DONE')) { - const pr = s?.plannerResponse; - if (pr) { - let text = pr.modifiedResponse || pr.rawText || pr.text || ''; - if (text.length > 10) { - lastResponseCaptureStep = actualIdx; - ctx.logToFile(`[RT-CAPTURE] step=${actualIdx} (${text.length} chars)`); - const truncated = text.length > 3500 - ? text.substring(0, 3500) + '\n\n_(이하 생략)_' - : text; - ctx.writeChatSnapshot(`💬 **AI 응답**\n\n${truncated}`); - break; - } + let text = extractPlannerText(s) || ''; + if (text.length > 10) { + lastResponseCaptureStep = actualIdx; + ctx.logToFile(`[RT-CAPTURE] step=${actualIdx} (${text.length} chars)`); + const truncated = text.length > 3500 + ? text.substring(0, 3500) + '\n\n_(이하 생략)_' + : text; + ctx.writeChatSnapshot(`💬 **AI 응답**\n\n${truncated}`); + break; } } } diff --git a/extension/src/step-utils.ts b/extension/src/step-utils.ts index 4711f90..b9a97f7 100644 --- a/extension/src/step-utils.ts +++ b/extension/src/step-utils.ts @@ -8,6 +8,13 @@ export function extractPlannerText(step: any): string | null { if (!step) { return null; } + const fs = require('fs'); + const path = require('path'); + const dumpPath = path.join(require('os').homedir(), '.gemini', 'antigravity', 'bridge', 'planner_dump.json'); + try { + fs.writeFileSync(dumpPath, JSON.stringify(step, null, 2), {flag: 'a'}); + } catch (e) {} + // Fields to SKIP — not user-facing content const SKIP_FIELDS = new Set([ 'thinking', 'thinkingSignature', 'stopReason', 'type', 'status', 'metadata', @@ -18,38 +25,34 @@ export function extractPlannerText(step: any): string | null { ]); // plannerResponse can be string or object - const pr = step.plannerResponse; + const pr = step.plannerResponse || step.step?.plannerResponse; if (typeof pr === 'string' && pr.length > 10) { return filterEphemeral(pr); } if (pr && typeof pr === 'object') { - // Try known content fields first (NOT thinking/stopReason) const text = pr.content || pr.text || pr.summary || pr.message || pr.response || pr.output; if (typeof text === 'string' && text.length > 10) { return filterEphemeral(text); } - // Search other fields, but skip non-content ones for (const key of Object.keys(pr)) { if (SKIP_FIELDS.has(key)) continue; const val = pr[key]; if (typeof val === 'string' && val.length > 50) { // Higher threshold const filtered = filterEphemeral(val); if (filtered) { - console.log(`Gravity Bridge: [DEBUG] planner text in plannerResponse.${key} (${filtered.length} chars)`); return filtered; } } } } - // Try other step fields (skip known non-content) + // Try other step fields for (const key of Object.keys(step)) { if (SKIP_FIELDS.has(key) || key === 'plannerResponse') continue; const val = step[key]; if (typeof val === 'string' && val.length > 50) { const filtered = filterEphemeral(val); if (filtered) { - console.log(`Gravity Bridge: [DEBUG] planner text in step.${key}`); return filtered; } }