From 2ece05fc6f63a805ff63076fbad25bafa4410e21 Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Fri, 10 Apr 2026 16:05:30 +0900 Subject: [PATCH] fix(extension): resolve AI response dropping for sub-5s executions by relaxing IDLE capture condition #task-607 --- .agents/references/known-issues.md | 7 ++++- docs/devlog/2026-04-10.md | 3 +- docs/devlog/entries/20260410-002.md | 13 ++++++++ extension/src/step-probe.ts | 46 ++++------------------------- 4 files changed, 27 insertions(+), 42 deletions(-) create mode 100644 docs/devlog/entries/20260410-002.md diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index 245445a..bfb0a8e 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -212,5 +212,10 @@ ### [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) 모두 모킹하여 직접 파싱 확인. + +### [2026-04-10] [Extension] Fast Execution `<5s` Response Capture Missed (IDLE-to-IDLE) +- **증상**: 디스코드로 내용이 아예 전달되지 않음. `[RT-CAPTURE]`, `[RESPONSE-CAPTURE]` 로그 모두 전혀 남지 않음. +- **원인**: AI 응답이나 코딩 작업이 5초(폴링 주기) 미만으로 매우 빠르게 끝나면, 확장이 `IDLE -> IDLE` 상태만 관찰하며 `wasRunning` 플래그가 `false`로 유지됨. 기존 `[RESPONSE-CAPTURE]` 조건식(`wasRunning && !isRunning && currentCount > ...`)이 `wasRunning=false`로 인해 블록되어 캡처 자체를 완전히 건너뛰게 됨. +- **해결**: `wasRunning` 검증을 삭제하고 `!isRunning && currentCount > lastResponseCaptureStep` 조건으로 완화하여 누락된 step이 있을 때 무조건 캡처하도록 변경. 추가로 오래된 `[RESPONSE-CAPTURE]` 내 하드코딩 파서를 `extractPlannerText`로 일원화 적용. +- **주의**: 폴링 방식에서는 상태(RUNNING->IDLE) 전이를 확신할 수 없으므로, Step Count(인덱스 전진)라는 100% 신뢰 가능한 마커를 통해 새 응답 여부를 감지해야 함. diff --git a/docs/devlog/2026-04-10.md b/docs/devlog/2026-04-10.md index 2d53a5a..a0f4f74 100644 --- a/docs/devlog/2026-04-10.md +++ b/docs/devlog/2026-04-10.md @@ -4,4 +4,5 @@ | NNN | HH:MM | 작업 설명 | 커밋해시 | 완료 방면 | |---|---|---|---|---| -| 001 | 15:53 | Gravity Bridge AI 응답 텍스트가 누락되는 버그 픽스 (extractPlannerText 적용 및 Nested 조회 추가) | TBD | ✅ | \ No newline at end of file +| 001 | 15:53 | Gravity Bridge AI 응답 텍스트가 누락되는 버그 픽스 (extractPlannerText 적용 및 Nested 조회 추가) | TBD | ✅ | +| 002 | 16:05 | Gravity Bridge 빠른 응답 누락 오류 해결 (IDLE-to-IDLE 패스 로직 완화) | TBD | ✅ | \ No newline at end of file diff --git a/docs/devlog/entries/20260410-002.md b/docs/devlog/entries/20260410-002.md new file mode 100644 index 0000000..0b31f9a --- /dev/null +++ b/docs/devlog/entries/20260410-002.md @@ -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 응답 추출 로직을 단일화하여 적용. diff --git a/extension/src/step-probe.ts b/extension/src/step-probe.ts index 14fb6bd..65ce960 100644 --- a/extension/src/step-probe.ts +++ b/extension/src/step-probe.ts @@ -859,8 +859,8 @@ function setupMonitor() { } } - if (wasRunning && !isRunning && currentCount > lastResponseCaptureStep) { - ctx.logToFile(`[RESPONSE-CAPTURE] RUNNING→IDLE, steps=${currentCount}, capturing response...`); + if (!isRunning && currentCount > lastResponseCaptureStep) { + ctx.logToFile(`[RESPONSE-CAPTURE] IDLE check, steps=${currentCount} > last=${lastResponseCaptureStep}, capturing response...`); lastResponseCaptureStep = currentCount; try { const offset = Math.max(0, currentCount - 5); @@ -875,41 +875,7 @@ function setupMonitor() { const s = steps[ri]; const sType = s?.type || ''; if (sType.includes('PLANNER_RESPONSE') && !sType.includes('EPHEMERAL')) { - let textContent = ''; - // Extract from plannerResponse field - const pr = s?.plannerResponse; - if (pr) { - // Priority: modifiedResponse (confirmed field from AG) - if (pr.modifiedResponse) textContent = pr.modifiedResponse; - else if (pr.rawText) textContent = pr.rawText; - else if (pr.text) textContent = pr.text; - else if (pr.message) textContent = typeof pr.message === 'string' ? pr.message : ''; - else if (pr.content?.parts) { - for (const p of pr.content.parts) { - if (p?.text) textContent += p.text; - } - } - // Log first time to capture actual field names - if (!textContent) { - ctx.logToFile(`[RESPONSE-CAPTURE] plannerResponse keys: [${Object.keys(pr).join(',')}] values(100): ${JSON.stringify(pr).substring(0, 200)}`); - } - } - // Extract from ephemeralMessage field - const em = s?.ephemeralMessage; - if (!textContent && em) { - if (typeof em === 'string') textContent = em; - else if (em.message) textContent = em.message; - else if (em.content) textContent = typeof em.content === 'string' ? em.content : JSON.stringify(em.content); - } - // Fallback: metadata, content, rawOutput - if (!textContent) { - const parts = s?.content?.parts || s?.parts || []; - for (const p of parts) { - if (p?.text) textContent += p.text; - } - } - if (!textContent && s?.metadata?.text) textContent = s.metadata.text; - if (!textContent && s?.rawOutput) textContent = typeof s.rawOutput === 'string' ? s.rawOutput : JSON.stringify(s.rawOutput); + const textContent = extractPlannerText(s) || ''; if (textContent.length > 10) { ctx.logToFile(`[RESPONSE-CAPTURE] found ${sType} (${textContent.length} chars) at step ${offset + ri}`); const truncated = textContent.length > 3500 @@ -918,7 +884,7 @@ function setupMonitor() { ctx.writeChatSnapshot(`💬 **AI 응답**\n\n${truncated}`); break; } else { - ctx.logToFile(`[RESPONSE-CAPTURE] ${sType} too short (${textContent.length}), keys=[${Object.keys(s).join(',')}]`); + ctx.logToFile(`[RESPONSE-CAPTURE] ${sType} too short (${textContent.length})`); } } } @@ -930,8 +896,8 @@ function setupMonitor() { ctx.writeChatSnapshot(`✅ **Step ${currentCount} 작업 종료**`); } - // ── Diff review detection: if session just went IDLE and files were modified ── - if (wasRunning && !isRunning && pendingModifiedFiles.length > 0) { + // ── Diff review detection: if session went IDLE and files were modified ── + if (!isRunning && pendingModifiedFiles.length > 0) { // Phase 3 FIX: Filter out brain/ artifact files (task.md, implementation_plan.md etc.) // These are AG internal artifacts, NOT code changes needing user review. const brainPathSegment = '.gemini/antigravity/brain/';