From 93ecbf6c43fd6f41fa6765a32e36aaef76cfb30f Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 13 Jun 2026 22:08:23 +0200 Subject: [PATCH] fix(projects): ARIA-generierte Dateien dem aktiven Projekt zuordnen Bisher haben nur App-Uploads (msg_type == "file") ein Projekt-Tag bekommen. Dateien die ARIA waehrend des Turns selbst schreibt (via [FILE: /shared/ uploads/aria_xyz.pdf]-Marker) sind dem Hauptchat zugefallen, auch wenn Stefan in einem Projekt war. Fix: Beim Verarbeiten der ARIA-Antwort in _process_core_response wird die turn_project_id aus payload.projectId (ChatOut.project_id vom Brain) ge- nutzt um jede gefundene ARIA-Datei sofort zu taggen, bevor sie als file_from_aria broadcast wird. Helper-Split: - _tag_file_to_project(path, pid): pure Write, pid schon bekannt - _tag_file_to_active_project(path): Convenience-Wrapper, fragt Brain nach active project (genutzt vom App-Upload-Handler, der noch nichts vom Projekt-Kontext weiss) Co-Authored-By: Claude Opus 4.7 --- bridge/aria_bridge.py | 45 +++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py index c8d71e8..3ff04da 100644 --- a/bridge/aria_bridge.py +++ b/bridge/aria_bridge.py @@ -1005,17 +1005,10 @@ class ARIABridge: cleaned = re.sub(r"\n{3,}", "\n\n", cleaned) return cleaned, files, missing - def _tag_file_to_active_project(self, file_path: str) -> None: - """Holt vom Brain das aktive Projekt + schreibt file_path → project_id - in /shared/config/file_projects.json. Best-effort, fail-silent. - Wird vom File-Save-Handler nach erfolgreichem Schreiben aufgerufen.""" + def _tag_file_to_project(self, file_path: str, project_id: str) -> None: + """Schreibt file_path → project_id in /shared/config/file_projects.json. + Best-effort, fail-silent. project_id leer = Eintrag entfernen (Hauptchat).""" try: - brain_url = os.environ.get("BRAIN_URL", "http://aria-brain:8080") - with urllib.request.urlopen(f"{brain_url}/projects/status", timeout=5) as r: - data = json.loads(r.read()) - active_id = (data.get("active_id") or "").strip() - if not active_id: - return manifest_path = "/shared/config/file_projects.json" os.makedirs("/shared/config", exist_ok=True) try: @@ -1027,15 +1020,35 @@ class ARIABridge: manifest = {} except Exception: manifest = {} - manifest[file_path] = active_id + if project_id: + manifest[file_path] = project_id + else: + manifest.pop(file_path, None) tmp = manifest_path + ".tmp" with open(tmp, "w") as f: json.dump(manifest, f, indent=2, ensure_ascii=False) os.replace(tmp, manifest_path) - logger.info("[file-project] %s → %s", file_path, active_id) + logger.info("[file-project] %s → %s", file_path, project_id or "(main)") except Exception as exc: logger.warning("[file-project] tag failed (%s): %s", file_path, exc) + def _tag_file_to_active_project(self, file_path: str) -> None: + """Convenience: Brain nach aktivem Projekt fragen + taggen. + Wird vom App-Upload-Handler genutzt (dort wissen wir die Projekt-ID + noch nicht aus dem Payload — Stefan kann ja zwischen App-Upload und + Chat-Send das Projekt gewechselt haben). ARIA-eigene Dateien gehen + ueber _tag_file_to_project mit turn_project_id direkt.""" + try: + brain_url = os.environ.get("BRAIN_URL", "http://aria-brain:8080") + with urllib.request.urlopen(f"{brain_url}/projects/status", timeout=5) as r: + data = json.loads(r.read()) + active_id = (data.get("active_id") or "").strip() + if not active_id: + return + self._tag_file_to_project(file_path, active_id) + except Exception as exc: + logger.warning("[file-project] active-query failed (%s): %s", file_path, exc) + async def _broadcast_aria_file(self, file_info: dict) -> None: """ARIA hat eine Datei fuer den User erstellt — App+Diagnostic informieren.""" logger.info("[rvs] ARIA-Datei rausgeben: %s (%s, %dKB)", @@ -1194,7 +1207,15 @@ class ARIABridge: # Der Marker wird aus dem Antworttext entfernt (TTS soll ihn nicht # vorlesen) und parallel als file_from_aria-Event geschickt. text, aria_files, missing_files = self._extract_file_markers(text) + # ARIA-Dateien dem aktiven Projekt zuordnen (falls eines aktiv war). + # turn_project_id kommt vom Brain mit dem /chat-Response und reflektiert + # den Stand NACH dem Turn — passt fuer Dateien die ARIA waehrend des + # Turns geschrieben hat (sie sind „im selben Projekt entstanden"). + turn_pid = (payload.get("projectId") or "").strip() if isinstance(payload, dict) else "" for f in aria_files: + server_path = f.get("serverPath") + if turn_pid and server_path: + self._tag_file_to_project(server_path, turn_pid) await self._broadcast_aria_file(f) # Bei fehlenden Files: User informieren (sonst sieht er nur stille # Verluste — ARIA hat den Marker hingeschrieben aber das File nicht