package main import ( "bufio" "encoding/json" "os" "path/filepath" "sort" "time" ) // SessionInfo holds metadata about a discovered session. type SessionInfo struct { ID string AgentCount int EventCount int FirstSeen time.Time LastSeen time.Time Agents map[string]string // agentID -> slug } // DiscoverSessions scans all JSONL files in a directory and groups them by sessionId. // Returns sessions sorted by LastSeen descending (most recent first). func DiscoverSessions(dir string) ([]SessionInfo, error) { matches, err := filepath.Glob(filepath.Join(dir, "*.jsonl")) if err != nil { return nil, err } sessions := make(map[string]*SessionInfo) for _, path := range matches { scanFileForSessions(path, sessions) } // Convert to slice and sort by LastSeen descending result := make([]SessionInfo, 0, len(sessions)) for _, s := range sessions { s.AgentCount = len(s.Agents) result = append(result, *s) } sort.Slice(result, func(i, j int) bool { return result[i].LastSeen.After(result[j].LastSeen) }) return result, nil } // scanFileForSessions reads a JSONL file and registers sessions found. // It reads the first and last few lines for speed on large files. func scanFileForSessions(path string, sessions map[string]*SessionInfo) { f, err := os.Open(path) if err != nil { return } defer f.Close() scanner := bufio.NewScanner(f) scanner.Buffer(make([]byte, 512*1024), 512*1024) for scanner.Scan() { var evt struct { SessionID string `json:"sessionId"` AgentID string `json:"agentId"` Slug string `json:"slug"` Timestamp string `json:"timestamp"` } if err := json.Unmarshal(scanner.Bytes(), &evt); err != nil { continue } if evt.SessionID == "" { continue } s, ok := sessions[evt.SessionID] if !ok { s = &SessionInfo{ ID: evt.SessionID, Agents: make(map[string]string), } sessions[evt.SessionID] = s } s.EventCount++ ts := parseTimestamp(evt.Timestamp) if !ts.IsZero() { if s.FirstSeen.IsZero() || ts.Before(s.FirstSeen) { s.FirstSeen = ts } if ts.After(s.LastSeen) { s.LastSeen = ts } } if evt.AgentID != "" { if _, exists := s.Agents[evt.AgentID]; !exists { s.Agents[evt.AgentID] = evt.Slug } } } } // shortSessionID returns the first 8 chars of a session UUID. func shortSessionID(id string) string { if len(id) > 8 { return id[:8] } return id }