commits
tags
import sys
import threading
import uuid
from datetime import datetime
from pathlib import Path
from . import config
from .flow_engine import run_flow
_RUNS: dict[str, dict] = {}
_LOCK = threading.Lock()
def _append_log(run_id: str, message: str) -> None:
line = f"[{datetime.now().strftime('%H:%M:%S')}] {message}"
with _LOCK:
_RUNS[run_id]["logs"].append(line)
# stderr, inte stdout: MCP-servern (rpa/mcp_server.py) talar JSON-RPC över
# stdout och en körning startad därifrån får inte skriva dit.
print(f"[run {run_id}] {message}", file=sys.stderr)
def start_run(
flow,
close_mode: str = "always",
settings: dict | None = None,
initial_vars: dict | None = None,
entry_subflow: str | None = None,
) -> str:
"""`flow` = nod/kant-grafen ({nodes, edges, start}) eller gammalt {steps} /
en steglista (migreras internt). entry_subflow kör bara den lokala subgrafen
med det id:t (för att testa en subflow-flik)."""
run_id = uuid.uuid4().hex[:12]
close_event = threading.Event()
trace_path = config.TRACE_DIR / f"{run_id}.zip"
with _LOCK:
_RUNS[run_id] = {
"status": "running",
"logs": [],
"browser_open": True,
"close_event": close_event,
"trace_path": trace_path,
"trace_ready": False,
"vars": {},
}
def on_finished(error: Exception | None, vars_: dict) -> None:
with _LOCK:
_RUNS[run_id]["status"] = "error" if error else "done"
_RUNS[run_id]["vars"] = vars_
def target() -> None:
try:
run_flow(
flow,
log=lambda m: _append_log(run_id, m),
download_dir=config.DOWNLOAD_DIR,
close_mode=close_mode,
close_event=close_event,
on_finished=on_finished,
trace_path=trace_path,
settings=settings,
initial_vars=initial_vars,
entry_subflow=entry_subflow,
)
except Exception as exc: # noqa: BLE001
_append_log(run_id, f"Oväntat fel: {exc}")
with _LOCK:
_RUNS[run_id]["status"] = "error"
finally:
with _LOCK:
_RUNS[run_id]["browser_open"] = False
_RUNS[run_id]["trace_ready"] = trace_path.exists()
threading.Thread(target=target, daemon=True).start()
return run_id
def get_run(run_id: str) -> dict | None:
with _LOCK:
run = _RUNS.get(run_id)
if run is None:
return None
return {
"status": run["status"],
"logs": list(run["logs"]),
"browser_open": run["browser_open"],
"trace_ready": run["trace_ready"],
"vars": run["vars"],
}
def get_trace_path(run_id: str) -> Path | None:
with _LOCK:
run = _RUNS.get(run_id)
if run is None or not run["trace_ready"]:
return None
return run["trace_path"]
def close_browser(run_id: str) -> bool:
"""Signalerar till en väntande körning att den får stänga webbläsaren nu."""
with _LOCK:
run = _RUNS.get(run_id)
if run is None:
return False
run["close_event"].set()
return True