From b323131c27394e811984eb18da8d4aaf10d0c5ff Mon Sep 17 00:00:00 2001 From: mingmxren Date: Wed, 4 Mar 2026 01:54:32 +0800 Subject: [PATCH] improve(session): redesign /session list display for clarity - Show summary (truncated to 40 chars) as primary identifier - Move #id to trailing parentheses to reduce visual noise - Use relative time (2h ago) instead of absolute timestamps - Add Summary field to SessionMeta, populated from Session.Summary - List row number = resume parameter, zero ambiguity Before: 1. [*] agent:main:telegram:direct:300900269#10 | updated: 2026-03-01 23:22 | messages: 8 After: 1. [*] Discussing React performance... | 8 msgs | 2h ago (#10) Co-Authored-By: Claude Opus 4.6 --- pkg/commands/cmd_session.go | 60 +++++++++++++++++++++++---- pkg/commands/session_handlers_test.go | 9 +++- pkg/session/manager.go | 2 + 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/pkg/commands/cmd_session.go b/pkg/commands/cmd_session.go index 087d9570e..3957ed4e7 100644 --- a/pkg/commands/cmd_session.go +++ b/pkg/commands/cmd_session.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/sipeed/picoclaw/pkg/session" ) @@ -72,20 +73,65 @@ func sessionResumeHandler() Handler { func formatSessionList(list []session.SessionMeta) string { lines := make([]string, 0, len(list)+1) - lines = append(lines, "Sessions for current chat:") + lines = append(lines, "Sessions:") + now := time.Now() for _, item := range list { - activeMarker := " " + activeMarker := " " if item.Active { - activeMarker = "*" + activeMarker = "[*]" } - updated := "-" + + summary := truncateSummary(item.Summary, 40) + + age := "-" if !item.UpdatedAt.IsZero() { - updated = item.UpdatedAt.Format("2006-01-02 15:04") + age = relativeTime(now, item.UpdatedAt) } + + tag := extractSessionTag(item.SessionKey) + lines = append(lines, fmt.Sprintf( - "%d. [%s] %s | updated: %s | messages: %d", - item.Ordinal, activeMarker, item.SessionKey, updated, item.MessageCnt, + "%d. %s %s | %d msgs | %s (%s)", + item.Ordinal, activeMarker, summary, item.MessageCnt, age, tag, )) } return strings.Join(lines, "\n") } + +func truncateSummary(s string, maxLen int) string { + s = strings.TrimSpace(s) + if s == "" { + return "(no summary)" + } + s = strings.ReplaceAll(s, "\n", " ") + if len(s) <= maxLen { + return s + } + return s[:maxLen] + "..." +} + +// extractSessionTag returns the "#N" suffix from a session key, or "#1" for +// the initial session that has no "#" separator. +func extractSessionTag(sessionKey string) string { + if i := strings.LastIndex(sessionKey, "#"); i >= 0 { + return "#" + sessionKey[i+1:] + } + return "#1" +} + +func relativeTime(now, t time.Time) string { + d := now.Sub(t) + switch { + case d < time.Minute: + return "just now" + case d < time.Hour: + m := int(d.Minutes()) + return fmt.Sprintf("%dm ago", m) + case d < 24*time.Hour: + h := int(d.Hours()) + return fmt.Sprintf("%dh ago", h) + default: + days := int(d.Hours() / 24) + return fmt.Sprintf("%dd ago", days) + } +} diff --git a/pkg/commands/session_handlers_test.go b/pkg/commands/session_handlers_test.go index e2ccf00b2..735d1acbf 100644 --- a/pkg/commands/session_handlers_test.go +++ b/pkg/commands/session_handlers_test.go @@ -3,6 +3,7 @@ package commands import ( "context" "errors" + "strings" "testing" "time" @@ -119,6 +120,7 @@ func TestSessionHandlers_SessionList(t *testing.T) { UpdatedAt: time.Date(2026, 3, 1, 9, 7, 0, 0, time.UTC), MessageCnt: 4, Active: true, + Summary: "Discussing React performance", }, }, } @@ -135,8 +137,11 @@ func TestSessionHandlers_SessionList(t *testing.T) { if res.Outcome != OutcomeHandled { t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomeHandled) } - if reply != "Sessions for current chat:\n1. [*] scope#3 | updated: 2026-03-01 09:07 | messages: 4" { - t.Fatalf("reply=%q", reply) + // Check key elements: header, active marker, summary, message count, session tag + for _, want := range []string{"Sessions:", "[*]", "Discussing React performance", "4 msgs", "(#3)"} { + if !strings.Contains(reply, want) { + t.Fatalf("reply missing %q, got %q", want, reply) + } } } diff --git a/pkg/session/manager.go b/pkg/session/manager.go index d74ffb318..7bb77cdbf 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -51,6 +51,7 @@ type SessionMeta struct { UpdatedAt time.Time `json:"updated_at"` MessageCnt int `json:"message_cnt"` Active bool `json:"active"` + Summary string `json:"summary,omitempty"` } type SessionManager struct { @@ -740,6 +741,7 @@ func (sm *SessionManager) buildSessionMetaListLocked(scope *scopeIndex) []Sessio if session, ok := sm.sessions[key]; ok { meta.UpdatedAt = session.Updated meta.MessageCnt = len(session.Messages) + meta.Summary = session.Summary } list = append(list, meta) }