fix(extension): skip Antigravity ghost sessions in Fallback 2 to prevent trajectory not found infinite loop

This commit is contained in:
Variet Worker
2026-04-10 22:19:09 +09:00
parent a99c283656
commit b3825e1c8a
3 changed files with 102 additions and 85 deletions

View File

@@ -248,3 +248,6 @@
- **원인**: 1) UI 버튼 텍스트에 `keyboard_arrow_up` 등 머티리얼 아이콘 텍스트가 접착(`Always runkeyboard_arrow_up`)되어 정규식이 실패할 것을 우려해 단어 경계(`\b`)를 제거한 패치가 원인. 단어 경계가 사라지면서 `/Run/i` 패턴이 `Running1 command` 같은 다른 상태 텍스트 버튼에 오탐(False Positive)됨. 2) DOM 순서상 상태 텍스트 버튼이 앞서 있으므로 오탐된 버튼이 우선 클릭됨. - **원인**: 1) UI 버튼 텍스트에 `keyboard_arrow_up` 등 머티리얼 아이콘 텍스트가 접착(`Always runkeyboard_arrow_up`)되어 정규식이 실패할 것을 우려해 단어 경계(`\b`)를 제거한 패치가 원인. 단어 경계가 사라지면서 `/Run/i` 패턴이 `Running1 command` 같은 다른 상태 텍스트 버튼에 오탐(False Positive)됨. 2) DOM 순서상 상태 텍스트 버튼이 앞서 있으므로 오탐된 버튼이 우선 클릭됨.
- **해결**: `trigger-click` 로직 실행 전 버튼의 `textContent`에서 `keyboard_arrow_up` 등 알려진 꼬리 아이콘 문자열을 명시적으로 제거(strip)하고, 모든 트리거 정규식에 다시 단어 경계(`\b`)를 강제 삽입하여 오탐을 원천 차단함. - **해결**: `trigger-click` 로직 실행 전 버튼의 `textContent`에서 `keyboard_arrow_up` 등 알려진 꼬리 아이콘 문자열을 명시적으로 제거(strip)하고, 모든 트리거 정규식에 다시 단어 경계(`\b`)를 강제 삽입하여 오탐을 원천 차단함.
- **주의**: UI 요소를 DOM에서 긁어올 때는 텍스트에 숨겨진 아이콘/웹폰트 리거쳐(ligatures)가 없는지 검토해야 함. 패턴 매칭 시 꼬리표를 먼저 제거하고 명확한 경계를 부여할 것. - **주의**: UI 요소를 DOM에서 긁어올 때는 텍스트에 숨겨진 아이콘/웹폰트 리거쳐(ligatures)가 없는지 검토해야 함. 패턴 매칭 시 꼬리표를 먼저 제거하고 명확한 경계를 부여할 것.
### [2026-04-10] [Extension] Ghost Session Hijack & Infinite Polling Loop (trajectory not found)
- **증상**: 신규 작업 시 '신호안들어와' (Discord로 릴레이 안 됨). 로그에 500 error trajectory not found 무한 반복.\n- **원인**: Antigravity가 작업하면서 brain/에 36글자 폴더를 생성하는데, Cascade가 아니므로 GetCascadeTrajectorySteps에서 500 에러를 냅니다. 하지만 이전 신규 세션 유실 방지 패치가 이 Ghost 세션을 RUNNING으로 강제 등록하면서, 활성 세션(activeSessionId)을 탈취하고 무한 에러 루프에 빠지게 만들었습니다.\n- **해결**: step-probe.ts에서 폴백 등록 시 error message에 'trajectory not found'가 포함되면 Ghost 세션으로 간주해 강제 등록(continue)을 건너뛰게 하고, Stall Probe 에러 catch에서도 UTF-8 에러가 아니면 stallProbed=true를 주어 재시도 무한 루프를 완전히 끊어냈습니다.\n- **주의**: uuid 길이(36자)만으로 디렉토리를 식별할 때 Antigravity와 Google Agent가 모호해질 수 있으므로, 반드시 Backend 응답의 확실한 에러(trajectory not found) 메시지로 예외 판별을 해야 합니다.\n

View File

@@ -1,89 +1,89 @@
{ {
"name": "gravity-bridge", "name": "gravity-bridge",
"displayName": "Gravity Bridge", "displayName": "Gravity Bridge",
"description": "Discord-based unified approval system for Antigravity AI interactions.", "description": "Discord-based unified approval system for Antigravity AI interactions.",
"version": "0.5.27", "version": "0.5.28",
"publisher": "variet", "publisher": "variet",
"engines": { "engines": {
"vscode": "^1.100.0" "vscode": "^1.100.0"
},
"categories": [
"Other",
"Chat"
],
"activationEvents": [
"onStartupFinished"
],
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./ && node -e \"const fs=require('fs'),p=require('path');const s=p.join('src','sdk'),d=p.join('out','sdk');if(fs.existsSync(s)){fs.mkdirSync(d,{recursive:true});fs.readdirSync(s).forEach(f=>fs.copyFileSync(p.join(s,f),p.join(d,f)));console.log('SDK copied to out/sdk')};\"",
"watch": "tsc -watch -p ./"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/vscode": "^1.100.0",
"typescript": "^5.3.0"
},
"contributes": {
"chatParticipants": [
{
"id": "gravity-bridge.gravity",
"name": "gravity",
"fullName": "Gravity Bridge",
"description": "대화 히스토리를 Discord로 전송 + AI 제어",
"isSticky": false
}
],
"commands": [
{
"command": "gravityBridge.start",
"title": "Gravity Bridge: Start"
},
{
"command": "gravityBridge.stop",
"title": "Gravity Bridge: Stop"
},
{
"command": "gravityBridge.approve",
"title": "Gravity Bridge: Approve Pending"
},
{
"command": "gravityBridge.reject",
"title": "Gravity Bridge: Reject Pending"
},
{
"command": "gravityBridge.connect",
"title": "Gravity Bridge: Connect Session"
}
],
"configuration": {
"title": "Gravity Bridge",
"properties": {
"gravityBridge.bridgePath": {
"type": "string",
"default": "",
"description": "Bridge 디렉토리 경로 (기본: ~/.gemini/antigravity/bridge)"
}, },
"gravityBridge.projectName": { "categories": [
"type": "string", "Other",
"default": "", "Chat"
"description": "프로젝트 이름 (기본: git remote 레포명)" ],
"activationEvents": [
"onStartupFinished"
],
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./ \u0026\u0026 node -e \"const fs=require(\u0027fs\u0027),p=require(\u0027path\u0027);const s=p.join(\u0027src\u0027,\u0027sdk\u0027),d=p.join(\u0027out\u0027,\u0027sdk\u0027);if(fs.existsSync(s)){fs.mkdirSync(d,{recursive:true});fs.readdirSync(s).forEach(f=\u003efs.copyFileSync(p.join(s,f),p.join(d,f)));console.log(\u0027SDK copied to out/sdk\u0027)};\"",
"watch": "tsc -watch -p ./"
}, },
"gravityBridge.hubUrl": { "devDependencies": {
"type": "string", "@types/node": "^20.0.0",
"default": "", "@types/vscode": "^1.100.0",
"description": "WebSocket Hub URL (예: wss://your-server.com/ws)" "typescript": "^5.3.0"
}, },
"gravityBridge.registrationCode": { "contributes": {
"type": "string", "chatParticipants": [
"default": "", {
"description": "Hub 등록 코드 (서버에서 발급)" "id": "gravity-bridge.gravity",
} "name": "gravity",
} "fullName": "Gravity Bridge",
} "description": "?€???ˆìФ? ë¦¬ë¥?Discordë¡??„송 + AI ?œì–´",
}, "isSticky": false
"dependencies": { }
"ws": "^8.19.0" ],
} "commands": [
{
"command": "gravityBridge.start",
"title": "Gravity Bridge: Start"
},
{
"command": "gravityBridge.stop",
"title": "Gravity Bridge: Stop"
},
{
"command": "gravityBridge.approve",
"title": "Gravity Bridge: Approve Pending"
},
{
"command": "gravityBridge.reject",
"title": "Gravity Bridge: Reject Pending"
},
{
"command": "gravityBridge.connect",
"title": "Gravity Bridge: Connect Session"
}
],
"configuration": {
"title": "Gravity Bridge",
"properties": {
"gravityBridge.bridgePath": {
"type": "string",
"default": "",
"description": "Bridge ?”ë ‰? ë¦¬ 경로 (기본: ~/.gemini/antigravity/bridge)"
},
"gravityBridge.projectName": {
"type": "string",
"default": "",
"description": "?„로?<3F>트 ?´ë¦„ (기본: git remote ?ˆí<CB86>¬ëª?"
},
"gravityBridge.hubUrl": {
"type": "string",
"default": "",
"description": "WebSocket Hub URL (?? wss://your-server.com/ws)"
},
"gravityBridge.registrationCode": {
"type": "string",
"default": "",
"description": "Hub ?±ë¡<C3AB> 코드 (?œë²„?<3F>서 발급)"
}
}
}
},
"dependencies": {
"ws": "^8.19.0"
}
} }

View File

@@ -275,6 +275,18 @@ function setupMonitor() {
} }
} catch (e: any) { } catch (e: any) {
if (pollCount <= 30) ctx.logToFile(`[POLL] Fallback 2 error for sid=${sid}: ${e.message}`); if (pollCount <= 30) ctx.logToFile(`[POLL] Fallback 2 error for sid=${sid}: ${e.message}`);
// If trajectory explicitly does not exist, it might be an Antigravity or non-Cascade session directory.
if (e.message?.includes('trajectory not found')) {
continue;
}
// FIXED: known-issues "AI Response Missing for New Sessions" -> Force register to prevent session loss on proto/UTF-8 parse errors
allTraj.trajectorySummaries[sid] = {
status: 'CASCADE_RUN_STATUS_RUNNING',
stepCount: 1, // Assume progressing to allow loop delta>0 trigger
lastModifiedTime: new Date(brainDirs[i].time).toISOString(),
summary: 'Discovered via brain/ scan (Fallback Error)',
trajectoryMetadata: { workspaces: [{ workspaceFolderAbsoluteUri: ctx.workspaceUri.replace(/\\/g, '/') }] }
};
} }
} }
} }
@@ -772,6 +784,8 @@ function setupMonitor() {
ctx.logToFile(`[STEP-PROBE] UTF-8 offset also failed: ${oe.message?.substring(0, 100)}`); ctx.logToFile(`[STEP-PROBE] UTF-8 offset also failed: ${oe.message?.substring(0, 100)}`);
ctx.stallProbed = true; // permanent error — block retry loop; resets on delta>0 ctx.stallProbed = true; // permanent error — block retry loop; resets on delta>0
} }
} else {
ctx.stallProbed = true; // Not a UTF-8 error (e.g. trajectory not found), prevent infinite loop
} }
} }
} }