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에서 세션 교체 시 최근 알림 인덱스 초기화를 잘못하여 세션의 첫 메시지를 무조건 드롭.
|
- **원인**: 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로 리셋.
|
- **해결**: 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는 반드시 본문을 노출해야 함.
|
- **주의**: 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,9 +366,7 @@ function setupMonitor() {
|
|||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
if (sType.includes('PLANNER_RESPONSE') && s?.status?.includes('DONE')) {
|
if (sType.includes('PLANNER_RESPONSE') && s?.status?.includes('DONE')) {
|
||||||
const pr = s?.plannerResponse;
|
let text = extractPlannerText(s) || '';
|
||||||
if (pr) {
|
|
||||||
let text = pr.modifiedResponse || pr.rawText || pr.text || '';
|
|
||||||
if (text.length > 10) {
|
if (text.length > 10) {
|
||||||
lastResponseCaptureStep = actualIdx;
|
lastResponseCaptureStep = actualIdx;
|
||||||
ctx.logToFile(`[RT-CAPTURE] step=${actualIdx} (${text.length} chars)`);
|
ctx.logToFile(`[RT-CAPTURE] step=${actualIdx} (${text.length} chars)`);
|
||||||
@@ -381,7 +379,6 @@ function setupMonitor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (rte: any) {
|
} catch (rte: any) {
|
||||||
// Non-critical — don't spam logs
|
// Non-critical — don't spam logs
|
||||||
if (pollCount <= 5) ctx.logToFile(`[RT-CAPTURE] error: ${rte.message.substring(0, 80)}`);
|
if (pollCount <= 5) ctx.logToFile(`[RT-CAPTURE] error: ${rte.message.substring(0, 80)}`);
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
export function extractPlannerText(step: any): string | null {
|
export function extractPlannerText(step: any): string | null {
|
||||||
if (!step) { return 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
|
// Fields to SKIP — not user-facing content
|
||||||
const SKIP_FIELDS = new Set([
|
const SKIP_FIELDS = new Set([
|
||||||
'thinking', 'thinkingSignature', 'stopReason', 'type', 'status', 'metadata',
|
'thinking', 'thinkingSignature', 'stopReason', 'type', 'status', 'metadata',
|
||||||
@@ -18,38 +25,34 @@ export function extractPlannerText(step: any): string | null {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// plannerResponse can be string or object
|
// plannerResponse can be string or object
|
||||||
const pr = step.plannerResponse;
|
const pr = step.plannerResponse || step.step?.plannerResponse;
|
||||||
if (typeof pr === 'string' && pr.length > 10) {
|
if (typeof pr === 'string' && pr.length > 10) {
|
||||||
return filterEphemeral(pr);
|
return filterEphemeral(pr);
|
||||||
}
|
}
|
||||||
if (pr && typeof pr === 'object') {
|
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;
|
const text = pr.content || pr.text || pr.summary || pr.message || pr.response || pr.output;
|
||||||
if (typeof text === 'string' && text.length > 10) {
|
if (typeof text === 'string' && text.length > 10) {
|
||||||
return filterEphemeral(text);
|
return filterEphemeral(text);
|
||||||
}
|
}
|
||||||
// Search other fields, but skip non-content ones
|
|
||||||
for (const key of Object.keys(pr)) {
|
for (const key of Object.keys(pr)) {
|
||||||
if (SKIP_FIELDS.has(key)) continue;
|
if (SKIP_FIELDS.has(key)) continue;
|
||||||
const val = pr[key];
|
const val = pr[key];
|
||||||
if (typeof val === 'string' && val.length > 50) { // Higher threshold
|
if (typeof val === 'string' && val.length > 50) { // Higher threshold
|
||||||
const filtered = filterEphemeral(val);
|
const filtered = filterEphemeral(val);
|
||||||
if (filtered) {
|
if (filtered) {
|
||||||
console.log(`Gravity Bridge: [DEBUG] planner text in plannerResponse.${key} (${filtered.length} chars)`);
|
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try other step fields (skip known non-content)
|
// Try other step fields
|
||||||
for (const key of Object.keys(step)) {
|
for (const key of Object.keys(step)) {
|
||||||
if (SKIP_FIELDS.has(key) || key === 'plannerResponse') continue;
|
if (SKIP_FIELDS.has(key) || key === 'plannerResponse') continue;
|
||||||
const val = step[key];
|
const val = step[key];
|
||||||
if (typeof val === 'string' && val.length > 50) {
|
if (typeof val === 'string' && val.length > 50) {
|
||||||
const filtered = filterEphemeral(val);
|
const filtered = filterEphemeral(val);
|
||||||
if (filtered) {
|
if (filtered) {
|
||||||
console.log(`Gravity Bridge: [DEBUG] planner text in step.${key}`);
|
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user