Compare commits
2 Commits
6739f8f30c
...
429cae47b7
| Author | SHA1 | Date | |
|---|---|---|---|
| 429cae47b7 | |||
| 5e5f515db4 |
@@ -19,6 +19,7 @@ description: 모든 작업에 자동 적용되는 에이전트 행동 규칙.
|
||||
7. NEVER truncate error messages — always show the full error output
|
||||
8. NEVER say "구현 완료" or "동작 확인" without ACTUAL end-to-end test — import/문법 통과는 검증이 아님
|
||||
9. NEVER confuse "코드가 논리적으로 맞음" with "실제로 동작함" — 실행 로그가 없으면 미검증
|
||||
10. NEVER fix code by looking at only the immediate file — always trace the full data flow across ALL affected files (producer → consumer → side effects) before proposing a fix
|
||||
|
||||
## ALWAYS (필수)
|
||||
|
||||
|
||||
@@ -496,3 +496,9 @@
|
||||
- **해결**: Extension auto-approve 경로 A 제거. Bot만 auto-approve 담당 (v0.3.11). Extension은 항상 `writePendingApproval()` 경로 사용
|
||||
- **주의**: 향후 Extension에서 직접 approve 로직을 추가할 때는 Bot auto-approve와의 경합을 반드시 고려. 단일 경로 원칙 유지
|
||||
|
||||
### [2026-03-15] DOM Observer "Deny" False Positive — Auto-approve 세션 크래시
|
||||
- **증상**: auto-approve ON 시 Discord에 "Deny" command가 자동 승인됨 → AI 세션 반복 크래시
|
||||
- **원인**: AG file_permission UI에 [Allow Once] [Allow This Conversation] [Deny] 3개 버튼 표시 → DOM observer가 "Deny" 텍스트를 독립 pending으로 생성 (`FALSE_POSITIVE_RE`에 "Deny" 미포함). 이 pending에는 `step_type`이 없어 default 분기 `runCommand: { confirm: true }` RPC 전송 → 실제 대기 중인 `file_permission`과 타입 불일치 → 세션 크래시
|
||||
- **해결**: (1) `FALSE_POSITIVE_RE`에 `Deny|Allow Once|Allow This Conversation|Dismiss|Decline` 추가, (2) bot.py auto-approve에 reject-word command 차단 가드, (3) smart button_index 선택 (방어)
|
||||
- **주의**: `FALSE_POSITIVE_RE`는 렌더러 인라인 스크립트 안에 있으므로 **VSIX 빌드 → AG 풀 재시작** 필요. 새 UI 버튼 패턴 추가 시 반드시 이 필터 점검. **수정 시 해당 파일만 보지 말고 전체 데이터 플로우(producer→consumer→side effects) 분석 필수 (AGENT.md 규칙 #10)**
|
||||
|
||||
|
||||
33
bot.py
33
bot.py
@@ -564,12 +564,41 @@ class GravityBot(commands.Bot):
|
||||
|
||||
# ── Auto-approve: if project has auto enabled, approve immediately ──
|
||||
if project in self.auto_approve_projects:
|
||||
# Defence: reject-word commands should NEVER be auto-approved
|
||||
# (DOM observer may create standalone "Deny" pending from file_permission UI)
|
||||
reject_commands = {"deny", "reject", "cancel", "decline", "dismiss", "stop"}
|
||||
if req.command.strip().lower() in reject_commands:
|
||||
logger.warning(f"Auto-approve BLOCKED: command='{req.command}' is reject-word — skipping")
|
||||
self._sent_approval_ids.add(req.request_id)
|
||||
continue
|
||||
|
||||
self._sent_approval_ids.add(req.request_id)
|
||||
|
||||
# Smart button_index: read buttons array from pending file
|
||||
# file_permission buttons = [Allow Once(0), Allow This Conv(1), Deny(2)]
|
||||
# MUST pick non-reject button for safety
|
||||
approve_btn_index = 0
|
||||
pending_file = self.bridge.pending_dir / f"{req.request_id}.json"
|
||||
if pending_file.exists():
|
||||
try:
|
||||
pdata = json.loads(pending_file.read_text(encoding="utf-8-sig"))
|
||||
btns = pdata.get("buttons")
|
||||
if btns and len(btns) > 1:
|
||||
reject_words = {"deny", "reject", "cancel", "reject all",
|
||||
"decline", "dismiss", "stop"}
|
||||
for b in btns:
|
||||
txt = b.get("text", "").lower().strip()
|
||||
if txt not in reject_words:
|
||||
approve_btn_index = b.get("index", 0)
|
||||
break
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
|
||||
# Write auto-approve response for Extension
|
||||
self.bridge.write_response(UserResponse(
|
||||
request_id=req.request_id,
|
||||
approved=True,
|
||||
button_index=0, # first button (Allow Once / Run)
|
||||
button_index=approve_btn_index,
|
||||
step_type=getattr(req, 'step_type', ''),
|
||||
project_name=project,
|
||||
))
|
||||
@@ -583,7 +612,7 @@ class GravityBot(commands.Bot):
|
||||
)
|
||||
embed.set_footer(text=f"auto-approve | {req.request_id[:12]}")
|
||||
await channel.send(embed=embed)
|
||||
logger.info(f"Auto-approved: {req.request_id[:12]} project={project}")
|
||||
logger.info(f"Auto-approved: {req.request_id[:12]} project={project} btn_idx={approve_btn_index}")
|
||||
continue
|
||||
|
||||
# Defer short-command pendings (e.g. "Run") by 4 cycles (~12s)
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
| 002 | 08:25~08:31 | Extension v0.3.10 버전 범프 & VSIX 빌드 | `10caae1` | ✅ |
|
||||
| 003 | 10:00~10:41 | 승인 라이프사이클 race condition 4건 수정 (HTML lock, pending status skip, auto_resolve Discord 알림, Bot approval_messages) | `f962036` | ✅ |
|
||||
| 004 | 10:41~10:53 | 성능 최적화 3건 (pollResponseGroup 1500ms, renderer adaptive idle, Bot single-pass scanner) + VSIX 빌드 | `ae0509f` | ✅ |
|
||||
| 005 | 15:17~17:09 | 크로스 프로젝트 신호 오염 진단 & 승인 플로우 아키텍처 수정 — DEDUP project_name 가드, double-fire auto-approve 제거, 실패 RPC 전략 30+개 삭제 (v0.3.11) | - | 🔧 |
|
||||
| 005 | 15:17~17:09 | 크로스 프로젝트 신호 오염 진단 & 승인 플로우 아키텍처 수정 — DEDUP project_name 가드, double-fire auto-approve 제거, 실패 RPC 전략 30+개 삭제 (v0.3.11) | `6739f8f` | ✅ |
|
||||
| 006 | 18:32~18:51 | Auto-approve 크래시 수정 — DOM Observer Deny false positive 필터 + Bot reject-word 가드 + AGENT.md 규칙 #10 추가 | `5e5f515` | ✅ |
|
||||
|
||||
|
||||
@@ -742,7 +742,7 @@ function startObserverHttpBridge() {
|
||||
const data = JSON.parse(body);
|
||||
// ── Server-side false positive filter ──
|
||||
const cmd = (data.command || '').trim();
|
||||
const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it)$/i;
|
||||
const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it|Deny|Allow Once|Allow This Conversation|Dismiss|Decline)$/i;
|
||||
if (FALSE_POSITIVE_RE.test(cmd)) {
|
||||
logToFile(`[HTTP] filtered false positive: "${cmd}"`);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -739,7 +739,7 @@ function startObserverHttpBridge(): Promise<number> {
|
||||
|
||||
// ── Server-side false positive filter ──
|
||||
const cmd = (data.command || '').trim();
|
||||
const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it)$/i;
|
||||
const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it|Deny|Allow Once|Allow This Conversation|Dismiss|Decline)$/i;
|
||||
if (FALSE_POSITIVE_RE.test(cmd)) {
|
||||
logToFile(`[HTTP] filtered false positive: "${cmd}"`);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
|
||||
Reference in New Issue
Block a user