diff --git a/pkg/commands/cmd_session.go b/pkg/commands/cmd_session.go index 3957ed4e7..ece190a57 100644 --- a/pkg/commands/cmd_session.go +++ b/pkg/commands/cmd_session.go @@ -81,7 +81,7 @@ func formatSessionList(list []session.SessionMeta) string { activeMarker = "[*]" } - summary := truncateSummary(item.Summary, 40) + summary := sessionLabel(item.Summary, item.Preview, 40) age := "-" if !item.UpdatedAt.IsZero() { @@ -98,16 +98,21 @@ func formatSessionList(list []session.SessionMeta) string { return strings.Join(lines, "\n") } -func truncateSummary(s string, maxLen int) string { - s = strings.TrimSpace(s) - if s == "" { - return "(no summary)" +// sessionLabel picks the best display text for a session list row: +// summary (if available) > first user message preview > "(empty)". +func sessionLabel(summary, preview string, maxLen int) string { + text := strings.TrimSpace(summary) + if text == "" { + text = strings.TrimSpace(preview) } - s = strings.ReplaceAll(s, "\n", " ") - if len(s) <= maxLen { - return s + if text == "" { + return "(empty)" } - 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 diff --git a/pkg/commands/session_handlers_test.go b/pkg/commands/session_handlers_test.go index 735d1acbf..7102a2db8 100644 --- a/pkg/commands/session_handlers_test.go +++ b/pkg/commands/session_handlers_test.go @@ -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) { ex := NewExecutor(NewRegistry(BuiltinDefinitions()), nil) diff --git a/pkg/session/manager.go b/pkg/session/manager.go index 7bb77cdbf..a47324cca 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -52,6 +52,7 @@ type SessionMeta struct { MessageCnt int `json:"message_cnt"` Active bool `json:"active"` Summary string `json:"summary,omitempty"` + Preview string `json:"preview,omitempty"` // first user message content } type SessionManager struct { @@ -742,12 +743,22 @@ func (sm *SessionManager) buildSessionMetaListLocked(scope *scopeIndex) []Sessio meta.UpdatedAt = session.Updated meta.MessageCnt = len(session.Messages) meta.Summary = session.Summary + meta.Preview = firstUserMessage(session.Messages) } list = append(list, meta) } 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 { next := make([]string, 0, len(ordered)+1) next = append(next, sessionKey)