@@ -40,12 +40,12 @@ class ChatControllerReconnectRestoreTest {
4040
4141 @Test
4242 @OptIn(ExperimentalCoroutinesApi ::class )
43- fun connectedRefreshCreatesDeviceSessionBeforeLoadingHistory () =
43+ fun connectedRefreshUpsertsDeviceSessionBeforeLoadingHistory () =
4444 runTest {
4545 val sessionKey = " agent:main:node-device"
4646 val gateway = ScriptedGateway (json)
4747 gateway.respondWith(" sessions.describe" , """ {"session":null}""" )
48- gateway.respondWith(" sessions.create " , """ {"ok":true,"key":"$sessionKey "}""" )
48+ gateway.respondWith(" sessions.patch " , """ {"ok":true,"key":"$sessionKey "}""" )
4949 gateway.respondWith(" chat.history" , historyResponse(" session-1" , emptyList()))
5050 val controller = newScopedController(gateway)
5151
@@ -57,16 +57,15 @@ class ChatControllerReconnectRestoreTest {
5757 runCurrent()
5858
5959 val describeIndex = gateway.calls.indexOfFirst { it.method == " sessions.describe" }
60- val createIndex = gateway.calls.indexOfFirst { it.method == " sessions.create " }
60+ val patchIndex = gateway.calls.indexOfFirst { it.method == " sessions.patch " }
6161 val historyIndex = gateway.calls.indexOfFirst { it.method == " chat.history" }
6262 assertTrue(describeIndex >= 0 )
63- assertTrue(createIndex > describeIndex)
64- assertTrue(historyIndex > createIndex )
63+ assertTrue(patchIndex > describeIndex)
64+ assertTrue(historyIndex > patchIndex )
6565 assertEquals(sessionKey, controller.sessionKey.value)
66- val createParams = json.parseToJsonElement(gateway.calls[createIndex].paramsJson.orEmpty()).jsonObject
67- assertEquals(sessionKey, createParams[" key" ]?.jsonPrimitive?.content)
68- assertEquals(" main" , createParams[" agentId" ]?.jsonPrimitive?.content)
69- assertEquals(" OpenClaw App · Pixel · device" , createParams[" label" ]?.jsonPrimitive?.content)
66+ val patchParams = json.parseToJsonElement(gateway.calls[patchIndex].paramsJson.orEmpty()).jsonObject
67+ assertEquals(sessionKey, patchParams[" key" ]?.jsonPrimitive?.content)
68+ assertEquals(" OpenClaw App · Pixel · device" , patchParams[" label" ]?.jsonPrimitive?.content)
7069 }
7170
7271 @Test
@@ -76,15 +75,15 @@ class ChatControllerReconnectRestoreTest {
7675 val sessionKey = " agent:main:node-device"
7776 val gateway = ScriptedGateway (json)
7877 gateway.respondWith(" sessions.describe" , """ {"session":null}""" )
79- gateway.respond(" sessions.create " ) { error(" create unavailable" ) }
78+ gateway.respond(" sessions.patch " ) { error(" patch unavailable" ) }
8079 gateway.respondWith(" chat.history" , historyResponse(" session-1" , emptyList()))
8180 val controller = newScopedController(gateway)
8281
8382 controller.prepareMainSessionKey(sessionKey)
8483 controller.onGatewayConnected(MainSessionBinding (sessionKey, " OpenClaw App · Pixel · device" ))
8584 runCurrent()
8685
87- assertEquals(1 , gateway.callCount(" sessions.create " ))
86+ assertEquals(1 , gateway.callCount(" sessions.patch " ))
8887 assertEquals(1 , gateway.callCount(" chat.history" ))
8988 assertEquals(sessionKey, controller.sessionKey.value)
9089 assertNull(controller.errorText.value)
@@ -158,7 +157,7 @@ class ChatControllerReconnectRestoreTest {
158157 storedLabel?.let { """ {"session":{"key":"$sessionKey ","label":"$it "}}""" }
159158 ? : """ {"session":null}"""
160159 }
161- gateway.respond(" sessions.create " ) { paramsJson ->
160+ gateway.respond(" sessions.patch " ) { paramsJson ->
162161 storedLabel =
163162 json
164163 .parseToJsonElement(paramsJson.orEmpty())
@@ -178,15 +177,15 @@ class ChatControllerReconnectRestoreTest {
178177 controller.onGatewayConnected(binding)
179178 runCurrent()
180179
181- assertEquals(1 , gateway.callCount(" sessions.create " ))
180+ assertEquals(1 , gateway.callCount(" sessions.patch " ))
182181 assertEquals(2 , gateway.callCount(" sessions.describe" ))
183182 assertEquals(2 , gateway.callCount(" chat.history" ))
184183
185184 storedLabel = " My Android session"
186185 controller.onGatewayConnected(binding.copy(label = " OpenClaw App · Renamed · device" ))
187186 runCurrent()
188187
189- assertEquals(1 , gateway.callCount(" sessions.create " ))
188+ assertEquals(1 , gateway.callCount(" sessions.patch " ))
190189 assertEquals(3 , gateway.callCount(" sessions.describe" ))
191190 assertEquals(3 , gateway.callCount(" chat.history" ))
192191 assertEquals(" My Android session" , storedLabel)
@@ -207,7 +206,7 @@ class ChatControllerReconnectRestoreTest {
207206 ?.content
208207 if (key == " agent:first:node-device" ) firstDescribe.await() else """ {"session":null}"""
209208 }
210- gateway.respond(" sessions.create " ) { paramsJson ->
209+ gateway.respond(" sessions.patch " ) { paramsJson ->
211210 val key =
212211 json
213212 .parseToJsonElement(paramsJson.orEmpty())
@@ -227,29 +226,32 @@ class ChatControllerReconnectRestoreTest {
227226 controller.refresh()
228227 runCurrent()
229228
230- val createIndexBeforeFirstDescribeCompletes =
231- gateway.calls.indexOfLast { it.method == " sessions.create" }
232- val historyCallsBeforeFirstDescribeCompletes =
233- gateway.calls.withIndex().filter { it.value.method == " chat.history" }
234- assertTrue(createIndexBeforeFirstDescribeCompletes >= 0 )
235- assertTrue(historyCallsBeforeFirstDescribeCompletes.isNotEmpty())
236- assertTrue(historyCallsBeforeFirstDescribeCompletes.all { it.index > createIndexBeforeFirstDescribeCompletes })
237- assertTrue(
238- historyCallsBeforeFirstDescribeCompletes.all {
239- gateway.sessionKeyOf(it.value.paramsJson) == " agent:second:node-device"
240- },
241- )
242- firstDescribe.complete(""" {"session":null}""" )
243- runCurrent()
244-
245- val createIndex = gateway.calls.indexOfLast { it.method == " sessions.create" }
229+ val patchCalls = gateway.calls.withIndex().filter { it.value.method == " sessions.patch" }
230+ val patchIndex = patchCalls.single().index
246231 val historyCalls = gateway.calls.withIndex().filter { it.value.method == " chat.history" }
247- assertEquals(1 , gateway.callCount(" sessions.create" ))
248- assertTrue(createIndex >= 0 )
232+ val patchParams =
233+ patchCalls
234+ .single()
235+ .value
236+ .paramsJson
237+ .orEmpty()
238+ val patchedKey =
239+ json
240+ .parseToJsonElement(patchParams)
241+ .jsonObject[" key" ]
242+ ?.jsonPrimitive
243+ ?.content
244+ assertEquals(" agent:second:node-device" , patchedKey)
249245 assertTrue(historyCalls.isNotEmpty())
250- assertTrue(historyCalls.all { it.index > createIndex })
246+ assertTrue(historyCalls.all { it.index > patchIndex })
251247 assertTrue(historyCalls.all { gateway.sessionKeyOf(it.value.paramsJson) == " agent:second:node-device" })
252248 assertEquals(" agent:second:node-device" , controller.sessionKey.value)
249+
250+ // The cancelled response must remain inert even if its server-side work completes later.
251+ firstDescribe.complete(""" {"session":null}""" )
252+ runCurrent()
253+ assertEquals(1 , gateway.callCount(" sessions.patch" ))
254+ assertTrue(gateway.calls.none { it.method == " chat.history" && gateway.sessionKeyOf(it.paramsJson) == " agent:first:node-device" })
253255 }
254256
255257 @Test
@@ -325,7 +327,7 @@ class ChatControllerReconnectRestoreTest {
325327
326328 @Test
327329 @OptIn(ExperimentalCoroutinesApi ::class )
328- fun reconnectRecreatesSessionDeletedWhileDisconnected () =
330+ fun reconnectUpsertsSessionDeletedWhileDisconnected () =
329331 runTest {
330332 val sessionKey = " agent:main:node-device"
331333 val gateway = ScriptedGateway (json)
@@ -337,7 +339,7 @@ class ChatControllerReconnectRestoreTest {
337339 """ {"session":null}"""
338340 }
339341 }
340- gateway.respond(" sessions.create " ) {
342+ gateway.respond(" sessions.patch " ) {
341343 sessionExists = true
342344 """ {"ok":true,"key":"$sessionKey "}"""
343345 }
@@ -354,7 +356,7 @@ class ChatControllerReconnectRestoreTest {
354356 runCurrent()
355357
356358 assertEquals(2 , gateway.callCount(" sessions.describe" ))
357- assertEquals(2 , gateway.callCount(" sessions.create " ))
359+ assertEquals(2 , gateway.callCount(" sessions.patch " ))
358360 }
359361
360362 @Test
0 commit comments