Compare commits
4 Commits
main
...
e5d45b589c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5d45b589c | ||
|
|
ab8df32742 | ||
|
|
bf7db72afb | ||
|
|
8a49320232 |
@@ -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) 자체를 검사하지 않게 됨.
|
||||
|
||||
@@ -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` | ✅ |
|
||||
|
||||
6
docs/devlog/2026-04-08.md
Normal file
6
docs/devlog/2026-04-08.md
Normal 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 배포 | ⏳ | ⏳ |
|
||||
@@ -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) 발생.
|
||||
|
||||
21
docs/devlog/entries/20260408-001.md
Normal file
21
docs/devlog/entries/20260408-001.md
Normal 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 배포 완료)
|
||||
17
docs/devlog/entries/20260408-002.md
Normal file
17
docs/devlog/entries/20260408-002.md
Normal 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 배포/로컬 설치 완료)
|
||||
4
extension/package-lock.json
generated
4
extension/package-lock.json
generated
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user