Chat UI: guard compact retries

This commit is contained in:
scoootscooob 2026-03-24 10:56:40 -07:00 committed by scoootscooob
parent 19093112ce
commit a395c757ab
2 changed files with 65 additions and 1 deletions

View file

@ -60,6 +60,9 @@ public final class OpenClawChatViewModel {
private var nextThinkingSelectionRequestID: UInt64 = 0
private var latestThinkingSelectionRequestIDsBySession: [String: UInt64] = [:]
private var latestThinkingLevelsBySession: [String: String] = [:]
private var isCompacting = false
private var lastCompactAt: Date?
private let compactCooldown: TimeInterval = 60
private var pendingToolCallsById: [String: OpenClawChatPendingToolCall] = [:] {
didSet {
@ -630,9 +633,26 @@ public final class OpenClawChatViewModel {
}
private func performCompact() async {
guard !self.isCompacting else { return }
guard !self.isSending, self.pendingRuns.isEmpty, !self.isAborting else {
self.errorText = "Wait for the current response before compacting the session."
return
}
if let lastCompactAt,
Date().timeIntervalSince(lastCompactAt) < self.compactCooldown
{
self.errorText = "Please wait before compacting this session again."
return
}
self.isCompacting = true
self.lastCompactAt = Date()
self.isLoading = true
self.errorText = nil
defer { self.isLoading = false }
defer {
self.isLoading = false
self.isCompacting = false
}
do {
try await self.transport.compactSession(sessionKey: self.sessionKey)

View file

@ -989,6 +989,50 @@ extension TestChatTransportState {
#expect(await MainActor.run { vm.errorText } == "Unable to compact the session. Please try again.")
}
@Test func compactTriggerIgnoresConcurrentAndImmediateRepeatRequests() async throws {
let before = historyPayload(
messages: [
chatTextMessage(role: "assistant", text: "before compact", timestamp: 1),
])
let after = historyPayload(
messages: [
chatTextMessage(role: "assistant", text: "after compact", timestamp: 2),
])
let gate = AsyncGate()
let (transport, vm) = await makeViewModel(
historyResponses: [before, after],
compactSessionHook: { _ in
await gate.wait()
})
try await loadAndWaitBootstrap(vm: vm)
await MainActor.run {
vm.input = "/compact"
vm.send()
vm.input = "/compact"
vm.send()
}
try await waitUntil("single compact request issued") {
await transport.compactSessionKeys() == ["main"]
}
#expect(await MainActor.run { vm.errorText } == nil)
await gate.open()
try await waitUntil("history reloaded after compact") {
await MainActor.run { vm.messages.first?.content.first?.text == "after compact" }
}
await MainActor.run {
vm.input = "/compact"
vm.send()
}
try await Task.sleep(for: .milliseconds(50))
#expect(await transport.compactSessionKeys() == ["main"])
#expect(await MainActor.run { vm.errorText } == "Please wait before compacting this session again.")
}
@Test func bootstrapsModelSelectionFromSessionAndDefaults() async throws {
let now = Date().timeIntervalSince1970 * 1000
let history = historyPayload()