fix(config,extension): BRAIN_PATH 빈문자열 버그 + 크로스프로젝트 DEDUP MERGE 수정

- config.py: os.getenv BRAIN_PATH 빈값 시 CWD 해석 → or 패턴으로 수정
- extension.ts: writePendingApproval DEDUP에 project_name 가드 3곳 추가
- extension.ts: HTTP /pending file_permission dedup에도 project_name 가드
- known-issues: 2건 추가 (BRAIN_PATH, DEDUP MERGE)
- devlog: 2026-03-11 생성
This commit is contained in:
Variet Worker
2026-03-11 09:36:55 +09:00
parent 71aa80d144
commit 1696a2976b
7 changed files with 39 additions and 12 deletions

View File

@@ -374,3 +374,16 @@
- **원인**: SDK `_findLSProcess()`가 workspace hint(`desktop_variet_agent`, 소문자)를 LS 프로세스 command line의 `Desktop_variet_agent`(대문자 D)과 `String.includes()`(대소문자 구분)로 비교 → 매치 실패 → 첫 번째 LS(gravity_control)로 fallback → variet-agent 세션 없음. 각 AG 창은 **별도의 LS 프로세스**를 가지며(workspace_id로 구분), SDK가 잘못된 LS에 연결하면 해당 창의 세션을 볼 수 없음
- **해결**: `extension.ts``fixLSConnection()` 함수 추가. SDK init 후 PowerShell로 모든 LS 프로세스를 조회, `--workspace_id`를 **대소문자 무시 비교**하여 올바른 LS 발견 → `sdk.ls.setConnection(port, csrfToken)`으로 재연결
- **주의**: 각 AG 창마다 별도 LS 프로세스 존재 확인됨 (workspace_id로 구분). SDK `_getWorkspaceHint()``.toLowerCase()` 적용하지만 검색 대상(LS command line)은 원본 대소문자를 유지하여 불일치 발생. 이 fix는 모든 multi-window AG 환경에서 필수
### [2026-03-11] config.py BRAIN_PATH — `.env` 빈 문자열 → CWD 해석 버그
- **증상**: 봇이 Extension의 snapshot/pending을 전혀 읽지 못함. 로그에 `Brain path: .`, `Bridge protocol initialized: bridge` (상대경로)
- **원인**: `.env``BRAIN_PATH=` (빈 값)이 있으면 `os.getenv("BRAIN_PATH", default)`가 빈 문자열 `""`를 반환 (default 미사용). `Path("")``Path(".")` → CWD로 해석. 봇의 bridge 디렉토리가 `CWD/bridge/`가 되어 Extension의 `~/.gemini/antigravity/bridge/`**다른 경로**
- **해결**: `os.getenv("BRAIN_PATH") or default` 패턴으로 빈 문자열도 처리
- **주의**: `os.getenv(key, default)`는 key가 존재하면 (빈 값이라도) default를 사용하지 않음. 환경변수의 빈 값을 fallback 처리하려면 반드시 `or` 사용
### [2026-03-11] Extension DEDUP MERGE — 크로스 프로젝트 pending 오염
- **증상**: `#ag-lifetimepd` 채널에 variet_agent의 `write_to_file` 승인 요청이 표시됨
- **원인**: `writePendingApproval()`의 DEDUP 로직이 `project_name`을 체크하지 않음. LifetimePD DOM observer가 "Deny" pending 생성 → 7초 후 variet_agent step_probe가 **같은 pending에 MERGE** → command가 덮어씌워짐. pending의 `project_name: lifetimepd`는 유지되어 잘못된 채널로 라우팅
- **해결**: 3곳 dedup 조건에 `existing.project_name === projectName` 가드 추가: (1) MERGE, (2) step_index 중복 스킵, (3) file_permission 중복 필터
- **주의**: 모든 Extension 인스턴스가 **동일한 `bridge/pending/` 디렉토리**를 공유하므로, pending 파일 간 상호작용 시 반드시 `project_name` 일치 여부 확인 필수

View File

@@ -16,10 +16,11 @@ class Config:
DISCORD_GUILD_ID: int = int(os.getenv("DISCORD_GUILD_ID") or "0")
# Antigravity Brain path
BRAIN_PATH: Path = Path(os.getenv(
"BRAIN_PATH",
os.path.expanduser("~/.gemini/antigravity/brain")
))
# NOTE: os.getenv returns "" (not None) when .env has BRAIN_PATH= (empty value).
# Path("") resolves to "." (CWD), which is WRONG. Use `or` to handle both None and "".
BRAIN_PATH: Path = Path(
os.getenv("BRAIN_PATH") or os.path.expanduser("~/.gemini/antigravity/brain")
)
# Watcher settings
DEBOUNCE_SECONDS: float = float(os.getenv("DEBOUNCE_SECONDS", "5"))

View File

@@ -16,4 +16,4 @@
| 012 | 19:30~20:35 | 크로스 프로젝트 response watcher 우회 수정 + file_permission write 도구 3-button 매핑 | `3b834e0` | ✅ |
| 013 | 21:04~22:19 | Deriva 신호 진단 + RUNNING 세션 우선 선택 + IDLE 채널 자동 생성 제거 | `6179c4d` | ✅ |
| 014 | 22:23~22:47 | SDK LS 프로세스 대소문자 매칭 버그 수정 — variet-agent 신호 미도달 해결 | `21fd309` | ✅ |
| 015 | 23:46~23:57 | v0.3.9 — SDK JS 파일 VSIX 미포함 수정 + start_bot.bat Python 경로 우선순위 | | ✅ |
| 015 | 23:46~23:57 | v0.3.9 — SDK JS 파일 VSIX 미포함 수정 + start_bot.bat Python 경로 우선순위 | `71aa80d` | ✅ |

View File

@@ -0,0 +1,7 @@
# 2026-03-11 Devlog
| # | 시간 | 작업 설명 | 커밋 | 상태 |
|---|------|----------|------|------|
| 001 | 00:00~00:20 | Discord 릴레이 미작동 진단 — config.py BRAIN_PATH 빈문자열 버그 수정 | `pending` | ✅ |
| 002 | 00:20~01:05 | 크로스 프로젝트 pending DEDUP MERGE 버그 진단 및 수정 (project_name 가드 3곳) | `pending` | ✅ |
| 003 | 09:25~09:33 | Auto-approve 기능 감사 (미구현 확인) + Vikunja 태스크 #304, #305 등록 | `pending` | ✅ |

View File

@@ -652,7 +652,8 @@ function startObserverHttpBridge() {
const existingFiles = fs.readdirSync(pendingDir).filter((f) => f.endsWith('.json'));
for (const ef of existingFiles) {
const existing = JSON.parse(fs.readFileSync(path.join(pendingDir, ef), 'utf-8'));
if (existing.step_type === 'file_permission' && existing.status === 'pending') {
if (existing.step_type === 'file_permission' && existing.status === 'pending'
&& existing.project_name === projectName) {
const age = nowMs - (existing.timestamp * 1000);
if (age < 10_000 && age >= 0) {
logToFile(`[HTTP] filtered duplicate file_permission (${age}ms old): ${ef}`);
@@ -2557,7 +2558,8 @@ function writePendingApproval(data) {
for (const ef of existingFiles) {
const efPath = path.join(pendingDir, ef);
const existing = JSON.parse(fs.readFileSync(efPath, 'utf-8'));
if (existing.source === 'dom_observer' && existing.status === 'pending') {
if (existing.source === 'dom_observer' && existing.status === 'pending'
&& existing.project_name === projectName) { // CRITICAL: same project only
const age = nowMs - (existing.timestamp * 1000);
if (age < DEDUP_WINDOW_MS && age >= 0) {
// MERGE: update DOM observer pending with detailed step_probe info
@@ -2574,7 +2576,8 @@ function writePendingApproval(data) {
}
}
// Dedup: skip if step_probe already created pending for same step_index (within window)
if (existing.status === 'pending' && data.step_index !== undefined && existing.step_index === data.step_index) {
if (existing.status === 'pending' && existing.project_name === projectName
&& data.step_index !== undefined && existing.step_index === data.step_index) {
const age = nowMs - (existing.timestamp * 1000);
if (age < DEDUP_WINDOW_MS && age >= 0) {
logToFile(`[DEDUP] skip: step_index ${data.step_index} already pending in ${ef}`);

File diff suppressed because one or more lines are too long

View File

@@ -653,7 +653,8 @@ function startObserverHttpBridge(): Promise<number> {
const existingFiles = fs.readdirSync(pendingDir).filter((f: string) => f.endsWith('.json'));
for (const ef of existingFiles) {
const existing = JSON.parse(fs.readFileSync(path.join(pendingDir, ef), 'utf-8'));
if (existing.step_type === 'file_permission' && existing.status === 'pending') {
if (existing.step_type === 'file_permission' && existing.status === 'pending'
&& existing.project_name === projectName) {
const age = nowMs - (existing.timestamp * 1000);
if (age < 10_000 && age >= 0) {
logToFile(`[HTTP] filtered duplicate file_permission (${age}ms old): ${ef}`);
@@ -2517,7 +2518,8 @@ function writePendingApproval(data: { conversation_id: string; command: string;
for (const ef of existingFiles) {
const efPath = path.join(pendingDir, ef);
const existing = JSON.parse(fs.readFileSync(efPath, 'utf-8'));
if (existing.source === 'dom_observer' && existing.status === 'pending') {
if (existing.source === 'dom_observer' && existing.status === 'pending'
&& existing.project_name === projectName) { // CRITICAL: same project only
const age = nowMs - (existing.timestamp * 1000);
if (age < DEDUP_WINDOW_MS && age >= 0) {
// MERGE: update DOM observer pending with detailed step_probe info
@@ -2532,7 +2534,8 @@ function writePendingApproval(data: { conversation_id: string; command: string;
}
}
// Dedup: skip if step_probe already created pending for same step_index (within window)
if (existing.status === 'pending' && data.step_index !== undefined && existing.step_index === data.step_index) {
if (existing.status === 'pending' && existing.project_name === projectName
&& data.step_index !== undefined && existing.step_index === data.step_index) {
const age = nowMs - (existing.timestamp * 1000);
if (age < DEDUP_WINDOW_MS && age >= 0) {
logToFile(`[DEDUP] skip: step_index ${data.step_index} already pending in ${ef}`);