589 return 590 } 591 592 if err := s.store.InsertTelemetry(events); err != nil { 593 http.Error(w, "insert failed: "+err.Error(), http.StatusInternalServerError) 594 return 595 } 596 // React to directive changes — update DB so dashboard sees it immediately 597 for _, ev := range events { 598 if ev.Type == "directive.evolved" && ev.AgentID > 0 { 599 var data struct { 600 New string `json:"new"` 601 } 602 if json.Unmarshal(ev.Data, &data) == nil && data.New != "" { 603 if _, err := s.store.db.Exec("UPDATE agents SET directive=? WHERE id=?", data.New, ev.AgentID); err == nil { 604 // Do not make Core's telemetry ingestion wait on re-entrant 605 // per-thread HTTP updates. Persistence is complete; each thread's 606 // next delivery also performs the same drift check defensively. 607 go s.refreshChannelChatConversationDirectives(ev.AgentID) 608 } 609 } 610 } 611 } 612 613 // No broadcast here — /telemetry/live handles real-time broadcast. 614 // This endpoint is for DB persistence only. 615 616 writeJSON(w, map[string]int{"inserted": len(events)}) 617} 618 619const storedToolResultPreviewBytes = 1000 620 621// sanitizeStoredToolResultTelemetry is a defensive server-side bound. New 622// cores already send a preview plus exact size metadata, but this also keeps 623// inline results from older cores from persisting an unbounded payload. 624func sanitizeStoredToolResultTelemetry(events []TelemetryEvent) { 625 for i := range events { 626 if events[i].Type != "tool.result" || len(events[i].Data) == 0 { 627 continue 628 } 629 var data map[string]any 630 if json.Unmarshal(events[i].Data, &data) != nil { 631 continue 632 } 633 result, ok := data["result"].(string) 634 if !ok { 635 continue 636 } 637 alreadyTruncated, _ := data["result_truncated"].(bool) 638 if len(result) > storedToolResultPreviewBytes && !(alreadyTruncated && len(result) <= storedToolResultPreviewBytes+3) { 639 end := storedToolResultPreviewBytes 640 for end > 0 && !utf8.ValidString(result[:end]) { 641 end-- 642 } 643 result = result[:end] + "..." 644 data["result"] = result 645 alreadyTruncated = true 646 } 647 data["result_preview_bytes"] = len(result) 648 data["result_truncated"] = alreadyTruncated 649 if raw, err := json.Marshal(data); err == nil {