docs: devlog 013 + known-issues (Reload Window stale session, RUNNING 우선 선택, IDLE 채널)

This commit is contained in:
2026-03-10 22:21:32 +09:00
parent 6179c4d242
commit 4d780ec5e7
4 changed files with 58 additions and 11 deletions

View File

@@ -351,3 +351,10 @@
- **해결**: 자동 채널 생성 루프 제거. 채널은 실제 신호(snapshot/pending) 도착 시 `_get_channel()`로 on-demand 생성 - **해결**: 자동 채널 생성 루프 제거. 채널은 실제 신호(snapshot/pending) 도착 시 `_get_channel()`로 on-demand 생성
- **주의**: `_get_channel()`은 snapshot scanner와 approval sender에서 이미 호출되므로 별도 사전 등록 불필요 - **주의**: `_get_channel()`은 snapshot scanner와 approval sender에서 이미 호출되므로 별도 사전 등록 불필요
### [2026-03-10] Reload Window 후 세션 stale — GetAllCascadeTrajectories 미갱신
- **증상**: Reload Window 후 현재 대화의 세션이 `GetAllCascadeTrajectories`에 IDLE/구 stepCount로 고정. 새 메시지를 보내도 RUNNING으로 변경되지 않음
- **원인**: Reload Window 시 Extension은 리셋되지만 LS 프로세스는 유지됨. LS 내부 trajectory tracker가 Reload 이전 세션 상태를 캐시하고 갱신하지 않음
- **해결**: AG 완전 종료 → 재실행 (Full restart). Reload Window로는 해결 불가
- **주의**: Extension 코드 변경 후 배포 시 Reload Window 대신 Full restart 권장. 이건 AG LS 내부 동작이라 사용자 측에서 수정 불가

View File

@@ -14,3 +14,4 @@
| 010 | 18:00~18:30 | v0.3.7 — file_permission 3-button 주입 + active_project.lock 제거 (멀티프로젝트) | `27deb2a` | ✅ | | 010 | 18:00~18:30 | v0.3.7 — file_permission 3-button 주입 + active_project.lock 제거 (멀티프로젝트) | `27deb2a` | ✅ |
| 011 | 18:50~19:29 | v0.3.8 — workspace URI 기반 세션 필터링 (멀티프로젝트 격리 완성) | `ae91134` | ✅ | | 011 | 18:50~19:29 | v0.3.8 — workspace URI 기반 세션 필터링 (멀티프로젝트 격리 완성) | `ae91134` | ✅ |
| 012 | 19:30~20:35 | 크로스 프로젝트 response watcher 우회 수정 + file_permission write 도구 3-button 매핑 | `3b834e0` | ✅ | | 012 | 19:30~20:35 | 크로스 프로젝트 response watcher 우회 수정 + file_permission write 도구 3-button 매핑 | `3b834e0` | ✅ |
| 013 | 21:04~22:19 | Deriva 신호 진단 + RUNNING 세션 우선 선택 + IDLE 채널 자동 생성 제거 | `6179c4d` | ✅ |

View File

@@ -55,8 +55,10 @@ const crypto = __importStar(require("crypto"));
// ─── File-based logging (AI can read directly) ─── // ─── File-based logging (AI can read directly) ───
function logToFile(msg) { function logToFile(msg) {
const ts = new Date().toISOString().replace('T', ' ').substring(0, 19); const ts = new Date().toISOString().replace('T', ' ').substring(0, 19);
const line = `${ts} ${msg}`; // Include projectName prefix so shared log can distinguish which extension instance logged
console.log(`Gravity Bridge: ${msg}`); const prefix = projectName ? `[${projectName}]` : '';
const line = `${ts} ${prefix} ${msg}`;
console.log(`Gravity Bridge: ${prefix} ${msg}`);
try { try {
if (!bridgePath) if (!bridgePath)
return; return;
@@ -1481,6 +1483,19 @@ function setupMonitor() {
let bestModTime = ''; let bestModTime = '';
const regDir = path.join(bridgePath, 'register'); const regDir = path.join(bridgePath, 'register');
const normalizedWorkspace = workspaceUri.replace(/\\/g, '/').toLowerCase(); const normalizedWorkspace = workspaceUri.replace(/\\/g, '/').toLowerCase();
// ── DEBUG: Log all available sessions on every 12th poll ──
const sessionIds = Object.keys(allTraj.trajectorySummaries);
if (pollCount <= 3 || pollCount % 12 === 0) {
logToFile(`[SESSION-FILTER] total=${sessionIds.length} myWorkspace="${normalizedWorkspace}"`);
for (const [sid, data] of Object.entries(allTraj.trajectorySummaries)) {
const tm = data.trajectoryMetadata;
const wsRaw = tm?.workspaces?.[0]?.workspaceFolderAbsoluteUri || 'NO_META';
const status = String(data.status || '').replace('CASCADE_RUN_STATUS_', '');
const steps = data.stepCount || 0;
const modT = (data.lastModifiedTime || '').substring(11, 19);
logToFile(`[SESSION-FILTER] ${sid.substring(0, 8)} ws=${wsRaw.substring(wsRaw.lastIndexOf('/') + 1)} steps=${steps} ${status} mod=${modT}`);
}
}
for (const [sid, data] of Object.entries(allTraj.trajectorySummaries)) { for (const [sid, data] of Object.entries(allTraj.trajectorySummaries)) {
// PRIMARY FILTER: Check workspace URI from trajectoryMetadata // PRIMARY FILTER: Check workspace URI from trajectoryMetadata
const trajMeta = data.trajectoryMetadata; const trajMeta = data.trajectoryMetadata;
@@ -1512,14 +1527,23 @@ function setupMonitor() {
} }
} }
const modTime = data.lastModifiedTime || ''; const modTime = data.lastModifiedTime || '';
if (!bestSession || modTime > bestModTime) { const candidateRunning = String(data.status || '').includes('RUNNING');
const bestIsRunning = bestSession ? String(bestSession.status || '').includes('RUNNING') : false;
// Prefer RUNNING over IDLE, then latest modTime within same status tier
if (!bestSession
|| (candidateRunning && !bestIsRunning)
|| (candidateRunning === bestIsRunning && modTime > bestModTime)) {
bestSession = data; bestSession = data;
bestSessionId = sid; bestSessionId = sid;
bestModTime = modTime; bestModTime = modTime;
} }
} }
if (!bestSession) if (!bestSession) {
if (pollCount <= 10 || pollCount % 12 === 0) {
logToFile(`[SESSION-FILTER] NO session matched! total=${sessionIds.length}`);
}
return; return;
}
const currentCount = bestSession.stepCount || 0; const currentCount = bestSession.stepCount || 0;
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');
@@ -1739,7 +1763,7 @@ function setupMonitor() {
conversation_id: activeSessionId, conversation_id: activeSessionId,
command, command,
description: `Step #${actualIndex} (${(oStep.type || '').replace('CORTEX_STEP_TYPE_', '')})`, description: `Step #${actualIndex} (${(oStep.type || '').replace('CORTEX_STEP_TYPE_', '')})`,
step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search'].includes(toolName) ? 'file_permission' : toolName, step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search', 'replace_file_content', 'write_to_file', 'multi_replace_file_content'].includes(toolName) ? 'file_permission' : toolName,
step_index: actualIndex, step_index: actualIndex,
source: 'step_probe_offset', source: 'step_probe_offset',
}); });
@@ -1795,7 +1819,7 @@ function setupMonitor() {
conversation_id: activeSessionId, conversation_id: activeSessionId,
command, command,
description, description,
step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search'].includes(toolName) ? 'file_permission' : toolName, step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search', 'replace_file_content', 'write_to_file', 'multi_replace_file_content'].includes(toolName) ? 'file_permission' : toolName,
step_index: si, step_index: si,
source: 'step_probe', source: 'step_probe',
}); });
@@ -2101,6 +2125,17 @@ function setupResponseWatcher() {
} }
catch { } catch { }
} }
else {
// Pending file missing (deleted or auto_resolved) — check response data itself
try {
const respData = JSON.parse(fs.readFileSync(fp, 'utf-8'));
if (respData.project_name && respData.project_name !== projectName) {
logToFile(`[RESPONSE] skip (from resp data) ${rid} (project=${respData.project_name}, we=${projectName})`);
return;
}
}
catch { }
}
setTimeout(() => processResponseFile(fp), 300); setTimeout(() => processResponseFile(fp), 300);
} }
} }
@@ -2213,14 +2248,18 @@ async function processResponseFile(filePath) {
} }
else if (isDomObserver) { else if (isDomObserver) {
// DOM observer path: ALSO try RPC strategies (renderer click is unreliable) // DOM observer path: ALSO try RPC strategies (renderer click is unreliable)
logToFile(`[RESPONSE] dom_observer → tryApprovalStrategies(${approved}, ${activeSessionId.substring(0, 8)}, type=${pendingStepType}, step=${pendingStepIndex})`); // Use sessionId from pending file if available, fallback to activeSessionId
const strategyResult = await tryApprovalStrategies(approved, activeSessionId, pendingStepType, pendingStepIndex); const targetSession = sessionId || activeSessionId;
logToFile(`[RESPONSE] dom_observer → tryApprovalStrategies(${approved}, ${targetSession.substring(0, 8)}, type=${pendingStepType}, step=${pendingStepIndex})`);
const strategyResult = await tryApprovalStrategies(approved, targetSession, pendingStepType, pendingStepIndex);
logToFile(`[RESPONSE] dom strategy result: ${strategyResult}`); logToFile(`[RESPONSE] dom strategy result: ${strategyResult}`);
} }
else { else {
// Step probe path: run ALL approval strategies // Step probe path: run ALL approval strategies
logToFile(`[RESPONSE] step_probe → tryApprovalStrategies(${approved}, ${activeSessionId.substring(0, 8)}, type=${pendingStepType}, step=${pendingStepIndex})`); // Use sessionId from pending file if available, fallback to activeSessionId
const strategyResult = await tryApprovalStrategies(approved, activeSessionId, pendingStepType, pendingStepIndex); const targetSession = sessionId || activeSessionId;
logToFile(`[RESPONSE] step_probe → tryApprovalStrategies(${approved}, ${targetSession.substring(0, 8)}, type=${pendingStepType}, step=${pendingStepIndex})`);
const strategyResult = await tryApprovalStrategies(approved, targetSession, pendingStepType, pendingStepIndex);
logToFile(`[RESPONSE] strategy result: ${strategyResult}`); logToFile(`[RESPONSE] strategy result: ${strategyResult}`);
} }
logToFile(`[RESPONSE] ${approved ? 'approve' : 'reject'} done (${isDomObserver ? 'dom' : 'step_probe'})`); logToFile(`[RESPONSE] ${approved ? 'approve' : 'reject'} done (${isDomObserver ? 'dom' : 'step_probe'})`);

File diff suppressed because one or more lines are too long