kenn-io--agentsview
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
249 行
6.1 KiB
Go
249 行
6.1 KiB
Go
package insight
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"go.kenn.io/agentsview/internal/db"
|
|
)
|
|
|
|
const maxSessions = 50
|
|
|
|
// GenerateRequest describes what insight to generate.
|
|
type GenerateRequest struct {
|
|
Type string
|
|
DateFrom string
|
|
DateTo string
|
|
Project string
|
|
Prompt string
|
|
SessionID string
|
|
AutomatedScope string
|
|
Summary *RangeSummary // non-nil only for multi-day ranges
|
|
}
|
|
|
|
// BuildPrompt queries sessions for the given date and assembles
|
|
// a prompt for the AI agent.
|
|
func BuildPrompt(
|
|
ctx context.Context,
|
|
database db.Store,
|
|
req GenerateRequest,
|
|
) (string, error) {
|
|
if req.SessionID != "" {
|
|
return buildSessionPrompt(ctx, database, req)
|
|
}
|
|
automatedScope := req.AutomatedScope
|
|
if automatedScope == "" {
|
|
automatedScope = "human"
|
|
}
|
|
filter := db.SessionFilter{
|
|
DateFrom: req.DateFrom,
|
|
DateTo: req.DateTo,
|
|
Limit: maxSessions + 1,
|
|
AutomatedScope: automatedScope,
|
|
}
|
|
if req.Project != "" {
|
|
filter.Project = req.Project
|
|
}
|
|
|
|
page, err := database.ListSessions(ctx, filter)
|
|
if err != nil {
|
|
return "", fmt.Errorf("querying sessions: %w", err)
|
|
}
|
|
|
|
var b strings.Builder
|
|
writeSystemInstruction(&b, req.Type)
|
|
if req.DateFrom == req.DateTo {
|
|
b.WriteString("\n## Date: ")
|
|
b.WriteString(req.DateFrom)
|
|
} else {
|
|
b.WriteString("\n## Date Range: ")
|
|
b.WriteString(req.DateFrom)
|
|
b.WriteString(" to ")
|
|
b.WriteString(req.DateTo)
|
|
}
|
|
b.WriteString("\n\n")
|
|
|
|
if req.Project != "" {
|
|
b.WriteString("## Project: ")
|
|
b.WriteString(req.Project)
|
|
b.WriteString("\n\n")
|
|
}
|
|
|
|
if req.Summary != nil {
|
|
req.Summary.WriteTo(&b)
|
|
}
|
|
|
|
sessions := page.Sessions
|
|
truncated := len(sessions) > maxSessions
|
|
if truncated {
|
|
sessions = sessions[:maxSessions]
|
|
}
|
|
|
|
b.WriteString("## Sessions\n\n")
|
|
if len(sessions) == 0 {
|
|
if req.DateFrom == req.DateTo {
|
|
b.WriteString(
|
|
"No sessions found for this date.\n",
|
|
)
|
|
} else {
|
|
b.WriteString(
|
|
"No sessions found for this date range.\n",
|
|
)
|
|
}
|
|
} else {
|
|
for i, s := range sessions {
|
|
fmt.Fprintf(&b, "### Session %d\n", i+1)
|
|
fmt.Fprintf(&b, "- ID: %s\n", s.ID)
|
|
fmt.Fprintf(&b, "- Project: %s\n", s.Project)
|
|
fmt.Fprintf(&b, "- Agent: %s\n", s.Agent)
|
|
if s.StartedAt != nil {
|
|
fmt.Fprintf(&b, "- Started: %s\n", *s.StartedAt)
|
|
}
|
|
if s.EndedAt != nil {
|
|
fmt.Fprintf(&b, "- Ended: %s\n", *s.EndedAt)
|
|
}
|
|
fmt.Fprintf(
|
|
&b, "- Messages: %d\n", s.MessageCount,
|
|
)
|
|
if s.FirstMessage != nil {
|
|
fmt.Fprintf(
|
|
&b, "- First message: %s\n",
|
|
truncateString(*s.FirstMessage, 200),
|
|
)
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
if truncated {
|
|
fmt.Fprintf(
|
|
&b,
|
|
"(Showing %d of %d sessions; "+
|
|
"remaining sessions omitted)\n\n",
|
|
maxSessions, page.Total,
|
|
)
|
|
}
|
|
}
|
|
|
|
if req.Prompt != "" {
|
|
b.WriteString("## User Query\n\n")
|
|
b.WriteString(
|
|
"The user has provided the following " +
|
|
"specific request. Prioritize addressing " +
|
|
"this in your response:\n\n",
|
|
)
|
|
b.WriteString(req.Prompt)
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|
|
|
|
func buildSessionPrompt(
|
|
ctx context.Context,
|
|
database db.Store,
|
|
req GenerateRequest,
|
|
) (string, error) {
|
|
sess, err := database.GetSession(ctx, req.SessionID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("getting session: %w", err)
|
|
}
|
|
if sess == nil {
|
|
return "", fmt.Errorf("session not found: %s", req.SessionID)
|
|
}
|
|
msgs, err := database.GetAllMessages(ctx, req.SessionID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("getting messages: %w", err)
|
|
}
|
|
timing, err := database.GetSessionTiming(ctx, req.SessionID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("getting timing: %w", err)
|
|
}
|
|
usage, err := database.GetSessionUsage(ctx, req.SessionID, false)
|
|
if err != nil {
|
|
return "", fmt.Errorf("getting usage: %w", err)
|
|
}
|
|
|
|
var b strings.Builder
|
|
writeSystemInstruction(&b, req.Type)
|
|
fmt.Fprintf(&b, "\n## Session: %s\n\n", req.SessionID)
|
|
fmt.Fprintf(&b, "- Project: %s\n", sess.Project)
|
|
fmt.Fprintf(&b, "- Agent: %s\n", sess.Agent)
|
|
if sess.StartedAt != nil {
|
|
fmt.Fprintf(&b, "- Started: %s\n", *sess.StartedAt)
|
|
}
|
|
if sess.EndedAt != nil {
|
|
fmt.Fprintf(&b, "- Ended: %s\n", *sess.EndedAt)
|
|
}
|
|
fmt.Fprintf(&b, "- Messages: %d\n", sess.MessageCount)
|
|
if usage != nil && usage.HasTokenData {
|
|
fmt.Fprintf(&b, "- Output tokens: %d\n", usage.TotalOutputTokens)
|
|
fmt.Fprintf(&b, "- Peak context tokens: %d\n", usage.PeakContextTokens)
|
|
}
|
|
if usage != nil && usage.HasCost {
|
|
fmt.Fprintf(&b, "- Cost: $%.4f\n", usage.CostUSD)
|
|
}
|
|
if timing != nil {
|
|
fmt.Fprintf(&b, "- Duration: %.1fs\n", float64(timing.TotalDurationMs)/1000)
|
|
fmt.Fprintf(&b, "- Tool calls: %d\n", timing.ToolCallCount)
|
|
}
|
|
|
|
b.WriteString("\n## Messages\n\n")
|
|
if len(msgs) == 0 {
|
|
b.WriteString("No messages found for this session.\n")
|
|
} else {
|
|
for _, m := range msgs {
|
|
if m.IsSystem {
|
|
continue
|
|
}
|
|
fmt.Fprintf(&b, "### Message %d: %s\n", m.Ordinal, m.Role)
|
|
if m.Timestamp != "" {
|
|
fmt.Fprintf(&b, "- Timestamp: %s\n", m.Timestamp)
|
|
}
|
|
if m.Model != "" {
|
|
fmt.Fprintf(&b, "- Model: %s\n", m.Model)
|
|
}
|
|
if hasContext, hasOutput := m.TokenPresence(); hasContext || hasOutput {
|
|
fmt.Fprintf(&b, "- Context tokens: %d\n", m.ContextTokens)
|
|
fmt.Fprintf(&b, "- Output tokens: %d\n", m.OutputTokens)
|
|
}
|
|
fmt.Fprintf(&b, "\n%s\n\n", truncateString(m.Content, 800))
|
|
}
|
|
}
|
|
if req.Prompt != "" {
|
|
b.WriteString("## User Query\n\n")
|
|
b.WriteString(req.Prompt)
|
|
b.WriteString("\n")
|
|
}
|
|
return b.String(), nil
|
|
}
|
|
|
|
func writeSystemInstruction(b *strings.Builder, typ string) {
|
|
switch typ {
|
|
case "agent_analysis":
|
|
b.WriteString(
|
|
"You are analyzing AI agent sessions. " +
|
|
"Provide deeper analysis of patterns, " +
|
|
"effectiveness, and suggestions for " +
|
|
"improving CLAUDE.md or agent workflows. " +
|
|
"Focus on actionable insights.\n",
|
|
)
|
|
default:
|
|
b.WriteString(
|
|
"You are summarizing a day of AI agent " +
|
|
"activity. Provide a concise markdown " +
|
|
"summary of what was accomplished, " +
|
|
"key decisions made, and notable " +
|
|
"patterns. Group by project if multiple " +
|
|
"projects are present.\n",
|
|
)
|
|
}
|
|
}
|
|
|
|
func truncateString(s string, max int) string {
|
|
runes := []rune(s)
|
|
if len(runes) <= max {
|
|
return s
|
|
}
|
|
return string(runes[:max]) + "..."
|
|
}
|