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
+26 -23
View File
@@ -6,14 +6,15 @@ import (
)
var validTypes = map[string]bool{
"session.start": true,
"session.end": true,
"run.start": true,
"run.end": true,
"span.start": true,
"span.end": true,
"error": true,
"metric.snapshot": true,
"session.start": true,
"session.end": true,
"run.start": true,
"run.end": true,
"span.start": true,
"span.end": true,
"error": true,
"metric.snapshot": true,
"openclaw.snapshot": true,
}
type ValidationError struct {
@@ -31,8 +32,8 @@ func Validate(m map[string]any) error {
if !ok {
return ValidationError{Field: "schema", Message: "missing or invalid"}
}
if name, _ := schema["name"].(string); name != "agentmon.event" {
return ValidationError{Field: "schema.name", Message: "must be 'agentmon.event'"}
if name, _ := schema["name"].(string); name != "agentmon.event" && name != "agentmon.openclaw" {
return ValidationError{Field: "schema.name", Message: "must be 'agentmon.event' or 'agentmon.openclaw'"}
}
if ver, _ := schema["version"].(float64); ver != 1 {
return ValidationError{Field: "schema.version", Message: "must be 1"}
@@ -60,20 +61,22 @@ func Validate(m map[string]any) error {
return ValidationError{Field: "event.ts", Message: "required"}
}
// Check source
source, ok := event["source"].(map[string]any)
if !ok {
return ValidationError{Field: "event.source", Message: "missing or invalid"}
}
// Source is optional for openclaw.snapshot events
if eventType != "openclaw.snapshot" {
source, ok := event["source"].(map[string]any)
if !ok {
return ValidationError{Field: "event.source", Message: "missing or invalid"}
}
if fw, _ := source["framework"].(string); fw == "" {
return ValidationError{Field: "event.source.framework", Message: "required"}
}
if cid, _ := source["client_id"].(string); cid == "" {
return ValidationError{Field: "event.source.client_id", Message: "required"}
}
if host, _ := source["host"].(string); host == "" {
return ValidationError{Field: "event.source.host", Message: "required"}
if fw, _ := source["framework"].(string); fw == "" {
return ValidationError{Field: "event.source.framework", Message: "required"}
}
if cid, _ := source["client_id"].(string); cid == "" {
return ValidationError{Field: "event.source.client_id", Message: "required"}
}
if host, _ := source["host"].(string); host == "" {
return ValidationError{Field: "event.source.host", Message: "required"}
}
}
return nil