fix(extension): v0.5.4 신호 감지 3중 버그 수정 — 세션 전환 즉시 probe, reviewAbsoluteUris 필드, stepIndex uint32 clamp + permission 매핑

This commit is contained in:
Variet Worker
2026-03-21 17:50:45 +09:00
parent f4ded343c7
commit a72c522ab5
6 changed files with 55 additions and 10 deletions

View File

@@ -65,6 +65,24 @@
> v0.4.5 수정 사항(Hub pending_owners, diff_review WS, auto_approve 이중쓰기, WS dual-write, ApprovalView fallback)은
> 코드 수정 완료됨. E2E 통합 검증은 Vikunja #410에서 추적 중.
### [2026-03-21] stepIndex=-1 — AG proto uint32 에러
- **증상**: DOM observer가 Allow 버튼 감지 → Discord 승인 → RPC `HandleCascadeUserInteraction` 400 에러
- **원인**: DOM observer 경로는 step index를 모름 → `stepIndex=-1` 전달 → AG proto `uint32` 필드에 음수 불가
- **해결**: `Math.max(0, ...)` 로 clamp. `permission` type → `runExtensionCode.confirm` 매핑 추가 (v0.5.4)
- **주의**: DOM observer 경로의 step_type은 항상 `stepIndex=-1`일 수 있으므로 proto 전달 전 양수 보장 필수
### [2026-03-21] reviewAbsoluteUris — latestNotifyUserStep 필드명 불일치
- **증상**: `notify_user`의 PathsToReview 파일 릴레이가 한 번도 작동하지 않음
- **원인**: AG 실제 필드명 `reviewAbsoluteUris` vs 코드 `pathsToReview`/`paths_to_review`/`filePaths`
- **해결**: `reviewAbsoluteUris` 를 첫 번째 후보로 추가 (v0.5.3)
- **주의**: AG RPC 필드명은 extension.log `[NOTIFY-STEP] keys=` 로 확인 가능. 추측 금지
### [2026-03-21] 세션 전환 — 첫 WAITING 감지 20-25s 지연
- **증상**: 새 대화 시작 후 첫 run_command 승인이 Discord에 안 오고 AG에서 직접 승인해야 함
- **원인**: `lastModTime=''` 리셋 → `modTimeChanged=true` → THINKING 분기 반복 → probe 15-25s 지연
- **해결**: `lastModTime=currentModTime` + `return` 제거 + 즉시 probe 강제 + 회귀 가드 추가 (v0.5.3)
- **주의**: 세션 전환 시 `wasRunning`/`pendingModifiedFiles` 리셋 필수 (이전 세션 잔여물로 false diff_review 방지)
---
## 핵심 작업 규칙 (과거 이슈에서 반복된 패턴)
@@ -86,3 +104,4 @@
| 11 | **HttpBridgeContext에 프리미티브 by-value 복사 금지** — 별도 객체 생성 시 getter 사용 | HttpBridgeContext stale primitive |
| 12 | **새 AG 도구 추가 시 step-probe step_type 매핑 + approval-handler RPC payload 매핑 양쪽 필수** | browser_subagent Allow |
| 13 | **WS `onConnected`에서 step-probe 상태 리셋 필수**`stallProbed`/`lastPendingStepIndex`는 TTL 없는 영구 값 | Idle→Resume 신호 소실 |
| 14 | **AG proto `uint32` 필드에 음수 전달 금지**`stepIndex` 등은 `Math.max(0, ...)` 필수 | stepIndex=-1 RPC 400 |

View File

@@ -0,0 +1,5 @@
# 2026-03-21 Devlog
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 1 | 17:48 | v0.5.3~v0.5.4 신호 감지 3중 버그 수정: 세션 전환 즉시 probe (20-25s→5s), reviewAbsoluteUris 필드 수정, stepIndex=-1 uint32 에러 수정 + permission 매핑 | `0fb33a9` | ✅ |

View File

@@ -1,12 +1,12 @@
{
"name": "gravity-bridge",
"version": "0.4.3",
"version": "0.5.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gravity-bridge",
"version": "0.4.3",
"version": "0.5.4",
"dependencies": {
"ws": "^8.19.0"
},

View File

@@ -2,7 +2,7 @@
"name": "gravity-bridge",
"displayName": "Gravity Bridge",
"description": "Antigravity ↔ Discord 브리지 연동 확장",
"version": "0.5.2",
"version": "0.5.4",
"publisher": "variet",
"engines": {
"vscode": "^1.100.0"
@@ -85,4 +85,4 @@
"dependencies": {
"ws": "^8.19.0"
}
}
}

View File

@@ -313,7 +313,7 @@ async function processResponseFile(filePath: string) {
*/
export async function tryApprovalStrategies(approved: boolean, sessionId: string, stepType: string = '', stepIndex: number = -1): Promise<string> {
const action = approved ? 'APPROVE' : 'REJECT';
const effectiveStepIndex = stepIndex >= 0 ? stepIndex : ctx.lastPendingStepIndex;
const effectiveStepIndex = Math.max(0, stepIndex >= 0 ? stepIndex : ctx.lastPendingStepIndex);
ctx.logToFile(`[APPROVAL] Starting ${action} strategies for session ${sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${effectiveStepIndex}`);
// ── Dynamic Command Discovery (log what's available during WAITING state) ──
@@ -388,6 +388,10 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string
interactionPayload = { filePermission: { allow: true, scope } };
} else if (typeLower.includes('elicitation')) {
interactionPayload = { elicitation: {} };
} else if (typeLower === 'permission' || typeLower.includes('permission')) {
// DOM observer 'permission' type: browser_subagent Allow/Deny dialog
// Try runExtensionCode first (most common for JS execution permission)
interactionPayload = { runExtensionCode: { confirm: true } };
} else {
// Default: try run_command (most common)
interactionPayload = { runCommand: { confirm: true } };

View File

@@ -258,6 +258,8 @@ function setupMonitor() {
const currentTitle = (bestSession.summary || 'Untitled').substring(0, 50);
const isRunning = String(bestSession.status || '').includes('RUNNING');
const currentModTime = bestSession.lastModifiedTime || (bestSession as any).lastModifiedTimestamp || (bestSession as any).modifiedTime || '';
// Session changed?
if (bestSessionId !== ctx.activeSessionId) {
ctx.activeSessionId = bestSessionId;
@@ -270,6 +272,14 @@ function setupMonitor() {
lastResponseCaptureStep = currentCount; // don't re-relay old responses
ctx.lastPendingStepIndex = -1;
ctx.stallProbed = false;
ctx.sawRunningAfterPending = true; // prevent stale auto_resolve from previous session
consecutiveIdleCount = 0;
lastModTime = currentModTime; // use currentModTime → prevents THINKING branch on first poll
// Reset transition/diff state from previous session (regression guard for fall-through)
wasRunning = isRunning;
pendingModifiedFiles = [];
pendingModifiedFilePaths = [];
pendingEditStepIndices = [];
// Don't register here — registration happens lazily in ctx.writeChatSnapshot/writePendingApproval
// to avoid race conditions between multiple extension instances
// Dump session keys + trajectoryMetadata on session change
@@ -280,7 +290,9 @@ function setupMonitor() {
ctx.logToFile(`[SESSION-INIT] trajectoryMetadata=${JSON.stringify(trajMeta).substring(0, 500)}`);
}
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${ctx.activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
return;
// Fall through to WAITING detection — immediate probe on session change.
// Previously, `return` here caused 20-25s delay before first WAITING
// detection (confirmed via extension.log). Now we immediately check.
}
const delta = currentCount - lastKnownStepCount;
@@ -356,10 +368,15 @@ function setupMonitor() {
// ── STALL-BASED approval detection with step probe ──
const currentModTime = bestSession.lastModifiedTime || (bestSession as any).lastModifiedTimestamp || (bestSession as any).modifiedTime || '';
const modTimeChanged = currentModTime !== lastModTime;
const isStall = isRunning && delta === 0;
// Session-change immediate probe: bypass stall debounce on first poll
// Without this, consecutiveIdleCount=0 prevents probe condition (>=1)
if (isStall && consecutiveIdleCount === 0 && !ctx.stallProbed && !modTimeChanged) {
consecutiveIdleCount = 1; // force probe condition satisfied
}
// Log modTime on stalls for debugging
if (isStall && consecutiveIdleCount < 8) {
ctx.logToFile(`[STALL-DBG] idle=${consecutiveIdleCount} modTime='${currentModTime}' changed=${modTimeChanged}`);
@@ -661,9 +678,9 @@ function setupMonitor() {
}
// ── PathsToReview: read and relay referenced artifact files ──
const pathsToReview: string[] = notifyData.pathsToReview
|| notifyData.paths_to_review
|| notifyData.filePaths
// AG field name is `reviewAbsoluteUris` (confirmed via extension.log NOTIFY-STEP keys)
const pathsToReview: string[] = notifyData.reviewAbsoluteUris
|| notifyData.pathsToReview
|| [];
if (pathsToReview.length > 0) {
ctx.logToFile(`[NOTIFY-STEP] PathsToReview: ${pathsToReview.length} files`);