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
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
#!/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())
|