diff --git a/README.md b/README.md index a7ee428..ac1a083 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,19 @@ AGENTMON_VM_NAME=zap # or orb, sun Deployment is automated via Ansible — see the [swarm ansible playbook](https://gitea-http.taildb3494.ts.net/will/swarm) `playbooks/customize.yml`. +## Codex Hook + +The `hooks/codex/` directory contains a TypeScript handler for Codex CLI telemetry. Current Codex support is session/run oriented: + +- `sessionStart` and `sessionEnd` map to `session.start`, `run.start`, `run.end`, and `session.end` +- `notify` maps turn-complete notifications into `run.end` +- prompt-submit hooks map user prompts into the next `run.start` +- usage payloads emit both `run.end.payload.usage` and a `metric.snapshot` event + +Sample Codex hook configuration lives in [hooks/codex/hooks.json](/home/will/lab/agentmon/hooks/codex/hooks.json). On the local Codex CLI version we checked (`0.116.0`), `notify` is confirmed. Online reports suggest prompt-submit hooks may appear as `userpromptsubmit` or `userPromptSubmit`, so the sample config includes those aliases. + +The current Codex integration does not assume tool or subagent span hooks exist. If a newer Codex CLI exposes official tool/span hooks, they can be added separately without changing the run/session flow above. + ## Go SDK Emit events from Go applications: diff --git a/cmd/query-api/main.go b/cmd/query-api/main.go index c4a3afd..0915263 100644 --- a/cmd/query-api/main.go +++ b/cmd/query-api/main.go @@ -125,6 +125,7 @@ func main() { Limit: limit, EventType: r.URL.Query().Get("event_type"), Framework: r.URL.Query().Get("framework"), + ClientID: r.URL.Query().Get("client_id"), } events, err := db.ListRecentEvents(r.Context(), f) if err != nil { @@ -195,6 +196,23 @@ func main() { httpx.WriteJSON(w, http.StatusOK, map[string]any{"session": session, "runs": runs}) }) + r.Get("/v1/agents/live", func(w http.ResponseWriter, r *http.Request) { + clientID := r.URL.Query().Get("client_id") + framework := r.URL.Query().Get("framework") + if clientID == "" || framework == "" { + httpx.WriteJSON(w, http.StatusBadRequest, map[string]any{"error": "missing_agent_selector"}) + return + } + + limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + events, err := db.ListAgentLiveEvents(r.Context(), framework, clientID, limit) + if err != nil { + httpx.WriteJSON(w, http.StatusInternalServerError, map[string]any{"error": "db_error"}) + return + } + httpx.WriteJSON(w, http.StatusOK, map[string]any{"events": events}) + }) + r.Get("/v1/runs/{runID}", func(w http.ResponseWriter, r *http.Request) { runID := chi.URLParam(r, "runID") run, spans, err := db.GetRunWithSpans(r.Context(), runID) diff --git a/cmd/web-ui/static/app.js b/cmd/web-ui/static/app.js index 9d5b6c8..d3e2372 100644 --- a/cmd/web-ui/static/app.js +++ b/cmd/web-ui/static/app.js @@ -475,6 +475,7 @@ const data = await api('/v1/sessions/' + sessionID); const s = data.session; const runs = data.runs || []; + const active = !s.ended_at; const duration = s.ended_at ? formatDuration(new Date(s.ended_at) - new Date(s.started_at)) : 'ongoing'; @@ -483,6 +484,10 @@ ← Back to Sessions