fix: cross-project event flooding + pending accumulation + diff_review brain exclusion

Phase 1: Collector auto-cleanup of auto_resolved/expired pending files after Gateway forwarding
Phase 2: Watcher project filter (only MY sessions emit events) + Collector event forward filter
Phase 3: Extension diff_review excludes brain/ artifact files (task.md, implementation_plan.md)
This commit is contained in:
Variet Worker
2026-03-16 23:05:27 +09:00
parent 7ca0bc0f1f
commit e3f8fb93f7
3 changed files with 172 additions and 37 deletions

View File

@@ -40,7 +40,11 @@ class BrainEvent:
class BrainEventHandler(FileSystemEventHandler):
"""Watchdog handler that filters, debounces, and deduplicates brain events."""
"""Watchdog handler that filters, debounces, and deduplicates brain events.
Phase 2 FIX: Only emits events for sessions belonging to the current project
(Config.PROJECT_NAME), using bridge/register/ files for session→project mapping.
"""
def __init__(self, event_queue: asyncio.Queue, loop: asyncio.AbstractEventLoop):
super().__init__()
@@ -49,6 +53,10 @@ class BrainEventHandler(FileSystemEventHandler):
self._last_events: dict[str, float] = {} # path -> timestamp (debounce)
self._content_hashes: dict[str, str] = {} # path -> md5 hash (dedup)
self._known_sessions: set[str] = set()
# Phase 2: project filter
self._session_project_map: dict[str, str] = {} # conv_id → project_name
self._project_map_ts: float = 0 # last load timestamp
self._PROJECT_MAP_TTL: float = 60.0 # reload every 60s
self._initialize_known_sessions()
def _initialize_known_sessions(self):
@@ -77,6 +85,47 @@ class BrainEventHandler(FileSystemEventHandler):
f"pre-loaded {hash_count} content hashes"
)
def _load_session_project_map(self) -> dict[str, str]:
"""Load session→project mapping from bridge/register/ files (cached)."""
now = time.time()
if now - self._project_map_ts < self._PROJECT_MAP_TTL:
return self._session_project_map
import json
register_dir = Config.BRAIN_PATH.parent / "bridge" / "register"
if not register_dir.exists():
self._project_map_ts = now
return self._session_project_map
new_map: dict[str, str] = {}
for f in register_dir.glob("*.json"):
try:
data = json.loads(f.read_text(encoding="utf-8-sig"))
conv_id = data.get("conversation_id", "")
project = data.get("project_name", "")
if conv_id and project:
new_map[conv_id] = project
except (json.JSONDecodeError, OSError):
pass
self._session_project_map = new_map
self._project_map_ts = now
return self._session_project_map
def _is_my_session(self, conv_id: str) -> bool:
"""Check if a session belongs to the current project.
Returns True for:
- Sessions registered to Config.PROJECT_NAME
- Unknown sessions (not in any register file — allow to avoid blocking)
Returns False for sessions registered to OTHER projects.
"""
session_map = self._load_session_project_map()
project = session_map.get(conv_id)
if project is None:
return True # Unknown → allow (newly started, not yet registered)
return project == Config.PROJECT_NAME
def dispatch(self, event: FileSystemEvent):
"""Early filter: skip events for files/dirs we don't care about.
@@ -169,6 +218,10 @@ class BrainEventHandler(FileSystemEventHandler):
if not conv_id:
return
# Phase 2 FIX: only emit events for MY project's sessions
if not self._is_my_session(conv_id):
return
# Exclude files in .system_generated subdirectory (AG internal logs)
try:
relative = path.relative_to(Config.BRAIN_PATH / conv_id)