fix(extension): resolve AI response dropping by adding nested payload extraction in step-utils
This commit is contained in:
@@ -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) 모두 모킹하여 직접 파싱 확인.
|
||||
|
||||
7
docs/devlog/2026-04-10.md
Normal file
7
docs/devlog/2026-04-10.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 2026-04-10 데브로그
|
||||
|
||||
## 작업 내역
|
||||
|
||||
| NNN | HH:MM | 작업 설명 | 커밋해시 | 완료 방면 |
|
||||
|---|---|---|---|---|
|
||||
| 001 | 15:53 | Gravity Bridge AI 응답 텍스트가 누락되는 버그 픽스 (extractPlannerText 적용 및 Nested 조회 추가) | TBD | ✅ |
|
||||
23
docs/devlog/entries/20260410-001.md
Normal file
23
docs/devlog/entries/20260410-001.md
Normal file
@@ -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 ?뚯떛??紐⑤몢 ?뺤긽 ?섑뻾?⑥쓣 ?뺤씤
|
||||
|
||||
## 誘몄셿猷??ы빆
|
||||
- ?놁쓬
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user