Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

156 lines
4.2 KiB
Go

package db
import (
"context"
"fmt"
"strings"
"go.kenn.io/agentsview/internal/parser"
)
// Stats holds database-wide statistics.
type Stats struct {
SessionCount int `json:"session_count"`
MessageCount int `json:"message_count"`
ProjectCount int `json:"project_count"`
MachineCount int `json:"machine_count"`
EarliestSession *string `json:"earliest_session"`
}
// rootSessionFilter is the WHERE clause shared by session list
// and stats to exclude sub-agent, fork, and trashed sessions.
const rootSessionFilter = `message_count > 0
AND relationship_type NOT IN ('subagent', 'fork')
AND deleted_at IS NULL`
func nonSourceBackedAgentPlaceholders() string {
agents := nonSourceBackedAgents()
placeholders := make([]string, len(agents))
for i := range agents {
placeholders[i] = "?"
}
return strings.Join(placeholders, ", ")
}
func nonSourceBackedAgentArgs() []any {
agents := nonSourceBackedAgents()
args := make([]any, len(agents))
for i, a := range agents {
args[i] = string(a)
}
return args
}
func nonSourceBackedAgents() []parser.AgentType {
var agents []parser.AgentType
for _, def := range parser.Registry {
if def.FileBased || def.Type == parser.AgentDevin {
continue
}
agents = append(agents, def.Type)
}
return agents
}
// FileBackedSessionCount returns the number of root sessions protected by local
// resync discovery. This includes literal file-backed sessions plus Devin's
// provider-backed local CLI archive sessions.
func (db *DB) FileBackedSessionCount(
ctx context.Context,
) (int, error) {
return db.fileBackedSessionCount(ctx, "", "", false)
}
// FileBackedSessionCountForMachine returns the protected root-session count
// for one sync source machine. Multi-source rebuilds use it so one healthy
// source cannot satisfy the empty-discovery guard for another source.
func (db *DB) FileBackedSessionCountForMachine(
ctx context.Context, machine string,
) (int, error) {
return db.fileBackedSessionCount(ctx, machine, "", true)
}
// FileBackedSessionCountForSource returns the protected root-session count
// for one namespaced rebuild contributor. ID prefixes distinguish a remote
// contributor from local sessions when both machines have the same hostname.
func (db *DB) FileBackedSessionCountForSource(
ctx context.Context, machine, idPrefix string,
) (int, error) {
return db.fileBackedSessionCount(ctx, machine, idPrefix, true)
}
func (db *DB) fileBackedSessionCount(
ctx context.Context, machine, idPrefix string, scoped bool,
) (int, error) {
machinePredicate := ""
args := nonSourceBackedAgentArgs()
if scoped {
machinePredicate = " AND machine = ?"
args = append(args, machine)
}
if idPrefix != "" {
machinePredicate += " AND substr(id, 1, length(?)) = ?"
args = append(args, idPrefix, idPrefix)
}
var count int
err := db.getReader().QueryRowContext(ctx,
`SELECT COUNT(*) FROM sessions
WHERE agent NOT IN (`+nonSourceBackedAgentPlaceholders()+`)
AND `+rootSessionFilter+machinePredicate,
args...,
).Scan(&count)
if err != nil {
return 0, fmt.Errorf(
"counting file-backed sessions: %w", err,
)
}
return count, nil
}
// GetStats returns database statistics, counting only root
// sessions with messages (matching the session list filter).
func (db *DB) GetStats(
ctx context.Context,
excludeOneShot, excludeAutomated bool,
) (Stats, error) {
filter := rootSessionFilter
if excludeOneShot {
if !excludeAutomated {
filter += " AND (user_message_count > 1 OR is_automated = 1)"
} else {
filter += " AND user_message_count > 1"
}
}
if excludeAutomated {
filter += " AND is_automated = 0"
}
query := fmt.Sprintf(`
SELECT
(SELECT COUNT(*) FROM sessions
WHERE %s),
(SELECT COALESCE(SUM(message_count), 0)
FROM sessions WHERE %s),
(SELECT COUNT(DISTINCT project) FROM sessions
WHERE %s),
(SELECT COUNT(DISTINCT machine) FROM sessions
WHERE %s),
(SELECT MIN(COALESCE(
NULLIF(started_at, ''), created_at
)) FROM sessions
WHERE %s)`,
filter, filter, filter, filter, filter)
var s Stats
err := db.getReader().QueryRowContext(ctx, query).Scan(
&s.SessionCount,
&s.MessageCount,
&s.ProjectCount,
&s.MachineCount,
&s.EarliestSession,
)
if err != nil {
return Stats{}, fmt.Errorf("fetching stats: %w", err)
}
return s, nil
}