From bf7db72afb7f17bbd7a7dc41e42642699703e42d Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Wed, 8 Apr 2026 05:17:17 +0900 Subject: [PATCH] fix(ext): resolve ui freeze and saftoautorun burst send memory leak (v0.5.15) --- .agents/references/known-issues.md | 12 ++++++++++++ extension/package-lock.json | 4 ++-- extension/package.json | 4 ++-- extension/src/extension.ts | 20 +++++++++++--------- extension/src/step-probe.ts | 14 ++++++++++++++ 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index f993a22..e1c6e8b 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -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`와 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) 자체λ₯Ό κ²€μ‚¬ν•˜μ§€ μ•Šκ²Œ 됨. diff --git a/extension/package-lock.json b/extension/package-lock.json index 8a888f2..9d2c6b5 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -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" }, diff --git a/extension/package.json b/extension/package.json index 35fdce5..1f39e9c 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "gravity-bridge", "displayName": "Gravity Bridge", "description": "Antigravity ↔ Discord λΈŒλ¦¬μ§€ 연동 ν™•μž₯", - "version": "0.5.14", + "version": "0.5.15", "publisher": "variet", "engines": { "vscode": "^1.100.0" @@ -85,4 +85,4 @@ "dependencies": { "ws": "^8.19.0" } -} \ No newline at end of file +} diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 186505a..df83f79 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -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 { // 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 { // 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; diff --git a/extension/src/step-probe.ts b/extension/src/step-probe.ts index afc8ed7..19bc3ce 100644 --- a/extension/src/step-probe.ts +++ b/extension/src/step-probe.ts @@ -41,6 +41,7 @@ const PENDING_MEMORY_TTL_MS = 60_000; // generateApprovalObserverScript β†’ extracted to ./observer-script.ts const lastSnapshotText = new Map(); +const autoRunSteps = new Set(); /** * Get current approval context for WS response routing. @@ -333,6 +334,19 @@ 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); + const truncatedCmd = cmdText.length > 500 ? cmdText.substring(0, 500) + '...' : cmdText; + ctx.logToFile(`[AUTO-RUN] step=${actualIdx} captured`); + ctx.writeChatSnapshot(`πŸ€– **[Background Execution]**\n\n\`\`\`bash\n${truncatedCmd}\n\`\`\``); + } } catch { } } if (sType.includes('PLANNER_RESPONSE') && s?.status?.includes('DONE')) {