fix(extension): v0.5.4 신호 감지 3중 버그 수정 — 세션 전환 즉시 probe, reviewAbsoluteUris 필드, stepIndex uint32 clamp + permission 매핑
This commit is contained in:
@@ -65,6 +65,24 @@
|
|||||||
> v0.4.5 수정 사항(Hub pending_owners, diff_review WS, auto_approve 이중쓰기, WS dual-write, ApprovalView fallback)은
|
> v0.4.5 수정 사항(Hub pending_owners, diff_review WS, auto_approve 이중쓰기, WS dual-write, ApprovalView fallback)은
|
||||||
> 코드 수정 완료됨. E2E 통합 검증은 Vikunja #410에서 추적 중.
|
> 코드 수정 완료됨. 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 |
|
| 11 | **HttpBridgeContext에 프리미티브 by-value 복사 금지** — 별도 객체 생성 시 getter 사용 | HttpBridgeContext stale primitive |
|
||||||
| 12 | **새 AG 도구 추가 시 step-probe step_type 매핑 + approval-handler RPC payload 매핑 양쪽 필수** | browser_subagent Allow |
|
| 12 | **새 AG 도구 추가 시 step-probe step_type 매핑 + approval-handler RPC payload 매핑 양쪽 필수** | browser_subagent Allow |
|
||||||
| 13 | **WS `onConnected`에서 step-probe 상태 리셋 필수** — `stallProbed`/`lastPendingStepIndex`는 TTL 없는 영구 값 | Idle→Resume 신호 소실 |
|
| 13 | **WS `onConnected`에서 step-probe 상태 리셋 필수** — `stallProbed`/`lastPendingStepIndex`는 TTL 없는 영구 값 | Idle→Resume 신호 소실 |
|
||||||
|
| 14 | **AG proto `uint32` 필드에 음수 전달 금지** — `stepIndex` 등은 `Math.max(0, ...)` 필수 | stepIndex=-1 RPC 400 |
|
||||||
|
|||||||
5
docs/devlog/2026-03-21.md
Normal file
5
docs/devlog/2026-03-21.md
Normal 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` | ✅ |
|
||||||
4
extension/package-lock.json
generated
4
extension/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.4.3",
|
"version": "0.5.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.4.3",
|
"version": "0.5.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"displayName": "Gravity Bridge",
|
"displayName": "Gravity Bridge",
|
||||||
"description": "Antigravity ↔ Discord 브리지 연동 확장",
|
"description": "Antigravity ↔ Discord 브리지 연동 확장",
|
||||||
"version": "0.5.2",
|
"version": "0.5.4",
|
||||||
"publisher": "variet",
|
"publisher": "variet",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.100.0"
|
"vscode": "^1.100.0"
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ async function processResponseFile(filePath: string) {
|
|||||||
*/
|
*/
|
||||||
export async function tryApprovalStrategies(approved: boolean, sessionId: string, stepType: string = '', stepIndex: number = -1): Promise<string> {
|
export async function tryApprovalStrategies(approved: boolean, sessionId: string, stepType: string = '', stepIndex: number = -1): Promise<string> {
|
||||||
const action = approved ? 'APPROVE' : 'REJECT';
|
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}`);
|
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) ──
|
// ── 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 } };
|
interactionPayload = { filePermission: { allow: true, scope } };
|
||||||
} else if (typeLower.includes('elicitation')) {
|
} else if (typeLower.includes('elicitation')) {
|
||||||
interactionPayload = { 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 {
|
} else {
|
||||||
// Default: try run_command (most common)
|
// Default: try run_command (most common)
|
||||||
interactionPayload = { runCommand: { confirm: true } };
|
interactionPayload = { runCommand: { confirm: true } };
|
||||||
|
|||||||
@@ -258,6 +258,8 @@ function setupMonitor() {
|
|||||||
const currentTitle = (bestSession.summary || 'Untitled').substring(0, 50);
|
const currentTitle = (bestSession.summary || 'Untitled').substring(0, 50);
|
||||||
const isRunning = String(bestSession.status || '').includes('RUNNING');
|
const isRunning = String(bestSession.status || '').includes('RUNNING');
|
||||||
|
|
||||||
|
const currentModTime = bestSession.lastModifiedTime || (bestSession as any).lastModifiedTimestamp || (bestSession as any).modifiedTime || '';
|
||||||
|
|
||||||
// Session changed?
|
// Session changed?
|
||||||
if (bestSessionId !== ctx.activeSessionId) {
|
if (bestSessionId !== ctx.activeSessionId) {
|
||||||
ctx.activeSessionId = bestSessionId;
|
ctx.activeSessionId = bestSessionId;
|
||||||
@@ -270,6 +272,14 @@ function setupMonitor() {
|
|||||||
lastResponseCaptureStep = currentCount; // don't re-relay old responses
|
lastResponseCaptureStep = currentCount; // don't re-relay old responses
|
||||||
ctx.lastPendingStepIndex = -1;
|
ctx.lastPendingStepIndex = -1;
|
||||||
ctx.stallProbed = false;
|
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
|
// Don't register here — registration happens lazily in ctx.writeChatSnapshot/writePendingApproval
|
||||||
// to avoid race conditions between multiple extension instances
|
// to avoid race conditions between multiple extension instances
|
||||||
// Dump session keys + trajectoryMetadata on session change
|
// Dump session keys + trajectoryMetadata on session change
|
||||||
@@ -280,7 +290,9 @@ function setupMonitor() {
|
|||||||
ctx.logToFile(`[SESSION-INIT] trajectoryMetadata=${JSON.stringify(trajMeta).substring(0, 500)}`);
|
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'}`);
|
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;
|
const delta = currentCount - lastKnownStepCount;
|
||||||
@@ -356,10 +368,15 @@ function setupMonitor() {
|
|||||||
|
|
||||||
// ── STALL-BASED approval detection with step probe ──
|
// ── STALL-BASED approval detection with step probe ──
|
||||||
|
|
||||||
const currentModTime = bestSession.lastModifiedTime || (bestSession as any).lastModifiedTimestamp || (bestSession as any).modifiedTime || '';
|
|
||||||
const modTimeChanged = currentModTime !== lastModTime;
|
const modTimeChanged = currentModTime !== lastModTime;
|
||||||
const isStall = isRunning && delta === 0;
|
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
|
// Log modTime on stalls for debugging
|
||||||
if (isStall && consecutiveIdleCount < 8) {
|
if (isStall && consecutiveIdleCount < 8) {
|
||||||
ctx.logToFile(`[STALL-DBG] idle=${consecutiveIdleCount} modTime='${currentModTime}' changed=${modTimeChanged}`);
|
ctx.logToFile(`[STALL-DBG] idle=${consecutiveIdleCount} modTime='${currentModTime}' changed=${modTimeChanged}`);
|
||||||
@@ -661,9 +678,9 @@ function setupMonitor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── PathsToReview: read and relay referenced artifact files ──
|
// ── PathsToReview: read and relay referenced artifact files ──
|
||||||
const pathsToReview: string[] = notifyData.pathsToReview
|
// AG field name is `reviewAbsoluteUris` (confirmed via extension.log NOTIFY-STEP keys)
|
||||||
|| notifyData.paths_to_review
|
const pathsToReview: string[] = notifyData.reviewAbsoluteUris
|
||||||
|| notifyData.filePaths
|
|| notifyData.pathsToReview
|
||||||
|| [];
|
|| [];
|
||||||
if (pathsToReview.length > 0) {
|
if (pathsToReview.length > 0) {
|
||||||
ctx.logToFile(`[NOTIFY-STEP] PathsToReview: ${pathsToReview.length} files`);
|
ctx.logToFile(`[NOTIFY-STEP] PathsToReview: ${pathsToReview.length} files`);
|
||||||
|
|||||||
Reference in New Issue
Block a user