From 246c8d532836129f2944e8ddf24a2cde464481a8 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 3 Jul 2026 10:45:45 +0900 Subject: [PATCH] =?UTF-8?q?feat(agent-office):=20node=5Fmonitor=EC=97=90?= =?UTF-8?q?=20trade-monitor=20=EC=9B=8C=EC=BB=A4=20=EB=93=B1=EC=9E=AC=20+?= =?UTF-8?q?=20trader=20=EB=A7=81=ED=81=AC=20from=EC=9D=84=20=EC=9B=8C?= =?UTF-8?q?=EC=BB=A4=EB=AA=85=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WSL 워커 관측 규칙 — 모든 WSL docker 워커는 /infra에서 모니터링 가능해야 함. trade-monitor(kind=trader) 등재 → /nodes·/infra 노출. 링크 from 하드코딩('ai_trade')을 w[name]으로 고쳐 다중 trader가 각자 링크를 갖도록 함. 미배포 워커는 prev=None이라 다운 경보 없음. --- agent-office/app/node_monitor.py | 7 ++++--- agent-office/tests/test_node_monitor.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/agent-office/app/node_monitor.py b/agent-office/app/node_monitor.py index e88a2c6..5dec02d 100644 --- a/agent-office/app/node_monitor.py +++ b/agent-office/app/node_monitor.py @@ -14,8 +14,9 @@ WORKER_REGISTRY = [ {"name": "video-render", "kind": "render", "queue": "queue:video-render"}, {"name": "image-render", "kind": "render", "queue": "queue:image-render"}, {"name": "insta-render", "kind": "render", "queue": "queue:insta-render"}, - {"name": "task-watcher", "kind": "watcher", "queue": None}, - {"name": "ai_trade", "kind": "trader", "queue": None}, + {"name": "task-watcher", "kind": "watcher", "queue": None}, + {"name": "ai_trade", "kind": "trader", "queue": None}, + {"name": "trade-monitor", "kind": "trader", "queue": None}, ] _redis = None @@ -92,7 +93,7 @@ async def collect_status(redis=None) -> dict: for w in out["workers"]: if w["kind"] == "trader": - out["links"].append({"from": "ai_trade", "to": "nas-stock", "type": "http-pull", + out["links"].append({"from": w["name"], "to": "nas-stock", "type": "http-pull", "status": "healthy" if w["alive"] else "down"}) elif w["kind"] == "render": out["links"].append({"from": "nas", "to": w["name"], "type": "redis-queue", diff --git a/agent-office/tests/test_node_monitor.py b/agent-office/tests/test_node_monitor.py index 5df84af..ca6249a 100644 --- a/agent-office/tests/test_node_monitor.py +++ b/agent-office/tests/test_node_monitor.py @@ -68,6 +68,18 @@ async def test_trader_http_pull_link(): link = next(l for l in st["links"] if l["from"] == "ai_trade") assert link["type"] == "http-pull" and link["status"] == "healthy" +@pytest.mark.asyncio +async def test_trade_monitor_registered_and_own_link(): + """WSL 워커 trade-monitor가 registry에 있어 /nodes에 노출되고, 링크 from은 + ai_trade 하드코딩이 아니라 자기 이름(trade-monitor)이어야 한다 (다중 trader 구분).""" + r = FakeRedis(kv={"worker:trade-monitor:heartbeat": _hb("trade-monitor", "trader", "market_open")}) + st = await node_monitor.collect_status(redis=r) + tm = next(w for w in st["workers"] if w["name"] == "trade-monitor") + assert tm["alive"] is True and tm["kind"] == "trader" + link = next(l for l in st["links"] if l["from"] == "trade-monitor") + assert link["type"] == "http-pull" and link["to"] == "nas-stock" and link["status"] == "healthy" + + @pytest.mark.asyncio async def test_paused_no_watcher_heartbeat_fallback_reason(): """paused=True인데 watcher heartbeat 없으면 paused_reason == 'trading' 폴백."""