feat: complete agent monitoring - hook, UI, and backend filter

- Add event_type and framework filters to events query endpoint
- Add /agents SPA route to web-ui server
- Add Agents nav link and route in frontend
- Add agents page CSS (timeline, VM pills, stats panel)
- Build VM status strip, activity timeline, and real-time stats
- Add agentmon hook for OpenClaw (HOOK.md + handler.ts)
- Add docker-compose, Dockerfile, and supporting infra files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-03-14 00:26:42 -07:00
parent 1927ec6622
commit 3434db3c59
29 changed files with 6228 additions and 231 deletions
+147
View File
@@ -0,0 +1,147 @@
// Package main demonstrates usage of the agentmon SDK.
package main
import (
"context"
"fmt"
"log"
"time"
"agentmon/internal/sdk"
)
func main() {
emitter, err := sdk.NewEmitter(sdk.Config{
ServerURL: "http://localhost:8080",
Framework: "example-agent",
ClientID: "example-client-001",
Host: "localhost",
BufferSize: 50,
EnableLogging: true,
})
if err != nil {
log.Fatalf("Failed to create emitter: %v", err)
}
defer emitter.Close(context.Background())
ctx := context.Background()
sessionID := generateSessionID()
log.Printf("Starting session: %s", sessionID)
sessionStart := sdk.NewSessionStart(sessionID, sdk.WithSource(emitter))
if err := emitter.Emit(ctx, sessionStart); err != nil {
log.Printf("Failed to emit session.start: %v", err)
}
runID := generateRunID()
log.Printf("Starting run: %s", runID)
runStart := sdk.NewRunStart(sessionID, runID,
sdk.WithSource(emitter),
sdk.WithAttributes(map[string]any{
"command": "example-command",
"cwd": "/home/user/project",
}),
)
if err := emitter.Emit(ctx, runStart); err != nil {
log.Printf("Failed to emit run.start: %v", err)
}
traceID := generateTraceID()
span1ID := generateSpanID()
span1Start := sdk.NewSpanStart(sessionID, runID, traceID, span1ID,
sdk.WithSource(emitter),
sdk.WithSpanKind("tool"),
sdk.WithName("ExampleTool"),
sdk.WithAttributes(map[string]any{
"tool": "example-tool",
}),
)
if err := emitter.Emit(ctx, span1Start); err != nil {
log.Printf("Failed to emit span.start: %v", err)
}
time.Sleep(100 * time.Millisecond)
span1End := sdk.NewSpanEnd(sessionID, runID, traceID, span1ID, "success", 100,
sdk.WithSource(emitter),
sdk.WithSpanKind("tool"),
sdk.WithName("ExampleTool"),
)
if err := emitter.Emit(ctx, span1End); err != nil {
log.Printf("Failed to emit span.end: %v", err)
}
span2ID := generateSpanID()
span2Start := sdk.NewSpanStart(sessionID, runID, traceID, span2ID,
sdk.WithSource(emitter),
sdk.WithSpanKind("llm"),
sdk.WithName("ClaudeRequest"),
sdk.WithAttributes(map[string]any{
"model": "claude-3-opus",
}),
)
if err := emitter.Emit(ctx, span2Start); err != nil {
log.Printf("Failed to emit span.start: %v", err)
}
time.Sleep(200 * time.Millisecond)
span2End := sdk.NewSpanEnd(sessionID, runID, traceID, span2ID, "success", 200,
sdk.WithSource(emitter),
sdk.WithSpanKind("llm"),
sdk.WithName("ClaudeRequest"),
sdk.WithLLMUsage("claude-3-opus", 1000, 500, 0.015),
)
if err := emitter.Emit(ctx, span2End); err != nil {
log.Printf("Failed to emit span.end: %v", err)
}
metrics := sdk.NewMetricSnapshot(sessionID, runID, map[string]any{
"tokens_in": 1000.0,
"tokens_out": 500.0,
"cost_usd": 0.015,
"latency_ms": 300.0,
"error_count": 0,
})
if err := emitter.Emit(ctx, metrics); err != nil {
log.Printf("Failed to emit metric.snapshot: %v", err)
}
runEnd := sdk.NewRunEnd(sessionID, runID, "success", 300,
sdk.WithSource(emitter),
sdk.WithLLMUsage("claude-3-opus", 1000, 500, 0.015),
)
if err := emitter.Emit(ctx, runEnd); err != nil {
log.Printf("Failed to emit run.end: %v", err)
}
sessionEnd := sdk.NewSessionEnd(sessionID, sdk.WithSource(emitter))
if err := emitter.Emit(ctx, sessionEnd); err != nil {
log.Printf("Failed to emit session.end: %v", err)
}
if err := emitter.Flush(ctx); err != nil {
log.Printf("Failed to flush events: %v", err)
}
log.Println("Example completed successfully!")
}
func generateSessionID() string {
return fmt.Sprintf("sess-%d", time.Now().UnixNano())
}
func generateRunID() string {
return fmt.Sprintf("run-%d", time.Now().UnixNano())
}
func generateTraceID() string {
return fmt.Sprintf("trace-%d", time.Now().UnixNano())
}
func generateSpanID() string {
return fmt.Sprintf("span-%d", time.Now().UnixNano())
}