@@ -17,58 +17,89 @@ enum WebChatPresentation {
1717 case panel( anchorProvider: ( ) -> NSRect ? )
1818}
1919
20+ struct WebChatRoute : Equatable , Sendable {
21+ let sessionKey : String
22+ let agentID : String ?
23+
24+ init ( sessionKey: String , agentID: String ? ) {
25+ self . sessionKey = sessionKey
26+ self . agentID = Self . normalizedAgentID ( agentID)
27+ }
28+
29+ func replacingSessionKey( _ sessionKey: String ) -> Self {
30+ Self ( sessionKey: sessionKey, agentID: self . agentID)
31+ }
32+
33+ static func normalizedAgentID( _ agentID: String ? ) -> String ? {
34+ let normalized = agentID? . trimmingCharacters ( in: . whitespacesAndNewlines) . lowercased ( )
35+ return normalized? . isEmpty == false ? normalized : nil
36+ }
37+ }
38+
2039@MainActor
2140final class WebChatManager {
2241 static let shared = WebChatManager ( )
2342
2443 private var windowController : WebChatSwiftUIWindowController ?
25- private var windowSessionKey : String ?
44+ private var windowRoute : WebChatRoute ?
2645 private var panelController : WebChatSwiftUIWindowController ?
27- private var panelSessionKey : String ?
28- private var currentChatSessionKey : String ?
46+ private var panelRoute : WebChatRoute ?
47+ private var currentChatRoute : WebChatRoute ?
2948 private var cachedPreferredSessionKey : String ?
3049
3150 var onPanelVisibilityChanged : ( ( Bool ) -> Void ) ?
3251
3352 var activeSessionKey : String ? {
34- self . currentChatSessionKey ?? self . panelSessionKey ?? self . windowSessionKey
53+ self . currentChatRoute ? . sessionKey ?? self . panelRoute ? . sessionKey ?? self . windowRoute ? . sessionKey
3554 }
3655
37- func show( sessionKey: String ) {
56+ func show( sessionKey: String , agentID: String ? = nil ) {
57+ let route = WebChatRoute ( sessionKey: sessionKey, agentID: agentID)
3858 self . closePanel ( )
3959 if let controller = self . windowController {
4060 // The window shell switches sessions in place (sidebar, /new);
41- // windowSessionKey tracks those switches, so a window already on
42- // the requested session must not be torn down and re-bootstrapped.
43- if self . windowSessionKey == sessionKey {
61+ // full route identity tracks those switches and the global owner.
62+ if Self . shouldReuseController ( currentRoute: self . windowRoute, requestedRoute: route) {
4463 controller. show ( )
4564 return
4665 }
4766
4867 controller. close ( )
4968 self . windowController = nil
50- self . windowSessionKey = nil
69+ self . windowRoute = nil
5170 }
52- let controller = WebChatSwiftUIWindowController ( sessionKey: sessionKey, presentation: . window)
71+ let controller = WebChatSwiftUIWindowController (
72+ sessionKey: route. sessionKey,
73+ agentID: route. agentID,
74+ presentation: . window)
5375 controller. onVisibilityChanged = { [ weak self] visible in
5476 self ? . onPanelVisibilityChanged ? ( visible)
5577 }
56- controller. onSessionKeyChanged = { [ weak self] key in
57- self ? . windowSessionKey = key
58- self ? . currentChatSessionKey = key
78+ controller. onSessionKeyChanged = { [ weak self, weak controller] key in
79+ guard let self, let controller, self . windowController === controller else { return }
80+ // Retaining the agent is safe: this surface has no in-window agent switcher,
81+ // and the controller pins explicit agents against gateway-default changes.
82+ let updatedRoute = ( self . windowRoute ?? route) . replacingSessionKey ( key)
83+ self . windowRoute = updatedRoute
84+ self . currentChatRoute = updatedRoute
5985 }
6086 self . windowController = controller
61- self . windowSessionKey = sessionKey
62- self . currentChatSessionKey = sessionKey
87+ self . windowRoute = route
88+ self . currentChatRoute = route
6389 controller. show ( )
6490 }
6591
66- func togglePanel( sessionKey: String , anchorProvider: @escaping ( ) -> NSRect ? ) {
92+ func togglePanel(
93+ sessionKey: String ,
94+ agentID: String ? = nil ,
95+ anchorProvider: @escaping ( ) -> NSRect ? )
96+ {
97+ let route = WebChatRoute ( sessionKey: sessionKey, agentID: agentID)
6798 if let controller = self . panelController {
68- if self . panelSessionKey != sessionKey {
99+ if !Self . shouldReuseController ( currentRoute : self . panelRoute , requestedRoute : route ) {
69100 controller. close ( )
70101 self . panelController = nil
71- self . panelSessionKey = nil
102+ self . panelRoute = nil
72103 } else {
73104 if controller. isVisible {
74105 controller. close ( )
@@ -80,28 +111,33 @@ final class WebChatManager {
80111 }
81112
82113 let controller = WebChatSwiftUIWindowController (
83- sessionKey: sessionKey,
114+ sessionKey: route. sessionKey,
115+ agentID: route. agentID,
84116 presentation: . panel( anchorProvider: anchorProvider) )
85117 controller. onClosed = { [ weak self] in
86118 self ? . panelHidden ( )
87119 }
88120 controller. onVisibilityChanged = { [ weak self] visible in
89121 self ? . onPanelVisibilityChanged ? ( visible)
90122 }
91- controller. onSessionKeyChanged = { [ weak self] key in
92- self ? . panelSessionKey = key
93- self ? . currentChatSessionKey = key
123+ controller. onSessionKeyChanged = { [ weak self, weak controller] key in
124+ guard let self, let controller, self . panelController === controller else { return }
125+ let updatedRoute = ( self . panelRoute ?? route) . replacingSessionKey ( key)
126+ self . panelRoute = updatedRoute
127+ self . currentChatRoute = updatedRoute
94128 }
95129 self . panelController = controller
96- self . panelSessionKey = sessionKey
97- self . currentChatSessionKey = sessionKey
130+ self . panelRoute = route
131+ self . currentChatRoute = route
98132 controller. presentAnchored ( anchorProvider: anchorProvider)
99133 }
100134
101135 func recordActiveSessionKey( _ sessionKey: String ) {
102136 let trimmed = sessionKey. trimmingCharacters ( in: . whitespacesAndNewlines)
103137 guard !trimmed. isEmpty else { return }
104- self . currentChatSessionKey = trimmed
138+ let route = self . currentChatRoute ?? self . panelRoute ?? self . windowRoute
139+ self . currentChatRoute = route? . replacingSessionKey ( trimmed)
140+ ?? WebChatRoute ( sessionKey: trimmed, agentID: nil )
105141 }
106142
107143 func closePanel( ) {
@@ -118,11 +154,11 @@ final class WebChatManager {
118154 func resetTunnels( ) {
119155 self . windowController? . close ( )
120156 self . windowController = nil
121- self . windowSessionKey = nil
157+ self . windowRoute = nil
122158 self . panelController? . close ( )
123159 self . panelController = nil
124- self . panelSessionKey = nil
125- self . currentChatSessionKey = nil
160+ self . panelRoute = nil
161+ self . currentChatRoute = nil
126162 self . cachedPreferredSessionKey = nil
127163 }
128164
@@ -134,4 +170,11 @@ final class WebChatManager {
134170 self . onPanelVisibilityChanged ? ( false )
135171 // Keep panel controller cached so reopening doesn't re-bootstrap.
136172 }
173+
174+ static func shouldReuseController(
175+ currentRoute: WebChatRoute ? ,
176+ requestedRoute: WebChatRoute ) -> Bool
177+ {
178+ currentRoute == requestedRoute
179+ }
137180}
0 commit comments