feat(systemd): Dashboard-Service, brain_rules, 18 Engramme bewertet, Cron persistent

Neu:
- systemd: secondbrain-dashboard.service (Port 8501, autostart)
- cron_rules.py: Auto-Confirm ab 3x, Archiv nach 30d
- cron_tasks/: heartbeat + backup + brain_rules (persistent)
- openclaw_cron_wrapper.py: subprocess-Isolation (kein SessionTakeover)
- chat_autosave.py: Auto-Save von Chat + Kontext-Anreicherung

Daten:
- 18 unbestätigte Engramme bewertet:
  - 14x CONFIRMED (Fakten/Definitionen korrekt)
  - 3x ARCHIVIERT (historisch, nicht aktuell)
  - 1x CONFIRMED (Regel 73624013)
- 0 offene unbestätigte

Closes Gitea-Issue: #9
This commit is contained in:
2026-05-25 22:35:44 +02:00
parent a5d5b2f2ec
commit 29bc45d623
5 changed files with 343 additions and 0 deletions

102
openclaw_cron_wrapper.py Normal file
View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
OpenClaw Cron Isolation Wrapper - Updatesicherer Workaround.
Persistent: Tasks und Logs liegen im Workspace, nicht in /tmp.
"""
import os
import sys
import json
import subprocess
import tempfile
from pathlib import Path
from datetime import datetime, timezone
# --- Konfiguration (persistent) ---
WORKSPACE = Path("/root/.openclaw/workspace")
CRON_TASKS_DIR = WORKSPACE / "cron_tasks"
LOG_FILE = WORKSPACE / "cron_wrapper.log"
BRAIN_DIR = WORKSPACE / "second-brain"
def log(msg: str):
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
line = f"[{ts}] {msg}\n"
with open(LOG_FILE, "a") as f:
f.write(line)
print(line.strip())
def run_isolated(task_name: str, task_args: dict = None) -> dict:
"""
Führt einen Task in echt isolierter Umgebung aus.
Kein Zugriff auf Session-Files, nur stdout/stderr.
"""
CRON_TASKS_DIR.mkdir(parents=True, exist_ok=True)
task_script = CRON_TASKS_DIR / f"{task_name}.py"
if not task_script.exists():
return {"success": False, "error": f"Task nicht gefunden: {task_script}"}
# Temp-Verzeichnis für Output (flüchtig ist OK, Ergebnis kommt via stdout)
temp_dir = tempfile.mkdtemp(prefix=f"cron_{task_name}_")
output_file = Path(temp_dir) / "output.json"
# Saubere Env: Keine OpenClaw-Session-Variablen
env = os.environ.copy()
for key in list(env.keys()):
if "OPENCLAW" in key.upper() or "SESSION" in key.upper():
del env[key]
env["CRON_TASK_NAME"] = task_name
env["CRON_OUTPUT_FILE"] = str(output_file)
env["BRAIN_DB"] = str(BRAIN_DIR / "data" / "brain.sqlite")
try:
result = subprocess.run(
[sys.executable, str(task_script)] + ([json.dumps(task_args)] if task_args else []),
capture_output=True,
text=True,
timeout=300,
cwd=str(temp_dir),
env=env,
)
stdout = result.stdout.strip()
stderr = result.stderr.strip()
output_data = {}
if output_file.exists():
try:
output_data = json.loads(output_file.read_text())
except Exception:
output_data = {"raw": output_file.read_text()}
return {
"success": result.returncode == 0,
"returncode": result.returncode,
"stdout": stdout[-2000:] if stdout else "",
"stderr": stderr[-1000:] if stderr else "",
"output": output_data,
}
except subprocess.TimeoutExpired:
return {"success": False, "error": "Timeout nach 300s"}
except Exception as e:
return {"success": False, "error": str(e)}
def main():
import argparse
parser = argparse.ArgumentParser(description="OpenClaw Cron Isolation Wrapper")
parser.add_argument("task", help="Task-Name aus cron_tasks/")
parser.add_argument("--args", help="JSON-Args für den Task")
args = parser.parse_args()
task_args = json.loads(args.args) if args.args else None
result = run_isolated(args.task, task_args)
print(json.dumps(result, indent=2, default=str))
return 0 if result["success"] else 1
if __name__ == "__main__":
sys.exit(main())