mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(chat): restore canonical native session ordering and search (#113886)
This commit is contained in:
committed by
GitHub
parent
81b7a0a006
commit
9defe24ec2
@@ -496,14 +496,11 @@ public enum ChatSessionSidebarModel {
|
||||
// active row selectable instead of showing an empty selection.
|
||||
entries.append(self.placeholder(key: currentSessionKey))
|
||||
}
|
||||
entries.sort { (($0.updatedAt ?? $0.lastActivityAt) ?? 0) > (($1.updatedAt ?? $1.lastActivityAt) ?? 0) }
|
||||
|
||||
let needle = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
guard !needle.isEmpty else { return entries }
|
||||
return entries.filter { entry in
|
||||
self.displayName(for: entry).lowercased().contains(needle) ||
|
||||
entry.key.lowercased().contains(needle)
|
||||
}
|
||||
// Gateway, cached lists, iOS, and macOS must share the same pin
|
||||
// chronology, stable key ties, and searchable session fields.
|
||||
return OpenClawChatSessionListOrganizer.filter(
|
||||
OpenClawChatSessionListOrganizer.organize(entries),
|
||||
search: query)
|
||||
}
|
||||
|
||||
private static func placeholder(key: String) -> OpenClawChatSessionEntry {
|
||||
|
||||
@@ -8,8 +8,12 @@ struct ChatSessionSidebarModelTests {
|
||||
private func entry(
|
||||
key: String,
|
||||
displayName: String? = nil,
|
||||
label: String? = nil,
|
||||
subject: String? = nil,
|
||||
sessionId: String? = nil,
|
||||
updatedAt: Double? = nil,
|
||||
pinned: Bool? = nil,
|
||||
pinnedAt: Double? = nil,
|
||||
archived: Bool? = nil,
|
||||
unread: Bool? = nil,
|
||||
lastReadAt: Double? = nil,
|
||||
@@ -31,11 +35,11 @@ struct ChatSessionSidebarModelTests {
|
||||
kind: nil,
|
||||
displayName: displayName,
|
||||
surface: nil,
|
||||
subject: nil,
|
||||
subject: subject,
|
||||
room: nil,
|
||||
space: nil,
|
||||
updatedAt: updatedAt,
|
||||
sessionId: nil,
|
||||
sessionId: sessionId,
|
||||
systemSent: nil,
|
||||
abortedLastRun: nil,
|
||||
thinkingLevel: nil,
|
||||
@@ -46,8 +50,10 @@ struct ChatSessionSidebarModelTests {
|
||||
modelProvider: nil,
|
||||
model: nil,
|
||||
contextTokens: nil,
|
||||
label: label,
|
||||
category: category,
|
||||
pinned: pinned,
|
||||
pinnedAt: pinnedAt,
|
||||
archived: archived,
|
||||
unread: unread,
|
||||
agentStatus: agentStatus,
|
||||
@@ -80,6 +86,49 @@ struct ChatSessionSidebarModelTests {
|
||||
#expect(sections[1].title == "Recent")
|
||||
}
|
||||
|
||||
@Test func `pinned sidebar sessions follow gateway pin chronology`() {
|
||||
let sections = ChatSessionSidebarModel.sections(
|
||||
sessions: [
|
||||
self.entry(key: "pinned-old", updatedAt: 300, pinned: true, pinnedAt: 100),
|
||||
self.entry(key: "pinned-new", updatedAt: 100, pinned: true, pinnedAt: 300),
|
||||
self.entry(key: "recent", updatedAt: 400),
|
||||
],
|
||||
currentSessionKey: "recent",
|
||||
query: "")
|
||||
|
||||
#expect(sections.map(\.id) == ["pinned", "recent"])
|
||||
#expect(sections[0].nodes.map(\.session.key) == ["pinned-new", "pinned-old"])
|
||||
#expect(sections[1].nodes.map(\.session.key) == ["recent"])
|
||||
}
|
||||
|
||||
@Test func `sidebar sessions preserve deterministic gateway key ties`() {
|
||||
let sections = ChatSessionSidebarModel.sections(
|
||||
sessions: [
|
||||
self.entry(key: "z-tie", updatedAt: 100),
|
||||
self.entry(key: "a-tie", updatedAt: 100),
|
||||
self.entry(key: "m-tie", updatedAt: 100),
|
||||
],
|
||||
currentSessionKey: "a-tie",
|
||||
query: "")
|
||||
|
||||
#expect(sections.flatMap(\.nodes).map(\.session.key) == ["a-tie", "m-tie", "z-tie"])
|
||||
}
|
||||
|
||||
@Test func `gateway groups preserve canonical session order`() {
|
||||
let sections = ChatSessionSidebarModel.sections(
|
||||
sessions: [
|
||||
self.entry(key: "project-z", updatedAt: 100, category: "Projects"),
|
||||
self.entry(key: "project-a", updatedAt: 100, category: "Projects"),
|
||||
self.entry(key: "recent", updatedAt: 50),
|
||||
],
|
||||
currentSessionKey: "recent",
|
||||
groups: [OpenClawChatSessionGroup(name: "Projects", position: 0)],
|
||||
query: "")
|
||||
|
||||
#expect(sections.map(\.id) == ["group:Projects", "recent"])
|
||||
#expect(sections[0].nodes.map(\.session.key) == ["project-a", "project-z"])
|
||||
}
|
||||
|
||||
@Test func `single unpinned section carries no title`() {
|
||||
let sections = ChatSessionSidebarModel.sections(
|
||||
sessions: [self.entry(key: "a", updatedAt: 100)],
|
||||
@@ -258,6 +307,52 @@ struct ChatSessionSidebarModelTests {
|
||||
#expect(sections.flatMap(\.nodes).map(\.session.key) == ["agent:main:research"])
|
||||
}
|
||||
|
||||
@Test(arguments: ["holiday", "KYOTO", "session-123", " HoLiDaY "])
|
||||
func `sidebar search matches every canonical gateway session field`(_ query: String) {
|
||||
let matching = self.entry(
|
||||
key: "agent:main:roadmap",
|
||||
displayName: "Planning",
|
||||
label: "Summer holiday",
|
||||
subject: "Kyoto itinerary",
|
||||
sessionId: "session-123",
|
||||
updatedAt: 200)
|
||||
let other = self.entry(
|
||||
key: "agent:main:other",
|
||||
displayName: "Unrelated",
|
||||
updatedAt: 100)
|
||||
let sections = ChatSessionSidebarModel.sections(
|
||||
sessions: [other, matching],
|
||||
currentSessionKey: other.key,
|
||||
query: query)
|
||||
|
||||
#expect(sections.flatMap(\.nodes).map(\.session.key) == [matching.key])
|
||||
}
|
||||
|
||||
@Test(arguments: ["holiday", "KYOTO", "session-123"])
|
||||
func `canonical search never reveals hidden or archived sidebar sessions`(_ query: String) {
|
||||
let archived = self.entry(
|
||||
key: "agent:main:archived",
|
||||
label: "Summer holiday",
|
||||
subject: "Kyoto itinerary",
|
||||
sessionId: "session-123",
|
||||
updatedAt: 300,
|
||||
archived: true)
|
||||
let onboarding = self.entry(
|
||||
key: "agent:main:onboarding",
|
||||
label: "Summer holiday",
|
||||
subject: "Kyoto itinerary",
|
||||
sessionId: "session-123",
|
||||
updatedAt: 200)
|
||||
let active = self.entry(key: "agent:main:active", updatedAt: 100)
|
||||
|
||||
let sections = ChatSessionSidebarModel.sections(
|
||||
sessions: [archived, onboarding, active],
|
||||
currentSessionKey: active.key,
|
||||
query: query)
|
||||
|
||||
#expect(sections.isEmpty)
|
||||
}
|
||||
|
||||
@Test func `session keys render as human names`() {
|
||||
#expect(ChatSessionSidebarModel.displayName(forKey: "agent:main:main") == "main")
|
||||
#expect(ChatSessionSidebarModel.displayName(forKey: "agent:ops:standup") == "standup (ops)")
|
||||
|
||||
Reference in New Issue
Block a user