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:
@@ -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` 일치 여부 확인 필수
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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` | ✅ |
|
||||
|
||||
7
docs/devlog/2026-03-11.md
Normal file
7
docs/devlog/2026-03-11.md
Normal 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` | ✅ |
|
||||
@@ -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
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user