79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
"""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)}
|