Compare commits

...

4 Commits

Author SHA1 Message Date
Variet Worker
e5d45b589c fix(bridge): disable pending generation for SafeToAutoRun 2026-04-08 05:52:20 +09:00
Variet Worker
ab8df32742 docs(changelog): write devlog entry 20260408-001 (task #583) 2026-04-08 05:18:41 +09:00
Variet Worker
bf7db72afb fix(ext): resolve ui freeze and saftoautorun burst send memory leak (v0.5.15) 2026-04-08 05:17:36 +09:00
Variet Worker
8a49320232 docs(devlog): record hash and Vikunja ID for session 001 2026-04-01 18:23:41 +09:00
11 changed files with 101 additions and 37 deletions

View File

@@ -29,6 +29,18 @@
## 🔴 Active/Recent Issues
### [2026-04-08] [VS Code Extension] 동기 프로세스(execSync)로 인한 UI 프리징
- **증상**: 익스텐션 활성화 시 `activate()` 내부에서 `detectProjectName()`가 호출되며 VS Code UI 전체가 최대 2초간 완전 멈춤(Freeze).
- **원인**: 확장 프로그램 구동 이벤트 루프에서 `cp.execSync('git remote get-url origin', { timeout: 2000 })`라는 무거운 동기 블로킹 연산을 수행.
- **해결** (v0.5.15): `.git/config` 설정 파일을 `fs.readFileSync`로 직접 파싱해 1ms 내에 안전하게 URL을 추출하도록 완전히 교체함.
- **주의**: VS Code Extension 개발 시 `activate`나 UI 스레드 상에서 `execSync` 등의 동기적 자식 프로세스 생성을 엄격히 금지.
### [2026-04-08] [Architecture] Discord Burst Rate Limit & 메모리 누수 방어(LRU)
- **증상**: `SafeToAutoRun` 구동 시 디스코드 자동 알림을 과거처럼 생명주기 단위 배열 비우기로 관리하면, WS 재연결 시 100여 개의 오래된 스텝이 일시(Burst) 발송되어 Discord Hub 60/10s Rate Limit을 초과, 영구 채널 파괴 위험 발생.
- **해결** (v0.5.15): `Set<number>`와 Size 1000 제약, 그리고 `Set.values().next().value`를 활용한 LRU Eviction 캐시로 리팩토링. 수명이 지나도 최근 스텝은 절대 잊지 않아 중복 발송을 원천 차단함.
- **주의**: 긴 큐나 상태 데이터를 다룰 때는 `clear()` 방식 대신 크기 제한이 있는 LRU 방식으로 안전망을 구축할 것.
### [2026-03-31] [step-probe] GetAllCascadeTrajectories 10-Item Hard Limit (Signal Drop)
- **증상**: `guitar_score` 등에서 활성화된 세션의 디스코드 승인 신호를 "계속해서" 잡지 못함. (WS 60초 타임아웃보다 더 치명적으로 신호가 아예 가지 않음)
- **원인**: Extension이 활성 세션을 찾기 위해 호출하는 `GetAllCascadeTrajectories` LS API가 `{}`(빈 인자)로 호출될 때, 기본적으로 **10개의 세션만 반환하는 하드 리밋(Pagination Limit)**이 걸려있음. 이로 인해 작업 내역이 누적되면 수많은 최신/진행 중 세션들이 10개 목록에서 밀려나 누락됨. 익스텐션은 세션이 없다고 판단해 강제로 `IDLE` 모드에 진입하며, 승인 대기열(WAITING) 자체를 검사하지 않게 됨.

View File

@@ -2,4 +2,4 @@
| NNN | HH:MM | 작업 설명 | `커밋해시` | ✅ 또는 🔧 |
|-------|-------|-----------|-------------|--------------|
| 001 | 18:22 | `step-probe` 10-Item Truncation/DoS 우회 (vsix v0.5.14) | `TBD` | ✅ |
| 001 | 18:22 | `step-probe` 10-Item Truncation/DoS 우회 (vsix v0.5.14) | `13f13ee` | ✅ |

View File

@@ -0,0 +1,6 @@
# 2026-04-08
| NNN | HH:MM | 작업 설명 | 커밋해시 | 상태 |
|---|---|---|---|---|
| 001 | 05:18 | Gravity Bridge 무결성 롤백, SafeToAutoRun 메모리 누수 방지(LRU), UI 프리징(execSync) 제거 및 v0.5.15 배포 | `bf7db72` | ✅ |
| 002 | 05:45 | SafeToAutoRun 명령어의 불필요한 Pending 파일 생성 방지 (Discord 중복 알림 차단) 및 v0.5.16 배포 | ⏳ | ⏳ |

View File

@@ -1,8 +1,8 @@
# step-probe Pagination 10-Item Truncation vs LS DoS 오류 수정
- **시간**: 2026-04-01 13:00~18:22
- **Commit**: `TBD`
- **Vikunja**: #N/A (임시 버그 픽스)
- **Commit**: `13f13ee`
- **Vikunja**: #567
## 결정 사항
- 기존 `v0.5.13`에서 `limit: 100`으로 Pagination Limit(기본 10개)을 우회하려 했으나, LS DB 스캔 및 거대한 JSON 파싱이 VS Code Event Loop 블로킹을 유발하여 UI 멈춤(DoS) 발생.

View File

@@ -0,0 +1,21 @@
# Gravity Bridge 무결성 롤백 및 동기화 이슈 해결
- **시간**: 2026-04-08 04:30~05:18
- **Commit**: `bf7db72`
- **Vikunja**: #583 → done
## 결정 사항
1. **`detectProjectName` 동기화 락 해제**
- 기존의 `cp.execSync('git remote ...')` 코드가 익스텐션 활성화(`activate`) 시점에 메인 스레드를 블로킹하여 VS Code 전체가 최대 2초간 멈추는 심각한 버그(Freeze)를 찾아냄.
- `.git/config` 파일을 비동기적으로(fs.readFileSync는 1ms 내외) 직접 파싱하도록 전환하여 무결성과 속도를 확보함. 하이픈 치환 로직도 제거하여 원본 프로젝트 명(`variet-llm`)이 디스코드 채널명과 일관되게 매칭되게 함.
2. **`SafeToAutoRun` LRU 사이즈 제한 캐시 도입**
- 본래 생명주기마다 `Set`을 비우려 했으나, 장기 실행 작업(수 분 이상 소요) 중 웹소켓 재접속 발생 시 100여 개의 오래된 스텝이 일괄 복사 전송(Burst Send)되며 Discord Hub의 60/10s Rate Limit을 타격시킬 위험성 발견.
- 1000개의 Max Size를 두고 초과 시 `.values().next().value`를 제거하는 LRU 방식으로 캐시 구현, 과거 최근 스텝을 절대 잊어버리지 않게 함으로써 디스코드 도배/영구 정지를 원천 봉쇄함.
3. **`fixLSConnection` 정규식 강화**
- Language Server 프로세스 매칭 시 `hint` 문자열 정규식을 `/[^a-z0-9]/gi`로 강화하여 공백, 대소문자뿐 아니라 모든 특수 구두점/인코딩을 무시한 채 순수 영숫자만으로 프로세스 식별을 100% 매칭하도록 만듦.
## 미완료
- 없음 (v0.5.15 .vsix 배포 완료)

View File

@@ -0,0 +1,17 @@
# Gravity Bridge 중복 메시지 알림(SafeToAutoRun) 이슈 해결
- **시간**: 2026-04-08 05:40~05:45
- **버전**: `v0.5.16`
## 작업 사항
1. **단백한 백그라운드 메시지 지원 (중복 알림 제거)**
- 문제: 배경 실행 명령어(`SafeToAutoRun: true`)가 찰나의 순간 동안 `WAITING`에 진입하면서 익스텐션이 Pending 파일을 생성하고, 이를 봇이 즉시 처리하며 `🤖 자동 승인됨`이라는 불필요한 메시지가 중복으로 발송되고 있었음. (프록시 단에서 `💬 AI 대화 내용: ⚡ 자동 실행됨` 기록을 자체 발송하고 있어 중복 됨)
- 스텝 프로브(`step-probe.ts`)가 WAITING 상태를 감지하더라도, 해당 명령어의 플래그에 `SafeToAutoRun`이 true로 존재하면 Pending 파일을 일체 생성하지 않도록 스킵 처리를 추가 (`if (!safeToAutoRun)`)
- 이를 통해 봇(`bot.py`)으로 불필요한 Pending 요청이 가지 않게 차단되었으며, `🤖 자동 승인됨` 텍스트 스팸이 완전히 해결됨.
2. **v0.5.15 잔재 코드 삭제**
- 어설프게 추가되었던 `🤖 [Background Execution]` 직접 Snapshot 생성 로직 삭제.
## 미완료
- 없음 (VSIX 배포/로컬 설치 완료)

View File

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

View File

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

View File

@@ -70,15 +70,17 @@ function detectProjectName(): string {
if (folders && folders.length > 0) {
const cwd = folders[0].uri.fsPath;
try {
const remoteUrl = cp.execSync('git remote get-url origin', {
cwd, encoding: 'utf-8', timeout: 2000, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore']
}).toString().trim();
const match = remoteUrl.match(/\/([^\/]+?)(?:\.git)?$/);
if (match && match[1]) {
return match[1].toLowerCase().replace(/[\s\-]+/g, '_');
const gitConfig = path.join(cwd, '.git', 'config');
if (fs.existsSync(gitConfig)) {
const cfg = fs.readFileSync(gitConfig, 'utf-8');
const match = cfg.match(/url\s*=\s*(.*?)\n/);
if (match && match[1]) {
const repo = match[1].trim().match(/\/([^\/]+?)(?:\.git)?$/);
if (repo && repo[1]) return repo[1].toLowerCase().replace(/[\s]+/g, '_');
}
}
} catch { }
return path.basename(cwd).toLowerCase().replace(/[\s\-]+/g, '_');
return path.basename(cwd).toLowerCase().replace(/[\s]+/g, '_');
}
return 'default';
}
@@ -219,7 +221,7 @@ export async function fixLSConnection(): Promise<boolean> {
// Generate the workspace hint the same way SDK does, but we'll match case-insensitively
const folder = folders[0].uri.fsPath;
const parts = folder.replace(/\\/g, '/').split('/');
const hint = parts.slice(-2).join('_').replace(/[-.\s]/g, '_').toLowerCase();
const hint = parts.slice(-2).join('_').replace(/[^a-z0-9]/gi, '').toLowerCase();
if (!hint) { logToFile('[LS-FIX] skipped: empty hint'); return false; }
@@ -255,7 +257,7 @@ export async function fixLSConnection(): Promise<boolean> {
// Match workspace_id arg against our hint
const wsMatch = line.match(/--workspace_id[= ](\S+)/i);
if (wsMatch) {
const wsid = wsMatch[1].toLowerCase();
const wsid = wsMatch[1].replace(/[^a-z0-9]/gi, '').toLowerCase();
if (wsid.includes(hint)) {
matchedLine = line;
break;

View File

@@ -41,6 +41,7 @@ const PENDING_MEMORY_TTL_MS = 60_000;
// generateApprovalObserverScript → extracted to ./observer-script.ts
const lastSnapshotText = new Map<string, string>();
const autoRunSteps = new Set<number>();
/**
* Get current approval context for WS response routing.
@@ -333,6 +334,17 @@ function setupMonitor() {
ctx.logToFile(`[DIFF-TRACK] + ${bn} (step ${actualIdx})`);
}
}
const safeToAutoRun = tcArgs.SafeToAutoRun === true || tcArgs.safeToAutoRun === true;
if (safeToAutoRun && !autoRunSteps.has(actualIdx)) {
if (autoRunSteps.size > 1000) {
const oldest = autoRunSteps.values().next().value;
if (oldest !== undefined) autoRunSteps.delete(oldest);
}
autoRunSteps.add(actualIdx);
const cmdText = tcArgs.CommandLine || tcArgs.command || tcArgs.Command || JSON.stringify(tcArgs);
ctx.logToFile(`[AUTO-RUN] step=${actualIdx} captured`);
}
} catch { }
}
if (sType.includes('PLANNER_RESPONSE') && s?.status?.includes('DONE')) {
@@ -482,9 +494,11 @@ function setupMonitor() {
const toolCall = oStep?.metadata?.toolCall;
const toolName = toolCall?.name || (oStep.type || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase();
let command = toolName;
let safeToAutoRun = false;
if (toolCall?.argumentsJson) {
try {
const args = JSON.parse(toolCall.argumentsJson);
safeToAutoRun = args.SafeToAutoRun === true || args.safeToAutoRun === true;
if (args.CommandLine) command = `${toolName}: ${args.CommandLine.substring(0, 1500)}`;
else if (args.TargetFile) command = `${toolName}: ${args.TargetFile}`;
else {
@@ -507,6 +521,8 @@ function setupMonitor() {
// Skip pending for workspace-less AG windows (project=default)
if (ctx.projectName === 'default') {
ctx.logToFile(`[STEP-PROBE] skip pending: ctx.projectName=default (no workspace)`);
} else if (safeToAutoRun) {
ctx.logToFile(`[STEP-PROBE] skip pending: SafeToAutoRun is true (step=${actualIndex})`);
} else {
// Always write pending — Bot decides auto-approve (prevents double-fire)
writePendingApproval({
@@ -544,11 +560,13 @@ function setupMonitor() {
const toolCall = step?.metadata?.toolCall;
const toolName = toolCall?.name || stepType.replace('CORTEX_STEP_TYPE_', '').toLowerCase();
let command = toolName;
let safeToAutoRun = false;
// Parse argumentsJson for command details
if (toolCall?.argumentsJson) {
try {
const args = JSON.parse(toolCall.argumentsJson);
safeToAutoRun = args.SafeToAutoRun === true || args.safeToAutoRun === true;
if (args.CommandLine) {
command = `${toolName}: ${args.CommandLine.substring(0, 1500)}`;
} else if (args.TargetFile) {
@@ -574,6 +592,8 @@ function setupMonitor() {
// Skip pending for workspace-less AG windows (project=default)
if (ctx.projectName === 'default') {
ctx.logToFile(`[STEP-PROBE] skip pending: ctx.projectName=default (no workspace)`);
} else if (safeToAutoRun) {
ctx.logToFile(`[STEP-PROBE] skip pending: SafeToAutoRun is true (step=${si})`);
} else {
// Always write pending — Bot decides auto-approve (prevents double-fire)
writePendingApproval({
@@ -626,9 +646,11 @@ function setupMonitor() {
const toolCall = oStep?.metadata?.toolCall;
const toolName = toolCall?.name || (oStep.type || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase();
let command = toolName;
let safeToAutoRun = false;
if (toolCall?.argumentsJson) {
try {
const args = JSON.parse(toolCall.argumentsJson);
safeToAutoRun = args.SafeToAutoRun === true || args.safeToAutoRun === true;
if (args.CommandLine) command = `${toolName}: ${args.CommandLine.substring(0, 1500)}`;
else if (args.TargetFile) command = `${toolName}: ${args.TargetFile}`;
else {
@@ -644,7 +666,11 @@ function setupMonitor() {
if (actualIndex > ctx.lastPendingStepIndex) ctx.lastPendingStepIndex = actualIndex;
lastPendingTime = Date.now();
ctx.sawRunningAfterPending = false;
if (ctx.projectName !== 'default') {
if (ctx.projectName === 'default') {
ctx.logToFile(`[STEP-PROBE] skip pending: ctx.projectName=default`);
} else if (safeToAutoRun) {
ctx.logToFile(`[STEP-PROBE] skip pending: SafeToAutoRun is true (step=${actualIndex})`);
} else {
writePendingApproval({
conversation_id: ctx.activeSessionId,
command,

View File

@@ -1,20 +0,0 @@
import zipfile, shutil, os
vsix = r"c:\Users\Variet-Worker\Desktop\gravity_control\extension\gravity-bridge-0.5.14.vsix"
dest = os.path.expanduser(r"~\.antigravity\extensions\variet.gravity-bridge-0.5.14")
tmp = r"C:\tmp\vsix_extract"
if os.path.exists(tmp):
shutil.rmtree(tmp)
os.makedirs(tmp, exist_ok=True)
with zipfile.ZipFile(vsix, 'r') as z:
z.extractall(tmp)
src = os.path.join(tmp, "extension")
if os.path.exists(dest):
shutil.rmtree(dest)
shutil.copytree(src, dest)
print(f"Installed to {dest}")
print("Files:", os.listdir(dest))