fix(brain): Trigger-Zeit UTC-konsistent, project_summary liest Hauptchat, Privatkopie-Regel

- Trigger-Zeit (Issue 4): fires_at wird beim Anlegen IMMER auf UTC normalisiert
  (naiv = lokale Wanduhrzeit Europe/Berlin → UTC, aware → astimezone). Passt zur
  UTC-Ausfuehrung in background.py → feuert zur gemeinten Ortszeit, zeitzonen-
  portabel. Tool-Desc + Prompt: fires_at = lokale Wanduhrzeit ohne Zone schreiben,
  Server rechnet um. Behebt den 2h-Versatz.
- project_summary (Issue 2): liest jetzt auch den Hauptchat (name='Hauptchat')
  und die Beschreibung sagt ARIA, es fuer 'schau mal in Projekt X / Hauptchat
  rein' / 'hol dir die Infos aus ...' zu nutzen.
- Privatkopie (Issue 3): IDENTITY_ANCHOR-Regel — YouTube-Downloads fuer Stefans
  PRIVATgebrauch sind eine Privatkopie (§53 UrhG), nicht reflexartig ablehnen/
  moralisieren; Urteil bleibt fuer Weiterverbreitung/kommerziell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 23:12:02 +02:00
co-authored by Claude Opus 4.8
parent 761217cb5a
commit 55938c3173
3 changed files with 76 additions and 26 deletions
+29 -19
View File
@@ -540,10 +540,13 @@ META_TOOLS = [
"fires_at": {
"type": "string",
"description": (
"Absoluter ISO-Timestamp UTC fuer feste Termine, z.B. "
"'2026-05-12T14:30:00Z'. Die aktuelle Zeit findest du im "
"System-Prompt unter '## Aktuelle Zeit'. Fuer relative Angaben "
"lieber `in_seconds` nutzen."
"Fester Termin als ISO-Timestamp. Schreib einfach die LOKALE "
"Wanduhrzeit, die Stefan meint, OHNE Zeitzone — z.B. 'um 14:30' "
"'2026-05-12T14:30:00'. Der Server rechnet sie selbst in UTC "
"um (naiv = Ortszeit Europe/Berlin). Nur wenn du explizit UTC "
"willst, haeng Z an ('...T12:30:00Z'). Die aktuelle Lokal-/UTC-"
"Zeit steht im System-Prompt unter '## Aktuelle Zeit'. Fuer "
"relative Angaben ('in 2 Stunden') lieber `in_seconds`."
),
},
"message": {"type": "string", "description": "Was soll bei der Erinnerung gesagt werden"},
@@ -1042,15 +1045,17 @@ META_TOOLS = [
"function": {
"name": "project_summary",
"description": (
"Fasst zusammen was zuletzt in einem Projekt passiert ist (letzte ~10 Turns). "
"Nutze zwingend wenn Stefan in ein altes Projekt einsteigt mit "
"'hol mich ab' / 'was war zuletzt' / 'erinner mich dran' — sonst "
"halluzinierst Du Inhalte die nicht da sind."
"Schau in einen ANDEREN Chat rein und fass zusammen was dort zuletzt "
"passiert ist (letzte ~12 Turns). Funktioniert fuer jedes Projekt (per "
"Name, Fuzzy-Match) UND fuer den Hauptchat (name='Hauptchat'). Nutze es "
"IMMER wenn Stefan sagt 'hol dir die Infos aus Projekt X', 'schau mal in "
"den Hauptchat/in Projekt Y rein', 'was war zuletzt bei ...', 'hol mich "
"ab' — sonst halluzinierst Du Inhalte die nicht da sind."
),
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Projekt-Name (Fuzzy-Match)."},
"name": {"type": "string", "description": "Projekt-Name (Fuzzy-Match) oder 'Hauptchat' fuer den Hauptthread."},
},
"required": ["name"],
},
@@ -2535,22 +2540,27 @@ class Agent:
pname = (arguments.get("name") or "").strip()
if not pname:
return "FEHLER: name ist Pflicht."
p = projects_mod.find_project(pname)
if not p:
return f"Kein Projekt '{pname}' gefunden."
# Letzte ~10 Turns des Projekts aus dem Conversation-Log
turns = [t for t in self.conversation.turns if t.project_id == p["id"]]
# Hauptchat (project_id="") explizit unterstuetzen — damit ARIA auch
# aus einem Projekt heraus in den Hauptthread reinschauen kann.
if pname.lower() in {"hauptchat", "hauptthread", "haupt", "main",
"mainchat", "haupt-chat", "hauptchat-thread"}:
turns = [t for t in self.conversation.turns if not t.project_id]
label, desc = "Hauptchat", "der Hauptthread (kein Projekt)"
else:
p = projects_mod.find_project(pname)
if not p:
return f"Kein Chat/Projekt '{pname}' gefunden (fuer den Hauptthread: name='Hauptchat')."
turns = [t for t in self.conversation.turns if t.project_id == p["id"]]
label, desc = p["name"], p.get("description", "(keine Beschreibung)")
if not turns:
return (f"Projekt '{p['name']}' existiert (id={p['id']}), aber im "
f"aktuellen Conversation-Window stehen noch keine Turns. "
f"Beschreibung: {p.get('description', '(keine)')}")
return (f"'{label}' hat im aktuellen Conversation-Window noch keine "
f"Turns. {desc}")
tail = turns[-12:]
summary_lines = []
for t in tail:
prefix = "Stefan" if t.role == "user" else "Du"
summary_lines.append(f"{prefix}: {t.content[:280]}")
preamble = (f"Projekt '{p['name']}'{p.get('description', '(keine Beschreibung)')}.\n"
f"Letzte {len(tail)} Turns:\n")
preamble = f"'{label}'{desc}.\nLetzte {len(tail)} Turns:\n"
return preamble + "\n".join(summary_lines)
if name == "project_end":
pname = (arguments.get("name") or "").strip()