feat(history): seed Logs + Status Feed with history on connect
Both panels were empty on Connect / agent select — only new events since
subscribe appeared. Backend already persists per-agent logs to
logs/<id>/YYYY-MM-DD.jsonl AND now keeps the last 100 status diffs in
a ring buffer (agents_and_robots 71b3b2b).
Frontend changes:
- fetch_log_history(s, agent_id, n) → GET /agents/{id}/logs?n=N, fills
s.log_lines BEFORE SSE subscribe so context appears instantly.
Handles the {count,id,lines:[...]} response shape from the backend.
- start_log_sse now spawns this fetch on entry; SSE adds new lines on top.
- fetch_status_history(s, n) → GET /status/recent?n=N, fills
s.status_events with [hist]-tagged entries before the live SSE attaches.
- Connect handler dispatches fetch_status_history() in a worker thread
alongside the existing start_status_sse + fetch_agents_async.
E2E (4 new, 29 total):
- test_status_recent_history_endpoint — shape contract
- test_status_recent_captures_stop_start_events — drives stop/start on
test-bot, asserts events appear in /status/recent within 5s. This is
the "send actions and observe feed" loop the user requested.
- test_agent_logs_history_endpoint — {count,id,lines} contract +
lines>0 for long-running assistant-bot
- test_status_recent_unauthorized_without_bearer — auth boundary
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,72 @@ def test_connect_succeeds_with_valid_apikey():
|
||||
assert n > 0, f"expected at least 1 agent, got {n}"
|
||||
|
||||
|
||||
def test_status_recent_history_endpoint():
|
||||
"""GET /status/recent returns a JSON array (may be empty after restart)."""
|
||||
apikey = _apikey()
|
||||
r = _curl([
|
||||
"-fsS",
|
||||
"-H", f"Authorization: Bearer {apikey}",
|
||||
f"{_url()}/status/recent?n=10",
|
||||
])
|
||||
assert r.returncode == 0, r.stderr
|
||||
body = json.loads(r.stdout)
|
||||
assert isinstance(body, list)
|
||||
|
||||
|
||||
def test_status_recent_captures_stop_start_events():
|
||||
"""Triggering stop/start must produce 2 entries in /status/recent.
|
||||
|
||||
Drives the end-to-end "send actions and observe feed" flow the user
|
||||
asked for: this is the same data path the C++ frontend uses to seed
|
||||
its Status Feed panel on Connect.
|
||||
"""
|
||||
apikey = _apikey()
|
||||
hdr = ["-H", f"Authorization: Bearer {apikey}"]
|
||||
# Snapshot history length before
|
||||
r0 = _curl(["-fsS", *hdr, f"{_url()}/status/recent?n=100"])
|
||||
before = json.loads(r0.stdout)
|
||||
|
||||
# Drive events: stop then start test-bot.
|
||||
_curl(["-sS", "-X", "POST", *hdr, f"{_url()}/agents/test-bot/stop"])
|
||||
import time
|
||||
time.sleep(2.5) # poller emits diff at most every 2s
|
||||
_curl(["-sS", "-X", "POST", *hdr, f"{_url()}/agents/test-bot/start"])
|
||||
time.sleep(2.5)
|
||||
|
||||
r1 = _curl(["-fsS", *hdr, f"{_url()}/status/recent?n=100"])
|
||||
after = json.loads(r1.stdout)
|
||||
# At least one new event captured (poller may coalesce or already had recents)
|
||||
assert len(after) > len(before) or any(
|
||||
e.get("agent_id") == "test-bot" for e in after[-5:]
|
||||
), f"no test-bot event in feed; before={len(before)} after={len(after)}"
|
||||
|
||||
|
||||
def test_agent_logs_history_endpoint():
|
||||
"""GET /agents/{id}/logs?n=N returns {count, id, lines:[...]} — historical tail."""
|
||||
apikey = _apikey()
|
||||
r = _curl([
|
||||
"-fsS",
|
||||
"-H", f"Authorization: Bearer {apikey}",
|
||||
f"{_url()}/agents/assistant-bot/logs?n=50",
|
||||
])
|
||||
assert r.returncode == 0, r.stderr
|
||||
body = json.loads(r.stdout)
|
||||
assert isinstance(body, dict)
|
||||
assert body.get("id") == "assistant-bot"
|
||||
lines = body.get("lines")
|
||||
assert isinstance(lines, list)
|
||||
# assistant-bot is long-running with persistent log file (logs/<id>/YYYY-MM-DD.jsonl)
|
||||
assert len(lines) > 0, "expected at least 1 historical log line"
|
||||
assert isinstance(lines[0], str)
|
||||
|
||||
|
||||
def test_status_recent_unauthorized_without_bearer():
|
||||
r = _curl(["-s", "-o", "/dev/null", "-w", "%{http_code}",
|
||||
f"{_url()}/status/recent"])
|
||||
assert r.stdout == "401", f"expected 401 got {r.stdout!r}"
|
||||
|
||||
|
||||
def test_app_survives_auto_refresh_cycle():
|
||||
"""Regression: app must NOT crash on Refresh Agents button click.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user