feat: live per-agent terminal activity with real scripts/SQL
Agent terminals were idle (one-shot probe) while agents were busy in the background. Now every agent streams what it is actually doing: - agent_terminal: emit_threadsafe() so background threads can stream lines. - agent_ops: Data Custodian DML loop logs the real INSERT/UPDATE/DELETE SQL (+ Mongo ops) and Hadoop-offload Trino CTAS/INSERT to its terminal; ETL Guardian announces each orchestrated movement. - movements: trigger_and_watch streams the Airflow DAG / API call, conf, before/after Trino counts and result to the owning agent terminal. - etl_offload: per-dataset read + pyarrow->S3 parquet writes and cycle summaries stream to the ETL Guardian terminal. - agent_activity (new): round-robin live probes for Lakehouse Ops, Hadoop Ranger (NameNode JMX + YARN), Infra Sentinel (Dockhand inventory + host load) and Network Watcher (VLAN 20/21 path checks). - fix: YARN ResourceManager runs on 10.0.21.62:8088 (was .61). - ui: terminal dock merges the selected agent ops stream with the node probe.
This commit is contained in:
+33
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import uuid
|
||||
from collections import deque
|
||||
from datetime import datetime, timezone
|
||||
@@ -13,6 +14,7 @@ PublishFn = Callable[[dict[str, Any]], Awaitable[None]]
|
||||
|
||||
_buffers: dict[str, deque[dict[str, Any]]] = {}
|
||||
_publish: PublishFn | None = None
|
||||
_loop: asyncio.AbstractEventLoop | None = None
|
||||
|
||||
|
||||
def init_terminals(agent_ids: list[str]) -> None:
|
||||
@@ -22,8 +24,38 @@ def init_terminals(agent_ids: list[str]) -> None:
|
||||
|
||||
|
||||
def set_terminal_publisher(fn: PublishFn) -> None:
|
||||
global _publish
|
||||
global _publish, _loop
|
||||
_publish = fn
|
||||
try: # capture the main event loop so background threads can stream too
|
||||
_loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
_loop = None
|
||||
|
||||
|
||||
def _build_line(agent_id: str, text: str, level: str, phase: str, prompt_id: str | None) -> dict[str, Any]:
|
||||
return {
|
||||
"id": str(uuid.uuid4())[:8],
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"agent_id": agent_id,
|
||||
"level": level,
|
||||
"phase": phase,
|
||||
"text": text,
|
||||
"prompt_id": prompt_id,
|
||||
}
|
||||
|
||||
|
||||
def emit_threadsafe(agent_id: str, text: str, *, level: str = "info", phase: str = "ops") -> dict[str, Any]:
|
||||
"""Append a terminal line and broadcast it from a non-async context (e.g. a
|
||||
background ``threading.Thread``). Safe to call from any thread."""
|
||||
init_terminals([agent_id])
|
||||
line = _build_line(agent_id, text, level, phase, None)
|
||||
_buffers[agent_id].append(line)
|
||||
if _publish and _loop and not _loop.is_closed():
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(_publish({"type": "terminal", "line": line}), _loop)
|
||||
except Exception:
|
||||
pass
|
||||
return line
|
||||
|
||||
|
||||
def get_terminal_lines(agent_id: str, limit: int = 200) -> list[dict[str, Any]]:
|
||||
|
||||
Reference in New Issue
Block a user