mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
fix(jetbrains): stabilize workspace reload state
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@ sealed class KiloAppState {
|
||||
data object Connecting : KiloAppState()
|
||||
data class Loading(val progress: LoadProgress) : KiloAppState()
|
||||
data class MigrationRequired(val detection: LegacyMigrationDetection) : KiloAppState()
|
||||
data class Ready(val data: AppData) : KiloAppState()
|
||||
data class Ready(val data: AppData, val rev: Long = 0) : KiloAppState()
|
||||
data class Error(val message: String, val errors: List<LoadError> = emptyList()) : KiloAppState()
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -57,6 +57,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.net.ConnectException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
@@ -118,6 +119,7 @@ class KiloBackendAppService private constructor(
|
||||
private var loader: Job? = null
|
||||
private var closed = false
|
||||
private val loadLock = Any()
|
||||
private val rev = AtomicLong()
|
||||
|
||||
private val _appState = MutableStateFlow<KiloAppState>(KiloAppState.Disconnected)
|
||||
val appState: StateFlow<KiloAppState> = _appState.asStateFlow()
|
||||
@@ -338,7 +340,6 @@ class KiloBackendAppService private constructor(
|
||||
private fun load() {
|
||||
synchronized(loadLock) {
|
||||
loader?.cancel()
|
||||
eventWatcher?.cancel()
|
||||
loader = cs.launch {
|
||||
val start = System.currentTimeMillis()
|
||||
log.info("Application starting — loading config, profile, notifications")
|
||||
@@ -655,7 +656,7 @@ class KiloBackendAppService private constructor(
|
||||
private fun setAppReady(data: AppData) {
|
||||
warnings = data.warnings
|
||||
if (data.warnings.isNotEmpty()) warnAppWarnings(data.warnings)
|
||||
_appState.value = KiloAppState.Ready(data)
|
||||
_appState.value = KiloAppState.Ready(data, rev.incrementAndGet())
|
||||
}
|
||||
|
||||
private fun setAppError(message: String, errors: List<LoadError>) {
|
||||
|
||||
+42
-35
@@ -19,9 +19,7 @@ import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -472,23 +470,25 @@ class KiloBackendWorkspaceTest {
|
||||
ws.state.first { it is KiloWorkspaceState.Ready }
|
||||
}
|
||||
|
||||
// Fire rapid reloads — simulates rapid SSE disposed events
|
||||
mock.providers = OPENAI_PROVIDERS_JSON
|
||||
val before = mock.requestCount("/provider")
|
||||
|
||||
repeat(5) { ws.reload() }
|
||||
|
||||
// Final state must be valid Ready
|
||||
withTimeout(15_000) {
|
||||
while (true) {
|
||||
val state = ws.state.value
|
||||
if (state is KiloWorkspaceState.Ready) {
|
||||
delay(300)
|
||||
if (ws.state.value is KiloWorkspaceState.Ready) break
|
||||
}
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
assertTrue(
|
||||
mock.awaitRequestCount("/provider", before + 1),
|
||||
"Workspace reload did not request providers; state=${ws.state.value}; logs=${log.messages}",
|
||||
)
|
||||
|
||||
val state = withTimeout(15_000) {
|
||||
ws.state.first {
|
||||
it is KiloWorkspaceState.Ready &&
|
||||
it.providers.providers.firstOrNull()?.id == "openai"
|
||||
}
|
||||
} as KiloWorkspaceState.Ready
|
||||
|
||||
val state = ws.state.value as KiloWorkspaceState.Ready
|
||||
assertEquals(1, state.providers.providers.size)
|
||||
assertEquals("openai", state.providers.providers[0].id)
|
||||
assertEquals(1, state.agents.agents.size)
|
||||
}
|
||||
|
||||
@@ -502,35 +502,29 @@ class KiloBackendWorkspaceTest {
|
||||
val app = setup()
|
||||
val initial = ready(app)
|
||||
|
||||
// Change providers response then fire disposed event
|
||||
mock.providers = """{
|
||||
"all": [{
|
||||
"id": "openai",
|
||||
"name": "OpenAI",
|
||||
"source": "api",
|
||||
"env": [],
|
||||
"options": {},
|
||||
"models": {}
|
||||
}],
|
||||
"default": {},
|
||||
"connected": ["openai"]
|
||||
}"""
|
||||
mock.providers = OPENAI_PROVIDERS_JSON
|
||||
|
||||
assertTrue(mock.awaitSseConnection())
|
||||
val prev = (app.appState.value as KiloAppState.Ready).rev
|
||||
val before = mock.requestCount("/global/config")
|
||||
val reload = async(start = CoroutineStart.UNDISPATCHED) {
|
||||
app.appState.drop(1).first { it is KiloAppState.Ready }
|
||||
app.appState.first { it is KiloAppState.Ready && it.rev > prev }
|
||||
}
|
||||
mock.pushEvent("global.disposed", """{"type":"global.disposed"}""")
|
||||
assertTrue(
|
||||
mock.awaitRequestCount("/global/config", before + 1),
|
||||
"global.disposed did not start app reload; state=${app.appState.value}; logs=${log.messages}",
|
||||
)
|
||||
withTimeout(15_000) { reload.await() }
|
||||
|
||||
// Get a fresh workspace — old one was stopped during reload
|
||||
val ws = app.workspaces.get("/test/project")
|
||||
assertTrue(ws !== initial)
|
||||
withTimeout(15_000) {
|
||||
ws.state.first { it is KiloWorkspaceState.Ready }
|
||||
}
|
||||
|
||||
val state = ws.state.value as KiloWorkspaceState.Ready
|
||||
val state = withTimeout(15_000) {
|
||||
ws.state.first {
|
||||
it is KiloWorkspaceState.Ready &&
|
||||
it.providers.providers.firstOrNull()?.id == "openai"
|
||||
}
|
||||
} as KiloWorkspaceState.Ready
|
||||
assertEquals("openai", state.providers.providers[0].id)
|
||||
}
|
||||
|
||||
@@ -572,6 +566,19 @@ class KiloBackendWorkspaceTest {
|
||||
"connected": ["anthropic"]
|
||||
}""".trimIndent()
|
||||
|
||||
private val OPENAI_PROVIDERS_JSON = """{
|
||||
"all": [{
|
||||
"id": "openai",
|
||||
"name": "OpenAI",
|
||||
"source": "api",
|
||||
"env": [],
|
||||
"options": {},
|
||||
"models": {}
|
||||
}],
|
||||
"default": {},
|
||||
"connected": ["openai"]
|
||||
}""".trimIndent()
|
||||
|
||||
private val AGENTS_JSON = """[
|
||||
{"name":"code","displayName":"Code","mode":"primary","permission":[],"options":{}}
|
||||
]""".trimIndent()
|
||||
|
||||
Reference in New Issue
Block a user