#!/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()