"""Worker service — process scanning and SOUL.md reading.""" import logging import os from typing import Any import psutil import config logger = logging.getLogger(__name__) def get_running_processes() -> list[dict[str, Any]]: """Scan for opencode worker processes (cmdline contains both 'opencode' and 'run').""" processes: list[dict[str, Any]] = [] try: for proc in psutil.process_iter(["pid", "create_time", "cmdline"]): try: cmdline = proc.info.get("cmdline") or [] cmdline_str = " ".join(cmdline) if "opencode" in cmdline_str and "run" in cmdline_str: # Truncate cmdline to 200 chars if len(cmdline_str) > 200: cmdline_str = cmdline_str[:200] + "..." # Calculate elapsed time create_time = proc.info.get("create_time", 0) processes.append({ "pid": proc.info["pid"], "cmdline": cmdline_str, "create_time": create_time, }) except (psutil.NoSuchProcess, psutil.AccessDenied): continue except Exception as exc: logger.exception("get_running_processes failed") return processes def get_current_task() -> dict[str, Any] | None: """Find the currently running task (status=running) from kanban DB.""" try: from extensions import get_db db = get_db() row = db.execute( "SELECT * FROM tasks WHERE status = 'running' ORDER BY started_at DESC LIMIT 1" ).fetchone() if row: return dict(row) except Exception as exc: logger.debug("get_current_task failed: %s", exc) return None def get_soul_excerpt(max_chars: int = 2000) -> str: """Read first max_chars from SOUL.md.""" try: with open(config.SOUL_MD_PATH, "r", encoding="utf-8") as f: content = f.read(max_chars) return content except (FileNotFoundError, PermissionError, OSError) as exc: logger.debug("SOUL.md read failed: %s", exc) return f"(SOUL.md not available: {exc})" def get_worker_status() -> dict[str, Any]: """Aggregate worker status: current task, processes, soul excerpt.""" return { "current_task": get_current_task(), "processes": get_running_processes(), "soul_excerpt": get_soul_excerpt(500), } def get_soul_full() -> dict[str, str]: """Return first 2000 chars of SOUL.md.""" return {"content": get_soul_excerpt(2000)}