Files
fn_registry/cpp/functions/viz/agent_runs_timeline.h
T
egutierrez 048a4a1457 feat(viz): agent_runs_timeline ImGui panel + SSE stub
Issue 0118.

fn_viz::render_agent_runs_timeline(TimelineState&):
- Filtros: multi-select apps, multi-select statuses, Since (days).
- Connection badge (● green / ◐ amber / ○ red) por state.connection_status.
- Tabla 7 cols: status icon | app chip | issue/card | branch | dod badge |
  duration | started. Selectable SpanAllColumns dispara on_select callback.
- Footer: contadores per-status sobre el set completo.

Thread-safe: snapshot bajo runs_mutex al inicio del frame. SSE client
NO implementado — poll_sse_runs() es stub documentado en .md ## Gotchas.
Consumer puede usar http_request_cpp_core para polling fallback contra
GET /api/runs hasta que un endpoint /api/runs/stream estable aparezca.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:31:29 +02:00

83 lines
2.9 KiB
C++

#pragma once
//
// agent_runs_timeline.h — ImGui panel: cross-app timeline of agent runs.
//
// API surface declared per issue 0118. Render is impure (uses ImGui); pure
// helpers live in agent_runs_timeline_helpers.{h,cpp} so unit tests can
// exercise filter / sort / formatting without a GL context.
//
// Typical wiring (consumer side):
//
// fn_viz::TimelineState g_state;
// g_state.sse_url = "http://127.0.0.1:8497/api/runs/stream";
// g_state.on_select = [](const std::string& id){ open_detail_panel(id); };
//
// ImGui::Begin("Agent runs");
// fn_viz::render_agent_runs_timeline(g_state);
// ImGui::End();
//
// // Whenever a worker thread (real SSE client, polling fallback, fixture)
// // wants to update the list:
// {
// std::lock_guard<std::mutex> lk(g_state.runs_mutex);
// g_state.runs = new_runs;
// g_state.connection_status = "connected";
// }
//
// SSE client itself is OUT OF SCOPE for this issue — see the documented stub
// in poll_sse_runs() and the .md ## Gotchas section.
#include <cstdint>
#include <functional>
#include <mutex>
#include <string>
#include <vector>
namespace fn_viz {
struct AgentRun {
std::string id;
std::string app; // kanban_cpp | skill_tree | ...
std::string issue_id;
std::string card_id;
std::string branch;
std::string status; // pending|running|done|validated|merged|aborted|failed
int64_t started_at = 0; // unix epoch seconds
int64_t finished_at = 0; // 0 if still running
int dod_total = 0;
int dod_done = 0;
int dod_validated = 0;
};
struct TimelineFilter {
std::vector<std::string> apps;
std::vector<std::string> statuses;
int64_t since_ts = 0; // 0 means "no lower bound"
};
struct TimelineState {
std::string sse_url;
std::vector<AgentRun> runs;
TimelineFilter filter;
std::function<void(const std::string& run_id)> on_select;
std::string selected_run_id;
std::string connection_status; // "connected"|"disconnected"|"connecting"
std::mutex runs_mutex; // SSE thread writes, UI reads
};
// Renders the timeline INLINE inside the current ImGui window. The caller
// owns ImGui::Begin/End — this function only draws filters, badge, table
// and footer counts. Safe to call every frame; cost is O(N) over runs.
void render_agent_runs_timeline(TimelineState& state);
// STUB. Documented in agent_runs_timeline.md ## Gotchas.
// Today this is a no-op. The intent is that a future issue wires either:
// (a) a real SSE client (libcurl multi + custom read callback), or
// (b) a polling fallback that calls fn_core::http_request("GET", url, ...)
// every N seconds and replaces state.runs under runs_mutex.
// Until then, the consumer is responsible for populating state.runs (e.g.
// test fixtures, or an in-process orchestrator that owns the data already).
void poll_sse_runs(TimelineState& state);
} // namespace fn_viz