fix(bridge): fix Discord signal relay false-positives and empty body logic

This commit is contained in:
Variet Worker
2026-04-10 00:12:01 +09:00
parent 22e1799d66
commit fadd39424b
6 changed files with 35 additions and 13 deletions

View File

@@ -197,3 +197,9 @@
| 16 | **익스텐션(Bridge)은 자의적 비즈니스 판단 절대 금지** — `SafeToAutoRun` 등의 조건 브랜치 분기는 모두 봇으로 위임 (Agnostic Bridge) | SafeToAutoRun Deadlock (v0.5.15) |
| 17 | **package.json 빌드 스크립트 강제** — `vscode:prepublish` 추가로 낡은 소스 배포 원천 차단 | VSIX v0.5.15 빌드 누락 |
| 18 | **동기식 `cp.execSync` 사용 금지** — Windows 환경에서 메인 이벤트루프 프리징 및 WS heartbeat 단절 유발 | detectProjectName 프리징 |
### [2026-04-09] [Bot/Extension] Discord Signal Relay Failure & Empty Body
- **증상**: 디스코드 봇은 '자동 승인됨'을 띄우지만 실제 코드 본문이 표시되지 않고, 채널에 진짜 채팅 메시지나 알림이 스팸 큐 뒤에 밀려 전송되지 않음.
- **원인**: 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는 반드시 본문을 노출해야 함.

4
bot.py
View File

@@ -732,7 +732,7 @@ class GravityBot(commands.Bot):
try:
embed = discord.Embed(
title="🤖 자동 승인됨",
description=f"```\n{req.command[:500]}\n```",
description=f"✅ **{req.command}**\n\n```\n{req.description[:2000]}\n```" if getattr(req, "description", "") else f"✅ **{req.command}**",
color=discord.Color.green(),
)
embed.set_footer(text=f"auto-approve | {req.request_id[:12]}")
@@ -1158,7 +1158,7 @@ class GravityBot(commands.Bot):
try:
embed = discord.Embed(
title="🤖 자동 승인됨",
description=f"```\n{request.command[:500]}\n```",
description=f"✅ **{request.command}**\n\n```\n{request.description[:2000]}\n```" if getattr(request, "description", "") else f"✅ **{request.command}**",
color=discord.Color.green(),
)
embed.set_footer(text=f"auto-approve | {request.request_id[:12]}")

View File

@@ -5,3 +5,4 @@
| 001 | 21:55 | Agent UI Tailwind/Native 마이그레이션 대응 (DOM 옵저버 구조 개편) | `HEAD` | ✅ |
| 002 | 22:30 | Agent UI 버튼 무시 버그 긴급수정 (CodeLens 필터교정) | `HEAD` | ✅ |
| 003 | 23:15 | Native UI 아이콘 글루잉 대응 스캐너 픽스 (DOM Regex 매칭 강화) | `HEAD` | ✅ |
| 004 | 00:10 | Discord Signal Relay & Auto-Approve Body Null 버그 수정 (False Positive 차단) | "HEAD" | ✅ |

View File

@@ -0,0 +1,15 @@
# Discord Signal Relay & Auto-Approve Body Null 버그 수정 (False Positive 차단)
- **시간**: 2026-04-10 00:00~00:10
- **Commit**: `HEAD`
- **Vikunja**: #태스크번호 → done
## 트러블슈팅 & 삽질
- **DOM 정규식의 반란**: Native UI 패치 이후 버튼의 text-gluing 제거 때문에 정규식을 광범위하게 바꿨으나, 하필 `Run\s*` 조건에 단어 경계(`\b`)를 누락하는 바람에 VS Code 하단의 시스템 상태 버튼인 `Running 1 command`까지 AI의 `Run` 버튼으로 인식해버림. 무한 PENDING 스팸을 만들어 브릿지 큐를 폭파시킨 주범.
- **Auto-Approve 본문 누락**: 봇에서 자동 승인 Embed 생성 시 `req.description` (실행될 본문 코드)을 아예 그리지 않고 `req.command` (단순 버튼 라벨)만 출력하도록 코딩되어 있었음. 사용자는 '자동 승인' 알림을 받지만 정작 무엇이 승인되었는지는 전혀 알 수 없어 '본문 표시 자체가 안 된다'고 오해할 수밖에 없었음.
- **첫 알림 메시지 무시**: `step-probe.ts`에서 세션 전환 시 `lastNotifyStepIndex`를 초기화할 때 `-1` 로 리셋하지 않아 새 세션의 첫 안내 메시지가 매번 씹히는 증상 발견.
## 결정 사항
- `bot.py`의 Auto-Approve Embed 구문에 수동 승인처럼 본문을 표출하도록 렌더링 로직 통일.
- `observer-script.ts``TerminalCommand` 정규식에 `\b`를 추가하여 시스템 버튼과의 혼선을 원천 차단함.
- `step-probe.ts` 의 index reset 초기값을 `-1` 로 명시화.

View File

@@ -275,19 +275,19 @@ 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/i, type:'terminal_command'},
{re:/^(?:Always\s*)?Accept all/i, type:'diff_review'},
{re:/^(?:Always\s*)?Accept/i, type:'agent_step'},
{re:/^(?:Always\s*)?Allow/i, type:'permission'},
{re:/^(?:Always\s*)?Approve/i, type:'agent_step'},
{re:/^Retry/i, type:'error_recovery'},
{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 actionable button patterns (for grouping siblings in same container)
var ALL_ACTION_RE=[/^(?:Always\s*)?Run/i,/^(?:Always\s*)?Accept/i,/^Reject/i,/^(?:Always\s*)?Allow/i,/^Deny/i,/^(?:Always\s*)?Approve/i,/^Cancel$/i,/^Retry$/i,/^Dismiss$/i,/^Stop$/i,/^Decline$/i];
var ALL_ACTION_RE=[/^(?:Always\s*)?Run\b/i,/^(?:Always\s*)?Accept\b/i,/^Reject\b/i,/^(?:Always\s*)?Allow\b/i,/^Deny\b/i,/^(?:Always\s*)?Approve\b/i,/^Cancel\b/i,/^Retry\b/i,/^Dismiss\b/i,/^Stop\b/i,/^Decline\b/i];
// Reject button patterns for finding the counterpart
var REJECT_RE=[/^reject$/i,/^reject all$/i,/^cancel$/i,/^deny$/i,/^stop$/i,/^decline$/i,/^dismiss$/i];
var REJECT_RE=[/^reject\b/i,/^cancel\b/i,/^deny\b/i,/^stop\b/i,/^decline\b/i,/^dismiss\b/i];
// ── Stable button fingerprint (no getBoundingClientRect — scroll-safe) ──
function btnId(b,type){

View File

@@ -1,4 +1,4 @@
/**
/**
* Step Probe Module - SDK polling, response handling, approval strategies.
* All shared state accessed through BridgeContext.
* Extracted from extension.ts.
@@ -269,8 +269,8 @@ function setupMonitor() {
activeTrajectoryId = (bestSession as any).trajectoryId || '';
activeSessionTitle = currentTitle;
lastKnownStepCount = currentCount;
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
lastNotifyStepIndex = -1;
lastTaskStepIndex = -1;
lastUserInputStepIdx = bestSession.lastUserInputStepIndex ?? -1;
lastResponseCaptureStep = currentCount; // don't re-relay old responses
ctx.lastPendingStepIndex = -1;