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:
parent
b323131c27
commit
c917184375
3 changed files with 66 additions and 9 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue