improve(session): use first user message as preview fallback in session list

When a session has no summary (most short conversations), show the first
user message content instead. Display priority: summary > preview > "(empty)".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mingmxren 2026-03-04 02:08:56 +08:00
parent b323131c27
commit c917184375
3 changed files with 66 additions and 9 deletions

View file

@ -81,7 +81,7 @@ func formatSessionList(list []session.SessionMeta) string {
activeMarker = "[*]" activeMarker = "[*]"
} }
summary := truncateSummary(item.Summary, 40) summary := sessionLabel(item.Summary, item.Preview, 40)
age := "-" age := "-"
if !item.UpdatedAt.IsZero() { if !item.UpdatedAt.IsZero() {
@ -98,16 +98,21 @@ func formatSessionList(list []session.SessionMeta) string {
return strings.Join(lines, "\n") return strings.Join(lines, "\n")
} }
func truncateSummary(s string, maxLen int) string { // sessionLabel picks the best display text for a session list row:
s = strings.TrimSpace(s) // summary (if available) > first user message preview > "(empty)".
if s == "" { func sessionLabel(summary, preview string, maxLen int) string {
return "(no summary)" text := strings.TrimSpace(summary)
if text == "" {
text = strings.TrimSpace(preview)
} }
s = strings.ReplaceAll(s, "\n", " ") if text == "" {
if len(s) <= maxLen { return "(empty)"
return s
} }
return s[:maxLen] + "..." text = strings.ReplaceAll(text, "\n", " ")
if len(text) <= maxLen {
return text
}
return text[:maxLen] + "..."
} }
// extractSessionTag returns the "#N" suffix from a session key, or "#1" for // extractSessionTag returns the "#N" suffix from a session key, or "#1" for

View file

@ -145,6 +145,47 @@ func TestSessionHandlers_SessionList(t *testing.T) {
} }
} }
func TestSessionHandlers_SessionList_PreviewFallback(t *testing.T) {
ops := &fakeSessionOps{
listValue: []session.SessionMeta{
{
Ordinal: 1,
SessionKey: "scope#2",
UpdatedAt: time.Date(2026, 3, 1, 9, 0, 0, 0, time.UTC),
MessageCnt: 3,
Active: true,
Preview: "How do I fix this login bug?",
},
{
Ordinal: 2,
SessionKey: "scope",
MessageCnt: 0,
},
},
}
rt := &Runtime{SessionOps: ops}
ex := NewExecutor(NewRegistry(BuiltinDefinitions()), rt)
var reply string
res := ex.Execute(context.Background(), Request{
ScopeKey: "scope",
Text: "/session list",
Reply: func(text string) error { reply = text; return nil },
})
if res.Outcome != OutcomeHandled {
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomeHandled)
}
// Row 1: no summary → falls back to preview
if !strings.Contains(reply, "How do I fix this login bug?") {
t.Fatalf("reply missing preview fallback, got %q", reply)
}
// Row 2: no summary, no preview, no messages → "(empty)"
if !strings.Contains(reply, "(empty)") {
t.Fatalf("reply missing (empty) for blank session, got %q", reply)
}
}
func TestSessionHandlers_NilRuntime_Unavailable(t *testing.T) { func TestSessionHandlers_NilRuntime_Unavailable(t *testing.T) {
ex := NewExecutor(NewRegistry(BuiltinDefinitions()), nil) ex := NewExecutor(NewRegistry(BuiltinDefinitions()), nil)

View file

@ -52,6 +52,7 @@ type SessionMeta struct {
MessageCnt int `json:"message_cnt"` MessageCnt int `json:"message_cnt"`
Active bool `json:"active"` Active bool `json:"active"`
Summary string `json:"summary,omitempty"` Summary string `json:"summary,omitempty"`
Preview string `json:"preview,omitempty"` // first user message content
} }
type SessionManager struct { type SessionManager struct {
@ -742,12 +743,22 @@ func (sm *SessionManager) buildSessionMetaListLocked(scope *scopeIndex) []Sessio
meta.UpdatedAt = session.Updated meta.UpdatedAt = session.Updated
meta.MessageCnt = len(session.Messages) meta.MessageCnt = len(session.Messages)
meta.Summary = session.Summary meta.Summary = session.Summary
meta.Preview = firstUserMessage(session.Messages)
} }
list = append(list, meta) list = append(list, meta)
} }
return list return list
} }
func firstUserMessage(msgs []providers.Message) string {
for _, m := range msgs {
if m.Role == "user" && strings.TrimSpace(m.Content) != "" {
return m.Content
}
}
return ""
}
func prependSessionUnique(ordered []string, sessionKey string) []string { func prependSessionUnique(ordered []string, sessionKey string) []string {
next := make([]string, 0, len(ordered)+1) next := make([]string, 0, len(ordered)+1)
next = append(next, sessionKey) next = append(next, sessionKey)