Compare commits
2 Commits
2ece05fc6f
...
e745744636
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e745744636 | ||
|
|
b88e75b075 |
@@ -219,3 +219,8 @@
|
||||
- **원인**: 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% 신뢰 가능한 마커를 통해 새 응답 여부를 감지해야 함.
|
||||
### [2026-04-10] [Bot] chat_snapshot_scanner 무한 Abort 및 파일 적체 (Exception 누락)
|
||||
- **증상**: 봇이 디스코드로 AI 답변(채팅 스냅샷)을 전혀 전송하지 못하고 렉이 걸림. ridge/chat_snapshots/에 처리되지 않은 JSON 파일이 수십 개 적체됨.
|
||||
- **원인**: ot.py의 chat_snapshot_scanner에서 파일을 순회 파싱할 때 내부의 .unlink() 과정에서 발생하는 예외나 discord.Embed 생성 예외 등을 루프 안에서 잡아주지 못함. 첫 에러 파일(poison pill)을 만나는 순간 루프 전체가 폭파되어 뒤쪽의 정상 파일들도 영원히 처리되지 않고 다음 폴 스케줄에서 다시 첫 파일에 막힘.
|
||||
- **해결**: 루프 내부에 except Exception을 추가하여 전역 예외를 잡아 방어. 실패한 파일은 glob에서 반복 시도되지 않게 .json.failed로 우회(rename)시켜 큐를 비워줌.
|
||||
- **주의**: 폴링/스캐너 or 루프 내부에서는 개별 아이템 파싱 단계에서 발생 가능한 모든 예외 상태에 대한 Defensive Catch 및 Continue(우회) 로직이 필수임.
|
||||
|
||||
6
bot.py
6
bot.py
@@ -1381,8 +1381,12 @@ class GravityBot(commands.Bot):
|
||||
logger.error(f"[SNAPSHOT] Discord send failed for {project}: {e}")
|
||||
|
||||
f.unlink() # Cleanup
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
except Exception as e:
|
||||
logger.warning(f"Bad chat snapshot {f.name}: {e}")
|
||||
try:
|
||||
f.rename(f.with_suffix('.json.failed'))
|
||||
except OSError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"Error scanning chat snapshots: {e}")
|
||||
|
||||
|
||||
@@ -5,4 +5,6 @@
|
||||
| NNN | HH:MM | 작업 설명 | 커밋해시 | 완료 방면 |
|
||||
|---|---|---|---|---|
|
||||
| 001 | 15:53 | Gravity Bridge AI 응답 텍스트가 누락되는 버그 픽스 (extractPlannerText 적용 및 Nested 조회 추가) | TBD | ✅ |
|
||||
| 002 | 16:05 | Gravity Bridge 빠른 응답 누락 오류 해결 (IDLE-to-IDLE 패스 로직 완화) | TBD | ✅ |
|
||||
| 002 | 16:05 | Gravity Bridge 빠른 응답 누락 오류 해결 (IDLE-to-IDLE 패스 로직 완화) | TBD | ✅ || 003 | 16:12 | [Bridge] DOM Observer <20><>Ž<EFBFBD><C5BD> <20><><EFBFBD><EFBFBD> (PATS <20><>Ȱ<EFBFBD><C8B0>ȭ)<29><> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD> <20>˸<EFBFBD> <20><><EFBFBD><EFBFBD> | TBD | ? |
|
||||
|
||||
| 004 | 16:31 | [Bot] chat_snapshot_scanner 미처리 예외 큐 막힘 현상 해결 및 Hermes Gateway 재시작 | TBD | ✅ |
|
||||
|
||||
15
docs/devlog/entries/20260410-003.md
Normal file
15
docs/devlog/entries/20260410-003.md
Normal 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 폴링 명령어를 받아 실제 물리 클릭만 수행하는 '수동적 렌더러' 역할로 격하됨.
|
||||
@@ -275,12 +275,9 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
// ONLY positive triggers should initiate a pending request group.
|
||||
// Negative/secondary buttons (Deny, Reject, Dismiss) will be collected as siblings.
|
||||
var PATS=[
|
||||
{re:/^(?:Always\s*)?Run\b/i, type:'terminal_command'},
|
||||
{re:/^(?:Always\s*)?Accept(?: all)?\b/i, type:'diff_review'},
|
||||
{re:/^(?:Always\s*)?Accept\b/i, type:'agent_step'},
|
||||
{re:/^(?:Always\s*)?Allow\b/i, type:'permission'},
|
||||
{re:/^(?:Always\s*)?Approve\b/i, type:'agent_step'},
|
||||
{re:/^Retry\b/i, type:'error_recovery'},
|
||||
// ALL PATS removed to prevent UI-level False Positives and "Empty Body" payloads.
|
||||
// 100% of pending detection is now handled by step-probe.ts which has full RPC context.
|
||||
// The DOM observer remains strictly for 'trigger-click' (executing physical clicks on fallback).
|
||||
];
|
||||
|
||||
// ALL actionable button patterns (for grouping siblings in same container)
|
||||
|
||||
@@ -46,17 +46,25 @@ export function extractPlannerText(step: any): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return filtered;
|
||||
// Fallback: nested fields not caught by top-level string iteration
|
||||
if (step.content?.parts) {
|
||||
let txt = '';
|
||||
for (const p of step.content.parts) { if (p?.text) txt += p.text; }
|
||||
if (txt.length > 10) return filterEphemeral(txt);
|
||||
}
|
||||
if (step.parts) {
|
||||
let txt = '';
|
||||
for (const p of step.parts) { if (p?.text) txt += p.text; }
|
||||
if (txt.length > 10) return filterEphemeral(txt);
|
||||
}
|
||||
if (step.metadata?.text && step.metadata.text.length > 10) {
|
||||
return filterEphemeral(step.metadata.text);
|
||||
}
|
||||
if (step.rawOutput) {
|
||||
const txt = typeof step.rawOutput === 'string' ? step.rawOutput : JSON.stringify(step.rawOutput);
|
||||
if (txt.length > 10) return filterEphemeral(txt);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user