@@ -63,6 +63,12 @@ class OpencodeCore(
63 // Tracks the in-flight probeHealth coroutine. Rapid double-taps on Connect would
63 // Tracks the in-flight probeHealth coroutine. Rapid double-taps on Connect would
64 // otherwise launch two concurrent health probes; cancel the prior one first.
64 // otherwise launch two concurrent health probes; cancel the prior one first.
65 private var connectJob: Job? = null
65 private var connectJob: Job? = null
66+ // Tracks the in-flight DeleteSession coroutine so navigating away can cancel it
67+ // before its response handler mutates the sessions list / screen the user already left.
68+ private var deleteSessionJob: Job? = null
69+ // Tracks the in-flight sendMessage coroutine so navigating away or switching sessions
70+ // can cancel it before its response handler touches stale message state.
71+ private var sendMessageJob: Job? = null
66 // Message IDs removed via SSE; a late MessageUpdated for one must not resurrect it.
72 // Message IDs removed via SSE; a late MessageUpdated for one must not resurrect it.
67 private val removedMessageIds = mutableSetOf<String>()
73 private val removedMessageIds = mutableSetOf<String>()
@@ -111,6 +117,7 @@ class OpencodeCore(
111 generating = false
117 generating = false
112 error = null
118 error = null
120+ sendMessageJob?.cancel()
114 loadMessagesJob?.cancel()
121 loadMessagesJob?.cancel()
115 loadMessagesJob = scope.launch { loadMessages(event.id) }
122 loadMessagesJob = scope.launch { loadMessages(event.id) }
@@ -126,7 +133,8 @@ class OpencodeCore(
126 // Delete on the server first; only drop it from the UI once that
133 // Delete on the server first; only drop it from the UI once that
127 // succeeds, so a failed request leaves the session recoverable.
134 // succeeds, so a failed request leaves the session recoverable.
128 loading = true; error = null; emit()
135 loading = true; error = null; emit()
136+ deleteSessionJob?.cancel()
129− scope.launch {
137+ deleteSessionJob = scope.launch {
130 http.delete("$serverUrl/session/$id", authHeader()).fold(
138 http.delete("$serverUrl/session/$id", authHeader()).fold(
131 onSuccess = { resp ->
139 onSuccess = { resp ->
132 if (resp.code in 200..299) {
140 if (resp.code in 200..299) {
@@ -171,7 +179,10 @@ class OpencodeCore(
171 generating = false
179 generating = false
172 loading = true
180 loading = true
182+ connectJob?.cancel()
174 createSessionJob?.cancel()
183 createSessionJob?.cancel()
184+ deleteSessionJob?.cancel()
185+ sendMessageJob?.cancel()
175 loadMessagesJob?.cancel()
186 loadMessagesJob?.cancel()
176 loadSessionsJob?.cancel()
187 loadSessionsJob?.cancel()
177 loadSessionsJob = scope.launch { loadSessions() }
188 loadSessionsJob = scope.launch { loadSessions() }
@@ -190,7 +201,10 @@ class OpencodeCore(
190 // Reset to the default so a late-firing close callback from the old
201 // Reset to the default so a late-firing close callback from the old
191 // SSE connection can't flash the reconnecting banner on the next connect.
202 // SSE connection can't flash the reconnecting banner on the next connect.
192 sseConnected = true
203 sseConnected = true
204+ connectJob?.cancel()
193 createSessionJob?.cancel()
205 createSessionJob?.cancel()
206+ deleteSessionJob?.cancel()
207+ sendMessageJob?.cancel()
194 loadMessagesJob?.cancel()
208 loadMessagesJob?.cancel()
195 closeSse()
209 closeSse()
@@ -230,7 +244,8 @@ class OpencodeCore(
232 val body = Protocol.json.encodeToString(PromptBody.serializer(), PromptBody(listOf(TextInput(text = text))))
246 val body = Protocol.json.encodeToString(PromptBody.serializer(), PromptBody(listOf(TextInput(text = text))))
247+ sendMessageJob?.cancel()
233− scope.launch {
248+ sendMessageJob = scope.launch {
234 val result = http.postJson("$serverUrl/session/$session/prompt_async", body, authHeader())
249 val result = http.postJson("$serverUrl/session/$session/prompt_async", body, authHeader())
235 result.fold(
250 result.fold(
236 onSuccess = { resp ->
251 onSuccess = { resp ->
@@ -284,6 +299,7 @@ class OpencodeCore(
285 else -> {
300 else -> {
286 loading = false
301 loading = false
287 error = "Server returned status ${resp.code}"
303 error = "Server returned status ${resp.code}"
@@ -291,6 +307,7 @@ class OpencodeCore(
292 onFailure = { e ->
308 onFailure = { e ->
293 loading = false
309 loading = false
294 error = "Connection failed: ${e.message}"
311 error = "Connection failed: ${e.message}"