fix(bridge): RUNNING 세션 우선 선택 + IDLE 채널 자동 생성 제거
- extension: bestSession 선택에 2단계 비교 (RUNNING > IDLE, then modTime) - extension: [SESSION-FILTER] 진단 로그 + [projectName] 로그 접두사 - bot: pending_approval_scanner의 IDLE 프로젝트 자동 채널 생성 제거 - known-issues: 2개 항목 추가 (IDLE 고착, 채널 증식)
This commit is contained in:
@@ -339,3 +339,15 @@
|
|||||||
- **해결**: 세 write 도구를 file_permission 리스트에 추가 (2곳: offset/normal scan)
|
- **해결**: 세 write 도구를 file_permission 리스트에 추가 (2곳: offset/normal scan)
|
||||||
- **주의**: AG가 파일 접근 권한을 요청하는 모든 도구는 이 리스트에 포함되어야 함
|
- **주의**: AG가 파일 접근 권한을 요청하는 모든 도구는 이 리스트에 포함되어야 함
|
||||||
|
|
||||||
|
### [2026-03-10] bestSession IDLE 고착 — RUNNING 세션 못 잡는 버그
|
||||||
|
- **증상**: Deriva AG에서 새 대화 시작 → bridge가 구 IDLE 세션만 추적, 새 RUNNING 세션 무시
|
||||||
|
- **원인**: `bestSession` 선택이 `lastModifiedTime`만 비교. 구 IDLE 세션의 modTime이 더 최신이면 새 RUNNING 세션보다 우선 선택됨
|
||||||
|
- **해결**: 2단계 비교 — (1) RUNNING 세션이 IDLE보다 항상 우선, (2) 동일 상태 내에서 `lastModifiedTime` 최신 순
|
||||||
|
- **주의**: Reload Window로도 해결되지만 근본적으로는 RUNNING 우선 로직이 필요. `[SESSION-FILTER]` 진단 로그도 추가됨
|
||||||
|
|
||||||
|
### [2026-03-10] Bot IDLE 채널 자동 생성 — 불필요한 Discord 채널 증식
|
||||||
|
- **증상**: 봇 시작 시 IDLE 프로젝트까지 포함하여 모든 등록된 프로젝트의 채널을 자동 생성
|
||||||
|
- **원인**: `pending_approval_scanner`가 매 사이클마다 `conv_to_project` 전체를 순회하며 채널 생성
|
||||||
|
- **해결**: 자동 채널 생성 루프 제거. 채널은 실제 신호(snapshot/pending) 도착 시 `_get_channel()`로 on-demand 생성
|
||||||
|
- **주의**: `_get_channel()`은 snapshot scanner와 approval sender에서 이미 호출되므로 별도 사전 등록 불필요
|
||||||
|
|
||||||
|
|||||||
7
bot.py
7
bot.py
@@ -511,11 +511,8 @@ class GravityBot(commands.Bot):
|
|||||||
# Reload conv→project registrations each cycle
|
# Reload conv→project registrations each cycle
|
||||||
self._load_registrations()
|
self._load_registrations()
|
||||||
|
|
||||||
# Ensure channels exist for all registered projects
|
# Channels are created on-demand when actual signals arrive
|
||||||
for project in set(self.conv_to_project.values()):
|
# (via _get_channel in snapshot scanner / approval sender)
|
||||||
if project not in self.project_channels:
|
|
||||||
await self._get_channel(project)
|
|
||||||
logger.info(f"Auto-created channel for registered project: {project}")
|
|
||||||
|
|
||||||
requests = self.bridge.get_pending_requests()
|
requests = self.bridge.get_pending_requests()
|
||||||
for req in requests:
|
for req in requests:
|
||||||
|
|||||||
@@ -1459,6 +1459,21 @@ 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) as [string, any][]) {
|
||||||
|
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) as [string, any][]) {
|
for (const [sid, data] of Object.entries(allTraj.trajectorySummaries) as [string, any][]) {
|
||||||
// PRIMARY FILTER: Check workspace URI from trajectoryMetadata
|
// PRIMARY FILTER: Check workspace URI from trajectoryMetadata
|
||||||
const trajMeta = data.trajectoryMetadata;
|
const trajMeta = data.trajectoryMetadata;
|
||||||
@@ -1489,14 +1504,24 @@ 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) return;
|
if (!bestSession) {
|
||||||
|
if (pollCount <= 10 || pollCount % 12 === 0) {
|
||||||
|
logToFile(`[SESSION-FILTER] NO session matched! total=${sessionIds.length}`);
|
||||||
|
}
|
||||||
|
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');
|
||||||
|
|||||||
Reference in New Issue
Block a user