fix(jetbrains): finish legacy migration progress

This commit is contained in:
kirillk
2026-05-25 15:10:55 -04:00
parent b37e3fd2a1
commit 4a6ddb5d3a
16 changed files with 260 additions and 15 deletions
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Migrate JetBrains autocomplete settings and finish skipped chat history migration progress.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Open the migration flow directly on settings selection and support removing the legacy settings file after migration.
@@ -68,6 +68,15 @@ class LegacySettingsFileMigrationStore(
override fun cleanup(targets: LegacyCleanupTargets): LegacyCleanupReport {
val root = read()?.toMutableMap() ?: return LegacyCleanupReport(cleaned = emptyList(), errors = emptyList())
if (targets.legacySettingsFile) {
val err = runCatching {
if (file.delete()) null else "Failed to delete ${file.absolutePath}"
}.getOrElse { it.message ?: "Failed to delete ${file.absolutePath}" }
return LegacyCleanupReport(
cleaned = if (err == null) listOf("legacySettingsFile") else emptyList(),
errors = listOfNotNull(err),
)
}
val cleaned = mutableListOf<String>()
if (targets.providerProfiles && root.remove("providerProfiles") != null) cleaned.add("providerProfiles")
if (targets.mcpSettings && root.remove("mcpSettings") != null) cleaned.add("mcpSettings")
@@ -218,15 +218,19 @@ class LegacyMigrationEngine(
// Check if already exists (unless force)
if (!sel.force && backend.sessionExists(sessionId)) {
val msg = "Session already exists, skipped"
val progress = LegacyMigrationSessionProgress(info, idx, selections.sessions.size, MigrationSessionPhase.skipped)
sink.session(progress)
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.success, "Session already exists, skipped"))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.success, msg))
sink.item(LegacyMigrationItemProgress(sel.id, MigrationItemProgressStatus.success, msg))
continue
}
if (conversationRaw == null) {
sink.session(LegacyMigrationSessionProgress(info, idx, selections.sessions.size, MigrationSessionPhase.error, "Conversation file not found"))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.error, "Conversation file not found"))
val msg = "Conversation file not found"
sink.session(LegacyMigrationSessionProgress(info, idx, selections.sessions.size, MigrationSessionPhase.error, msg))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.error, msg))
sink.item(LegacyMigrationItemProgress(sel.id, MigrationItemProgressStatus.error, msg))
continue
}
@@ -238,6 +242,7 @@ class LegacyMigrationEngine(
val msg = e.message ?: "Parse error"
sink.session(LegacyMigrationSessionProgress(info, idx, selections.sessions.size, MigrationSessionPhase.error, msg))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.error, msg))
sink.item(LegacyMigrationItemProgress(sel.id, MigrationItemProgressStatus.error, msg))
null
} ?: continue
@@ -247,6 +252,7 @@ class LegacyMigrationEngine(
val msg = e.message ?: "Project import failed"
sink.session(LegacyMigrationSessionProgress(info, idx, selections.sessions.size, MigrationSessionPhase.error, msg))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.error, msg))
sink.item(LegacyMigrationItemProgress(sel.id, MigrationItemProgressStatus.error, msg))
null
} ?: continue
@@ -267,12 +273,15 @@ class LegacyMigrationEngine(
val msg = e.message ?: "Session import failed"
sink.session(LegacyMigrationSessionProgress(info, idx, selections.sessions.size, MigrationSessionPhase.error, msg))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.error, msg))
sink.item(LegacyMigrationItemProgress(sel.id, MigrationItemProgressStatus.error, msg))
null
} ?: continue
if (importResult.skipped) {
val msg = "Session already exists, skipped"
sink.session(LegacyMigrationSessionProgress(info, idx, selections.sessions.size, MigrationSessionPhase.skipped))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.success, "Session already exists, skipped"))
results.add(LegacyMigrationResultItem(sel.id, MigrationItemCategory.session, MigrationItemStatus.success, msg))
sink.item(LegacyMigrationItemProgress(sel.id, MigrationItemProgressStatus.success, msg))
continue
}
@@ -339,11 +348,11 @@ class LegacyMigrationEngine(
}
}
// Autocomplete — report skipped/warning (no JetBrains equivalent known yet)
// Autocomplete settings are persisted by the JetBrains frontend before backend migration starts.
if (selections.settings.autocomplete && settings.autocomplete != null) {
sink.item(LegacyMigrationItemProgress("Autocomplete settings", MigrationItemProgressStatus.migrating))
results.add(LegacyMigrationResultItem("Autocomplete settings", MigrationItemCategory.settings, MigrationItemStatus.warning, "Autocomplete settings migration is not yet available for JetBrains"))
sink.item(LegacyMigrationItemProgress("Autocomplete settings", MigrationItemProgressStatus.warning, "Autocomplete settings migration is not yet available for JetBrains"))
results.add(LegacyMigrationResultItem("Autocomplete settings", MigrationItemCategory.settings, MigrationItemStatus.success))
sink.item(LegacyMigrationItemProgress("Autocomplete settings", MigrationItemProgressStatus.success))
}
return LegacyMigrationReport(results)
@@ -155,6 +155,7 @@ data class LegacyMigrationSelections(
val sessions: List<MigrationSessionSelection>,
val defaultModel: Boolean,
val settings: MigrationSettingsSelections,
val keepLegacySettingsFile: Boolean = true,
)
// ---------------------------------------------------------------------------
@@ -219,6 +220,7 @@ data class LegacyCleanupTargets(
val customModes: Boolean = false,
val globalState: Boolean = false,
val taskHistory: Boolean = false,
val legacySettingsFile: Boolean = false,
)
data class LegacyCleanupReport(
@@ -113,7 +113,7 @@ class KiloMigrationRpcApiImpl : KiloMigrationRpcApi {
}
override suspend fun cleanup(targets: LegacyCleanupTargetsDto): LegacyCleanupReportDto {
LOG.info("Migration RPC cleanup: providerProfiles=${targets.providerProfiles} mcp=${targets.mcpSettings} modes=${targets.customModes} state=${targets.globalState} history=${targets.taskHistory}")
LOG.info("Migration RPC cleanup: providerProfiles=${targets.providerProfiles} mcp=${targets.mcpSettings} modes=${targets.customModes} state=${targets.globalState} history=${targets.taskHistory} file=${targets.legacySettingsFile}")
val mgr = manager()
val store = storeService.store()
val report = withContext(Dispatchers.IO) { mgr.cleanup(store, MigrationRpcMapper.fromDto(targets)) }
@@ -122,5 +122,5 @@ class KiloMigrationRpcApiImpl : KiloMigrationRpcApi {
}
private fun selectionSummary(selections: LegacyMigrationSelectionsDto): String =
"providers=${selections.providers.size} mcp=${selections.mcpServers.size} modes=${selections.customModes.size} sessions=${selections.sessions.size} model=${selections.defaultModel} settings=true"
"providers=${selections.providers.size} mcp=${selections.mcpServers.size} modes=${selections.customModes.size} sessions=${selections.sessions.size} model=${selections.defaultModel} settings=true keepFile=${selections.keepLegacySettingsFile}"
}
@@ -135,6 +135,7 @@ internal object MigrationRpcMapper {
sessions = dto.sessions.map(::fromDto),
defaultModel = dto.defaultModel,
settings = fromDto(dto.settings),
keepLegacySettingsFile = dto.keepLegacySettingsFile,
)
private fun fromDto(dto: MigrationSessionSelectionDto): MigrationSessionSelection =
@@ -223,6 +224,7 @@ internal object MigrationRpcMapper {
customModes = dto.customModes,
globalState = dto.globalState,
taskHistory = dto.taskHistory,
legacySettingsFile = dto.legacySettingsFile,
)
fun toDto(r: LegacyCleanupReport): LegacyCleanupReportDto =
@@ -1,9 +1,11 @@
package ai.kilocode.backend.migration
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
@@ -142,10 +144,13 @@ class LegacyMigrationOrchestrationTest {
val sessionId = ai.kilocode.backend.migration.session.LegacySessionIds.createSessionId("t1")
backend.existingSessionIds = setOf(sessionId)
val sel = noSelections().copy(sessions = listOf(MigrationSessionSelection("t1", force = false)))
val report = eng.migrate(sel)
val items = mutableListOf<LegacyMigrationItemProgress>()
val report = eng.migrate(sel, itemSink(items))
val item = report.items.find { it.category == MigrationItemCategory.session }
assertNotNull(item)
assertTrue(item!!.message?.contains("skipped", ignoreCase = true) == true)
assertEquals(listOf(MigrationItemProgressStatus.migrating, MigrationItemProgressStatus.success), items.map { it.status })
assertTrue(items[1].message?.contains("skipped", ignoreCase = true) == true)
assertEquals(0, backend.projectCalls.size)
}
@@ -173,9 +178,42 @@ class LegacyMigrationOrchestrationTest {
}
backend.sessionImportSkipped = true
val sel = noSelections().copy(sessions = listOf(MigrationSessionSelection("t1")))
eng.migrate(sel)
val items = mutableListOf<LegacyMigrationItemProgress>()
eng.migrate(sel, itemSink(items))
assertEquals(0, backend.messageCalls.size)
assertEquals(0, backend.partCalls.size)
assertEquals(listOf(MigrationItemProgressStatus.migrating, MigrationItemProgressStatus.success), items.map { it.status })
assertTrue(items[1].message?.contains("skipped", ignoreCase = true) == true)
}
@Test
fun `migrate - missing session conversation emits terminal error progress`() {
val (eng, _, _) = setup {
taskHistory = """[{"id":"t1","task":"Test"}]"""
}
val sel = noSelections().copy(sessions = listOf(MigrationSessionSelection("t1")))
val items = mutableListOf<LegacyMigrationItemProgress>()
eng.migrate(sel, itemSink(items))
assertEquals(listOf(MigrationItemProgressStatus.migrating, MigrationItemProgressStatus.error), items.map { it.status })
assertEquals("Conversation file not found", items[1].message)
}
@Test
fun `migrate - autocomplete settings report success`() {
val (eng, _, _) = setup {
globalState["ghostServiceSettings"] = kotlinx.serialization.json.JsonObject(
mapOf(
"enableAutoTrigger" to JsonPrimitive(true),
"enableSmartInlineTaskKeybinding" to JsonPrimitive(true),
"enableChatAutocomplete" to JsonPrimitive(true),
)
)
}
val sel = noSelections().copy(settings = noSelections().settings.copy(autocomplete = true))
val report = eng.migrate(sel)
val item = report.items.single { it.item == "Autocomplete settings" }
assertEquals(MigrationItemStatus.success, item.status)
assertEquals(null, item.message)
}
// -----------------------------------------------------------------------
@@ -209,9 +247,40 @@ class LegacyMigrationOrchestrationTest {
assertTrue(report.hasErrors)
}
// -----------------------------------------------------------------------
// Cleanup
// -----------------------------------------------------------------------
@Test
fun `cleanup - legacy settings file target deletes file`() {
val (eng, fixture, _) = setup {
providerProfiles = """{"currentApiConfigName":"p","apiConfigs":{}}"""
}
val report = eng.cleanup(LegacyCleanupTargets(legacySettingsFile = true))
assertEquals(listOf("legacySettingsFile"), report.cleaned)
assertEquals(emptyList<String>(), report.errors)
assertFalse(fixture.exists())
}
@Test
fun `cleanup - data target preserves legacy settings file`() {
val (eng, fixture, _) = setup {
providerProfiles = """{"currentApiConfigName":"p","apiConfigs":{}}"""
}
val report = eng.cleanup(LegacyCleanupTargets(providerProfiles = true))
assertEquals(listOf("providerProfiles"), report.cleaned)
assertEquals(emptyList<String>(), report.errors)
assertTrue(fixture.exists())
}
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
private fun assertNotNull(actual: Any?) = kotlin.test.assertNotNull(actual)
private fun itemSink(items: MutableList<LegacyMigrationItemProgress>) = object : LegacyMigrationSink {
override fun item(progress: LegacyMigrationItemProgress) { items.add(progress) }
override fun session(progress: LegacyMigrationSessionProgress) = Unit
}
}
@@ -28,6 +28,8 @@ internal class LegacySettingsFileFixture {
migrationStatus = LegacySettingsFileMigrationStore(file).status()
}
fun exists() = file.exists()
private fun flush() {
val root = mutableMapOf<String, JsonElement>()
migrationStatus?.let { root["migrationStatus"] = JsonPrimitive(it.name) }
@@ -0,0 +1,40 @@
package ai.kilocode.client.autocomplete
import ai.kilocode.rpc.dto.LegacyAutocompleteSettingsDto
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service
@Service(Service.Level.APP)
@State(
name = "KiloAutocompleteSettings",
storages = [Storage("kiloAutocompleteSettings.xml")],
)
class KiloAutocompleteSettingsService : PersistentStateComponent<KiloAutocompleteSettingsService.State> {
data class State(
var enableAutoTrigger: Boolean? = null,
var enableSmartInlineTaskKeybinding: Boolean? = null,
var enableChatAutocomplete: Boolean? = null,
)
private var state = State()
override fun getState(): State = state
override fun loadState(state: State) {
this.state = state
}
fun applyLegacy(settings: LegacyAutocompleteSettingsDto) {
settings.enableAutoTrigger?.let { state.enableAutoTrigger = it }
settings.enableSmartInlineTaskKeybinding?.let { state.enableSmartInlineTaskKeybinding = it }
settings.enableChatAutocomplete?.let { state.enableChatAutocomplete = it }
}
companion object {
fun getInstance(): KiloAutocompleteSettingsService = service()
}
}
@@ -3,10 +3,13 @@
package ai.kilocode.client.migration
import ai.kilocode.client.app.KiloAppService
import ai.kilocode.client.autocomplete.KiloAutocompleteSettingsService
import ai.kilocode.log.KiloLog
import ai.kilocode.rpc.KiloMigrationRpcApi
import ai.kilocode.rpc.dto.KiloAppStateDto
import ai.kilocode.rpc.dto.KiloAppStatusDto
import ai.kilocode.rpc.dto.LegacyAutocompleteSettingsDto
import ai.kilocode.rpc.dto.LegacyCleanupTargetsDto
import ai.kilocode.rpc.dto.LegacyMigrationEventDto
import ai.kilocode.rpc.dto.LegacyMigrationResultItemDto
import ai.kilocode.rpc.dto.LegacyMigrationStatusDto
@@ -49,12 +52,19 @@ class KiloMigrationService internal constructor(
private val cs: CoroutineScope,
private val rpc: KiloMigrationRpcApi?,
appState: StateFlow<KiloAppStateDto>?,
private val autocomplete: ((LegacyAutocompleteSettingsDto) -> Unit)?,
) : MigrationUiController {
/** Platform constructor — resolves RPC lazily. */
constructor(cs: CoroutineScope) : this(cs, null, service<KiloAppService>().state)
constructor(cs: CoroutineScope) : this(cs, null, service<KiloAppService>().state, null)
internal constructor(cs: CoroutineScope, rpc: KiloMigrationRpcApi?) : this(cs, rpc, null)
internal constructor(cs: CoroutineScope, rpc: KiloMigrationRpcApi?) : this(cs, rpc, null, null)
internal constructor(
cs: CoroutineScope,
rpc: KiloMigrationRpcApi?,
appState: StateFlow<KiloAppStateDto>?,
) : this(cs, rpc, appState, null)
companion object {
private val LOG = KiloLog.create(KiloMigrationService::class.java)
@@ -67,6 +77,7 @@ class KiloMigrationService internal constructor(
private val migrating = AtomicBoolean(false)
private val migrateJob = AtomicReference<Job?>(null)
private val lastSelections = AtomicReference<MigrationUiSelections?>(null)
init {
if (appState != null) {
@@ -95,9 +106,11 @@ class KiloMigrationService internal constructor(
return
}
LOG.info("Migration wizard: user started migration ${selectionSummary(selections)}")
lastSelections.set(selections)
val dto = MigrationSelectionBuilder.toDto(selections)
val initialProgress = buildInitialProgress(selections, current.detection)
applyAutocomplete(selections, current.detection)
_state.value = current.copy(
phase = MigrationUiPhase.migrating,
@@ -194,10 +207,14 @@ class KiloMigrationService internal constructor(
}
val hasErrors = current.results.any { it.status == MigrationItemStatusDto.error }
val status = if (hasErrors) LegacyMigrationStatusDto.completed_with_errors else LegacyMigrationStatusDto.completed
val selections = lastSelections.get()
LOG.info("Migration wizard: user finished migration status=$status results=${current.results.size} errors=${current.results.count { it.status == MigrationItemStatusDto.error }}")
cs.launch {
try {
call { finalize(status) }
if (selections?.keepLegacySettingsFile == false) {
call { cleanup(cleanupTargets()) }
}
} catch (e: Exception) {
LOG.warn("migration finalize failed", e)
}
@@ -304,8 +321,27 @@ class KiloMigrationService internal constructor(
)
}
private fun applyAutocomplete(
selections: MigrationUiSelections,
detection: ai.kilocode.rpc.dto.LegacyMigrationDetectionDto,
) {
if (!selections.settings.autocomplete) return
val settings = detection.settings?.autocomplete ?: return
val apply = autocomplete ?: { KiloAutocompleteSettingsService.getInstance().applyLegacy(it) }
apply(settings)
}
private fun selectionSummary(selections: MigrationUiSelections): String =
"providers=${selections.providers.size}:${selections.providers.joinToString(",")} mcp=${selections.mcpServers.size}:${selections.mcpServers.joinToString(",")} modes=${selections.customModes.size}:${selections.customModes.joinToString(",")} sessions=${selections.sessions.size} model=${selections.defaultModel} settings=${settingsSummary(selections.settings)}"
"providers=${selections.providers.size}:${selections.providers.joinToString(",")} mcp=${selections.mcpServers.size}:${selections.mcpServers.joinToString(",")} modes=${selections.customModes.size}:${selections.customModes.joinToString(",")} sessions=${selections.sessions.size} model=${selections.defaultModel} settings=${settingsSummary(selections.settings)} keepFile=${selections.keepLegacySettingsFile}"
private fun cleanupTargets() = LegacyCleanupTargetsDto(
providerProfiles = true,
mcpSettings = true,
customModes = true,
globalState = true,
taskHistory = true,
legacySettingsFile = true,
)
private fun settingsSummary(settings: MigrationSettingsUiSelections): String =
"commandRules=${settings.autoApproval.commandRules},read=${settings.autoApproval.readPermission},write=${settings.autoApproval.writePermission},execute=${settings.autoApproval.executePermission},mcp=${settings.autoApproval.mcpPermission},task=${settings.autoApproval.taskPermission},language=${settings.language},autocomplete=${settings.autocomplete}"
@@ -56,6 +56,7 @@ object MigrationSelectionBuilder {
sessions = sessions,
defaultModel = defaultModel,
settings = settingsSel,
keepLegacySettingsFile = true,
)
}
@@ -83,6 +84,7 @@ object MigrationSelectionBuilder {
language = selections.settings.language,
autocomplete = selections.settings.autocomplete,
),
keepLegacySettingsFile = selections.keepLegacySettingsFile,
)
/**
@@ -106,5 +108,6 @@ object MigrationSelectionBuilder {
language = false,
autocomplete = false,
),
keepLegacySettingsFile = true,
)
}
@@ -34,6 +34,7 @@ data class MigrationUiSelections(
val sessions: List<String> = emptyList(),
val defaultModel: Boolean = false,
val settings: MigrationSettingsUiSelections = MigrationSettingsUiSelections(),
val keepLegacySettingsFile: Boolean = true,
)
// ---------------------------------------------------------------------------
@@ -8,6 +8,7 @@
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="ai.kilocode.client.migration.KiloMigrationService"/>
<applicationService serviceImplementation="ai.kilocode.client.autocomplete.KiloAutocompleteSettingsService"/>
<toolWindow id="Kilo Code"
anchor="left"
@@ -3,10 +3,12 @@ package ai.kilocode.client.migration
import ai.kilocode.client.testing.FakeMigrationRpcApi
import ai.kilocode.rpc.dto.KiloAppStateDto
import ai.kilocode.rpc.dto.KiloAppStatusDto
import ai.kilocode.rpc.dto.LegacyAutocompleteSettingsDto
import ai.kilocode.rpc.dto.LegacyMigrationDetectionDto
import ai.kilocode.rpc.dto.LegacyMigrationEventDto
import ai.kilocode.rpc.dto.LegacyMigrationResultItemDto
import ai.kilocode.rpc.dto.LegacyMigrationStatusDto
import ai.kilocode.rpc.dto.LegacySettingsDto
import ai.kilocode.rpc.dto.MigrationItemCategoryDto
import ai.kilocode.rpc.dto.MigrationItemProgressStatusDto
import ai.kilocode.rpc.dto.MigrationItemStatusDto
@@ -27,13 +29,15 @@ class KiloMigrationServiceTest : BasePlatformTestCase() {
private lateinit var rpc: FakeMigrationRpcApi
private lateinit var service: KiloMigrationService
private lateinit var app: MutableStateFlow<KiloAppStateDto>
private val autocomplete = mutableListOf<LegacyAutocompleteSettingsDto>()
override fun setUp() {
super.setUp()
scope = CoroutineScope(SupervisorJob())
rpc = FakeMigrationRpcApi()
app = MutableStateFlow(KiloAppStateDto(KiloAppStatusDto.DISCONNECTED))
service = KiloMigrationService(scope, rpc, app)
autocomplete.clear()
service = KiloMigrationService(scope, rpc, app) { autocomplete.add(it) }
}
override fun tearDown() {
@@ -95,9 +99,30 @@ class KiloMigrationServiceTest : BasePlatformTestCase() {
settle()
assertEquals(1, rpc.finalizeCalls.size)
assertEquals(LegacyMigrationStatusDto.completed, rpc.finalizeCalls[0])
assertEquals(0, rpc.cleanupCalls.size)
assertEquals(MigrationUiState.Hidden, service.state.value)
}
fun `test finish after unchecked keep file cleans up legacy settings file`() {
app.value = KiloAppStateDto(KiloAppStatusDto.MIGRATION_REQUIRED, migration = sampleDetection())
settle()
service.start(MigrationUiSelections(providers = listOf("profile1"), keepLegacySettingsFile = false))
settle()
service.finish()
settle()
assertEquals(1, rpc.finalizeCalls.size)
assertEquals(1, rpc.cleanupCalls.size)
val targets = rpc.cleanupCalls[0]
assertTrue(targets.providerProfiles)
assertTrue(targets.mcpSettings)
assertTrue(targets.customModes)
assertTrue(targets.globalState)
assertTrue(targets.taskHistory)
assertTrue(targets.legacySettingsFile)
}
fun `test start emits migrating state and initial pending progress`() = runBlocking {
app.value = KiloAppStateDto(KiloAppStatusDto.MIGRATION_REQUIRED, migration = sampleDetection())
delay(100)
@@ -172,6 +197,40 @@ class KiloMigrationServiceTest : BasePlatformTestCase() {
assertEquals(2, dto.sessions.size)
assertTrue(dto.sessions.all { it.force })
assertEquals(listOf("ses_1", "ses_2"), dto.sessions.map { it.id })
assertTrue(dto.keepLegacySettingsFile)
}
fun `test start persists selected legacy autocomplete settings`() {
val detection = sampleDetection().copy(
settings = LegacySettingsDto(
autoApprovalEnabled = null,
allowedCommands = null,
deniedCommands = null,
alwaysAllowReadOnly = null,
alwaysAllowReadOnlyOutsideWorkspace = null,
alwaysAllowWrite = null,
alwaysAllowExecute = null,
alwaysAllowMcp = null,
alwaysAllowModeSwitch = null,
alwaysAllowSubtasks = null,
language = null,
autocomplete = LegacyAutocompleteSettingsDto(
enableAutoTrigger = true,
enableSmartInlineTaskKeybinding = false,
enableChatAutocomplete = true,
),
)
)
app.value = KiloAppStateDto(KiloAppStatusDto.MIGRATION_REQUIRED, migration = detection)
settle()
service.start(MigrationUiSelections(settings = MigrationSettingsUiSelections(autocomplete = true)))
settle()
assertEquals(1, autocomplete.size)
assertEquals(true, autocomplete[0].enableAutoTrigger)
assertEquals(false, autocomplete[0].enableSmartInlineTaskKeybinding)
assertEquals(true, autocomplete[0].enableChatAutocomplete)
}
private fun sampleDetection() = LegacyMigrationDetectionDto(
@@ -125,6 +125,7 @@ data class LegacyMigrationSelectionsDto(
val sessions: List<MigrationSessionSelectionDto>,
val defaultModel: Boolean,
val settings: MigrationSettingsSelectionsDto,
val keepLegacySettingsFile: Boolean = true,
)
// ---------------------------------------------------------------------------
@@ -201,6 +202,7 @@ data class LegacyCleanupTargetsDto(
val customModes: Boolean = false,
val globalState: Boolean = false,
val taskHistory: Boolean = false,
val legacySettingsFile: Boolean = false,
)
@Serializable