mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
Chat UI: accept canonical main session key alias (#20311)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: a4ed5235bc
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- UI/Sessions: accept the canonical main session-key alias in Chat UI flows so main-session routing stays consistent. (#20311) Thanks @mbelinky.
|
||||
- iOS/Onboarding: prevent pairing-status flicker during auto-resume by keeping resumed state transitions stable. (#20310) Thanks @mbelinky.
|
||||
- OpenClawKit/Protocol: preserve JSON boolean literals (`true`/`false`) when bridging through `AnyCodable` so Apple client RPC params no longer re-encode booleans as `1`/`0`. Thanks @mbelinky.
|
||||
- iOS/Onboarding: stabilize pairing and reconnect behavior by resetting stale pairing request state on manual retry, disconnecting both operator and node gateways on operator failure, and avoiding duplicate pairing loops from operator transport identity attachment. (#20056) Thanks @mbelinky.
|
||||
|
||||
@@ -447,7 +447,10 @@ public final class OpenClawChatViewModel {
|
||||
// even when this view currently uses an alias key (for example "main").
|
||||
// Never drop events for our own pending run on key mismatch, or the UI can stay
|
||||
// stuck at "thinking" until the user reopens and forces a history reload.
|
||||
if let sessionKey = chat.sessionKey, sessionKey != self.sessionKey, !isOurRun {
|
||||
if let sessionKey = chat.sessionKey,
|
||||
!Self.matchesCurrentSessionKey(incoming: sessionKey, current: self.sessionKey),
|
||||
!isOurRun
|
||||
{
|
||||
return
|
||||
}
|
||||
if !isOurRun {
|
||||
@@ -481,6 +484,21 @@ public final class OpenClawChatViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
private static func matchesCurrentSessionKey(incoming: String, current: String) -> Bool {
|
||||
let incomingNormalized = incoming.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
let currentNormalized = current.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
if incomingNormalized == currentNormalized {
|
||||
return true
|
||||
}
|
||||
// Common alias pair in operator clients: UI uses "main" while gateway emits canonical.
|
||||
if (incomingNormalized == "agent:main:main" && currentNormalized == "main") ||
|
||||
(incomingNormalized == "main" && currentNormalized == "agent:main:main")
|
||||
{
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func handleAgentEvent(_ evt: OpenClawAgentEventPayload) {
|
||||
if let sessionId, evt.runId != sessionId {
|
||||
return
|
||||
|
||||
@@ -261,6 +261,56 @@ extension TestChatTransportState {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func acceptsCanonicalSessionKeyEventsForExternalRuns() async throws {
|
||||
let now = Date().timeIntervalSince1970 * 1000
|
||||
let history1 = OpenClawChatHistoryPayload(
|
||||
sessionKey: "main",
|
||||
sessionId: "sess-main",
|
||||
messages: [
|
||||
AnyCodable([
|
||||
"role": "user",
|
||||
"content": [["type": "text", "text": "first"]],
|
||||
"timestamp": now,
|
||||
]),
|
||||
],
|
||||
thinkingLevel: "off")
|
||||
let history2 = OpenClawChatHistoryPayload(
|
||||
sessionKey: "main",
|
||||
sessionId: "sess-main",
|
||||
messages: [
|
||||
AnyCodable([
|
||||
"role": "user",
|
||||
"content": [["type": "text", "text": "first"]],
|
||||
"timestamp": now,
|
||||
]),
|
||||
AnyCodable([
|
||||
"role": "assistant",
|
||||
"content": [["type": "text", "text": "from external run"]],
|
||||
"timestamp": now + 1,
|
||||
]),
|
||||
],
|
||||
thinkingLevel: "off")
|
||||
|
||||
let transport = TestChatTransport(historyResponses: [history1, history2])
|
||||
let vm = await MainActor.run { OpenClawChatViewModel(sessionKey: "main", transport: transport) }
|
||||
|
||||
await MainActor.run { vm.load() }
|
||||
try await waitUntil("bootstrap") { await MainActor.run { vm.messages.count == 1 } }
|
||||
|
||||
transport.emit(
|
||||
.chat(
|
||||
OpenClawChatEventPayload(
|
||||
runId: "external-run",
|
||||
sessionKey: "agent:main:main",
|
||||
state: "final",
|
||||
message: nil,
|
||||
errorMessage: nil)))
|
||||
|
||||
try await waitUntil("history refresh after canonical external event") {
|
||||
await MainActor.run { vm.messages.count == 2 }
|
||||
}
|
||||
}
|
||||
|
||||
@Test func preservesMessageIDsAcrossHistoryRefreshes() async throws {
|
||||
let now = Date().timeIntervalSince1970 * 1000
|
||||
let history1 = OpenClawChatHistoryPayload(
|
||||
|
||||
Reference in New Issue
Block a user