mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 11:02:26 +00:00
Fix iOS selected agent chat routing
This commit is contained in:
@@ -6,6 +6,7 @@ struct ChatProTab: View {
|
||||
@Environment(NodeAppModel.self) private var appModel
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@State private var viewModel: OpenClawChatViewModel?
|
||||
@State private var programmaticSessionSwitchCounts: [String: Int] = [:]
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
@@ -104,6 +105,14 @@ struct ChatProTab: View {
|
||||
sessionKey: sessionKey,
|
||||
transport: IOSGatewayChatTransport(gateway: self.appModel.operatorSession),
|
||||
onSessionChanged: { sessionKey in
|
||||
if self.consumeProgrammaticSessionSwitch(sessionKey) {
|
||||
// Programmatic switches complete asynchronously; stale completions
|
||||
// must repair back to the current app-model session, not become focus.
|
||||
if sessionKey != self.appModel.chatSessionKey {
|
||||
self.syncChatViewModel()
|
||||
}
|
||||
return
|
||||
}
|
||||
self.appModel.focusChatSession(sessionKey)
|
||||
},
|
||||
diagnosticsLog: { message in
|
||||
@@ -112,9 +121,26 @@ struct ChatProTab: View {
|
||||
return
|
||||
}
|
||||
guard viewModel.sessionKey != sessionKey else { return }
|
||||
self.recordProgrammaticSessionSwitch(sessionKey)
|
||||
viewModel.switchSession(to: sessionKey)
|
||||
}
|
||||
|
||||
private func recordProgrammaticSessionSwitch(_ sessionKey: String) {
|
||||
self.programmaticSessionSwitchCounts[sessionKey, default: 0] += 1
|
||||
}
|
||||
|
||||
private func consumeProgrammaticSessionSwitch(_ sessionKey: String) -> Bool {
|
||||
guard let count = self.programmaticSessionSwitchCounts[sessionKey] else {
|
||||
return false
|
||||
}
|
||||
if count <= 1 {
|
||||
self.programmaticSessionSwitchCounts[sessionKey] = nil
|
||||
} else {
|
||||
self.programmaticSessionSwitchCounts[sessionKey] = count - 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private var talkControl: OpenClawChatTalkControl {
|
||||
OpenClawChatTalkControl(
|
||||
isEnabled: self.appModel.talkMode.isEnabled,
|
||||
|
||||
@@ -502,7 +502,7 @@ struct CommandCenterTab: View {
|
||||
trailing: "session",
|
||||
color: self.gatewayConnected ? OpenClawBrand.ok : .secondary,
|
||||
progress: nil,
|
||||
route: .chat(self.appModel.chatSessionKey)),
|
||||
route: .chat(self.appModel.defaultChatSessionKey)),
|
||||
WorkItem(
|
||||
id: "talk-mode",
|
||||
icon: "waveform",
|
||||
|
||||
@@ -762,6 +762,7 @@ final class NodeAppModel {
|
||||
let selected = (self.selectedAgentId ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !selected.isEmpty, !decoded.agents.contains(where: { $0.id == selected }) {
|
||||
self.selectedAgentId = nil
|
||||
self.focusedChatSessionKey = nil
|
||||
}
|
||||
self.talkMode.updateMainSessionKey(self.mainSessionKey)
|
||||
self.homeCanvasRevision &+= 1
|
||||
@@ -779,13 +780,19 @@ final class NodeAppModel {
|
||||
|
||||
func setSelectedAgentId(_ agentId: String?) {
|
||||
let trimmed = (agentId ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let nextSelectedAgentId = trimmed.isEmpty ? nil : trimmed
|
||||
let currentSelectedAgentId = self.selectedAgentId?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let selectedAgentChanged = currentSelectedAgentId != nextSelectedAgentId
|
||||
let stableID = (self.connectedGatewayID ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if stableID.isEmpty {
|
||||
self.selectedAgentId = trimmed.isEmpty ? nil : trimmed
|
||||
self.selectedAgentId = nextSelectedAgentId
|
||||
} else {
|
||||
self.selectedAgentId = trimmed.isEmpty ? nil : trimmed
|
||||
self.selectedAgentId = nextSelectedAgentId
|
||||
GatewaySettingsStore.saveGatewaySelectedAgentId(stableID: stableID, agentId: self.selectedAgentId)
|
||||
}
|
||||
if selectedAgentChanged {
|
||||
self.focusedChatSessionKey = nil
|
||||
}
|
||||
self.talkMode.updateMainSessionKey(self.mainSessionKey)
|
||||
self.homeCanvasRevision &+= 1
|
||||
if let relay = ShareGatewayRelaySettings.loadConfig() {
|
||||
@@ -1837,9 +1844,13 @@ extension NodeAppModel {
|
||||
{
|
||||
return focused
|
||||
}
|
||||
return self.defaultChatSessionKey
|
||||
}
|
||||
|
||||
var defaultChatSessionKey: String {
|
||||
// Keep chat aligned with the gateway's resolved main session key.
|
||||
// A hardcoded "ios" base creates synthetic placeholder sessions in the chat UI.
|
||||
return self.mainSessionKey
|
||||
self.mainSessionKey
|
||||
}
|
||||
|
||||
func openChat(sessionKey: String?) {
|
||||
@@ -2005,6 +2016,7 @@ extension NodeAppModel {
|
||||
self.gatewayDefaultAgentId = nil
|
||||
self.gatewayAgents = []
|
||||
self.selectedAgentId = GatewaySettingsStore.loadGatewaySelectedAgentId(stableID: stableID)
|
||||
self.focusedChatSessionKey = nil
|
||||
self.homeCanvasRevision &+= 1
|
||||
self.apnsLastRegisteredTokenHex = nil
|
||||
}
|
||||
|
||||
@@ -214,6 +214,44 @@ private final class MockBootstrapNotificationCenter: NotificationCentering, @unc
|
||||
#expect(appModel.mainSessionKey == "agent:agent-123:main")
|
||||
}
|
||||
|
||||
@Test @MainActor func selectingAgentClearsExplicitChatFocus() {
|
||||
let appModel = NodeAppModel()
|
||||
appModel.gatewayDefaultAgentId = "main"
|
||||
let rustSessionKey = SessionKey.makeAgentSessionKey(agentId: "rust-claw", baseKey: "main")
|
||||
|
||||
appModel.setSelectedAgentId("rust-claw")
|
||||
#expect(appModel.chatSessionKey == rustSessionKey)
|
||||
appModel.focusChatSession(rustSessionKey)
|
||||
|
||||
appModel.setSelectedAgentId("main")
|
||||
#expect(appModel.defaultChatSessionKey == "main")
|
||||
#expect(appModel.mainSessionKey == "main")
|
||||
#expect(appModel.chatSessionKey == "main")
|
||||
}
|
||||
|
||||
@Test @MainActor func sameSelectedAgentKeepsExplicitChatFocus() {
|
||||
let appModel = NodeAppModel()
|
||||
appModel.gatewayDefaultAgentId = "main"
|
||||
appModel.setSelectedAgentId("main")
|
||||
appModel.openChat(sessionKey: "incident-42")
|
||||
|
||||
appModel.setSelectedAgentId("main")
|
||||
#expect(appModel.defaultChatSessionKey == "main")
|
||||
#expect(appModel.chatSessionKey == "incident-42")
|
||||
}
|
||||
|
||||
@Test @MainActor func defaultChatSessionKeyIgnoresExplicitChatFocus() {
|
||||
let appModel = NodeAppModel()
|
||||
appModel.gatewayDefaultAgentId = "main"
|
||||
appModel.setSelectedAgentId("rust-claw")
|
||||
appModel.openChat(sessionKey: "incident-42")
|
||||
|
||||
#expect(appModel.defaultChatSessionKey == SessionKey.makeAgentSessionKey(
|
||||
agentId: "rust-claw",
|
||||
baseKey: "main"))
|
||||
#expect(appModel.chatSessionKey == "incident-42")
|
||||
}
|
||||
|
||||
@Test @MainActor func execApprovalPromptPresentationTracksLatestNotificationTap() throws {
|
||||
let appModel = NodeAppModel()
|
||||
try appModel._test_presentExecApprovalPrompt(
|
||||
|
||||
Reference in New Issue
Block a user