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:
102
openclaw_cron_wrapper.py
Normal file
102
openclaw_cron_wrapper.py
Normal 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())
|
||||
Reference in New Issue
Block a user