atlas-iac/services/hermes/scripts/herdr_tab_router.py
2026-08-09 14:08:45 -03:00

160 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Turn interactive tabs in the Agent coordinator space into Hermes sessions."""
from __future__ import annotations
import argparse
import json
import os
import re
import shlex
import subprocess
import time
from pathlib import Path
from typing import Any, Callable
HERDR_BIN = Path("/opt/data/tools/bin/herdr")
WORKSPACE_ROOT = Path("/opt/data/workspace")
PROJECT_ROOT = WORKSPACE_ROOT / "projects"
def _slug(value: str) -> str:
"""Return a stable label accepted by Herdr's agent-name argument."""
normalized = re.sub(r"[^a-z0-9]+", "-", value.strip().lower()).strip("-")
return normalized or "session"
def project_for_label(label: str, project_root: Path = PROJECT_ROOT) -> Path | None:
"""Resolve a tab label to an existing isolated project directory."""
wanted = _slug(label)
if not project_root.is_dir():
return None
for candidate in project_root.iterdir():
if candidate.is_dir() and _slug(candidate.name) == wanted:
return candidate.resolve()
return None
def _run_json(command: list[str]) -> dict[str, Any]:
"""Run a Herdr command and return its JSON object response."""
completed = subprocess.run(
command,
text=True,
capture_output=True,
timeout=130,
check=False,
env=os.environ.copy(),
)
if completed.returncode != 0:
detail = completed.stderr.strip() or completed.stdout.strip()
raise RuntimeError(detail or f"command failed with {completed.returncode}")
try:
payload = json.loads(completed.stdout)
except json.JSONDecodeError as error:
raise RuntimeError("Herdr returned non-JSON output") from error
if not isinstance(payload, dict):
raise RuntimeError("Herdr returned an unexpected response")
return payload
def _result_list(payload: dict[str, Any], key: str) -> list[dict[str, Any]]:
"""Extract a typed list from a Herdr response."""
result = payload.get("result") or {}
values = result.get(key) or []
return [item for item in values if isinstance(item, dict)]
def find_workspace(
label: str,
run: Callable[[list[str]], dict[str, Any]] = _run_json,
) -> str | None:
"""Return the live workspace id for the named Agent coordinator space."""
payload = run([str(HERDR_BIN), "workspace", "list"])
matches = [
str(item.get("workspace_id") or "")
for item in _result_list(payload, "workspaces")
if item.get("label") == label
]
return next((item for item in matches if item), None)
def route_unmanaged_tabs(
workspace_id: str,
run: Callable[[list[str]], dict[str, Any]] = _run_json,
project_root: Path = PROJECT_ROOT,
) -> list[str]:
"""Start routed Hermes in every unmanaged pane of the coordinator space."""
tabs_payload = run([str(HERDR_BIN), "tab", "list"])
panes_payload = run(
[str(HERDR_BIN), "pane", "list", "--workspace", workspace_id]
)
tab_labels = {
str(item.get("tab_id") or ""): str(item.get("label") or "session")
for item in _result_list(tabs_payload, "tabs")
if item.get("workspace_id") == workspace_id
}
started: list[str] = []
for pane in _result_list(panes_payload, "panes"):
if pane.get("workspace_id") != workspace_id or pane.get("agent"):
continue
pane_id = str(pane.get("pane_id") or "")
if not pane_id:
continue
label = tab_labels.get(str(pane.get("tab_id") or ""), "session")
project = project_for_label(label, project_root)
if project is not None and Path(str(pane.get("foreground_cwd") or "")) != project:
run(
[
str(HERDR_BIN),
"pane",
"run",
pane_id,
f"cd {shlex.quote(str(project))}",
]
)
pane_suffix = _slug(pane_id.split(":")[-1])
agent_name = f"{_slug(label)}-{pane_suffix}"
run(
[
str(HERDR_BIN),
"agent",
"start",
agent_name,
"--kind",
"hermes",
"--pane",
pane_id,
"--timeout",
"120000",
]
)
started.append(pane_id)
return started
def run_loop(label: str, interval: float) -> None:
"""Continuously reconcile Agent tabs while allowing Herdr to own workers."""
while True:
try:
workspace_id = find_workspace(label)
if workspace_id:
for pane_id in route_unmanaged_tabs(workspace_id):
print(f"Started routed Hermes session in {pane_id}", flush=True)
except Exception as error:
print(f"Agent tab routing retry: {type(error).__name__}: {error}", flush=True)
time.sleep(interval)
def main() -> None:
"""Parse controller options and reconcile the Agent coordinator space."""
parser = argparse.ArgumentParser()
parser.add_argument("--workspace-label", default="coordinator")
parser.add_argument("--interval", type=float, default=1.0)
args = parser.parse_args()
run_loop(args.workspace_label, max(args.interval, 0.25))
if __name__ == "__main__":
main()