mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
feat(jetbrains): log CLI download/bundled mode and mark bundled Core version
Backend now logs a clear one-line mode statement when resolving Core (BUNDLED vs DOWNLOAD), and logs distinctly when a cached/extracted binary is reused so no download or extraction happens. Adds a cliBundled() RPC so the frontend Core-info popup can prefix the version with "Bundled" when Core wasn't downloaded.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": minor
|
||||
---
|
||||
|
||||
Log whether the JetBrains plugin downloads Core or uses the bundled/cached version, and mark the Core version shown in the popup as "Bundled" when it wasn't downloaded.
|
||||
+8
-4
@@ -126,14 +126,18 @@ class KiloBackendCliManager(
|
||||
private suspend fun resolveCli(onProgress: (CliDownload) -> Unit): File {
|
||||
val force = forceExtract
|
||||
forceExtract = false
|
||||
val version = KiloProps.cliVersion()
|
||||
val platform = KiloCliPlatform.current()
|
||||
if (KiloRepoCli.available()) {
|
||||
if (force) log.info("Force re-extracting bundled CLI ${KiloProps.cliVersion()}")
|
||||
if (force) log.info("Force re-extracting bundled CLI $version")
|
||||
log.info("Kilo CLI mode: BUNDLED — using CLI $version ($platform) shipped in the plugin; no download needed")
|
||||
val cli = KiloRepoCli.extract(force)
|
||||
onProgress(CliDownload(100, KiloProps.cliVersion(), KiloCliPlatform.current()))
|
||||
onProgress(CliDownload(100, version, platform))
|
||||
return cli
|
||||
}
|
||||
if (force) log.info("Force re-downloading CLI ${KiloProps.cliVersion()}")
|
||||
return KiloCliDownloader(log = log).resolve(KiloProps.cliVersion(), force, onProgress)
|
||||
if (force) log.info("Force re-downloading CLI $version")
|
||||
log.info("Kilo CLI mode: DOWNLOAD — resolving CLI $version ($platform) from the GitHub release")
|
||||
return KiloCliDownloader(log = log).resolve(version, force, onProgress)
|
||||
}
|
||||
|
||||
// Must be called from a background thread — devStorageEnv() performs blocking I/O (mkdirs).
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ class KiloCliDownloader(
|
||||
"completeExists=${done.isFile} digestValid=$valid exe=${exe.absolutePath} complete=${done.absolutePath}"
|
||||
)
|
||||
if (!exe.isFile || !valid) return null
|
||||
log.info("Using cached Kilo CLI $version for $platform at ${exe.absolutePath}")
|
||||
log.info("Kilo CLI $version ($platform) already cached at ${exe.absolutePath}; skipping download and extraction")
|
||||
if (!SystemInfo.isWindows) exe.setExecutable(true)
|
||||
prune(version)
|
||||
return exe
|
||||
|
||||
@@ -36,10 +36,12 @@ object KiloRepoCli {
|
||||
val exe = File(root, "$platform/bin/${KiloCliPlatform.exe()}")
|
||||
val done = File(root, ".complete")
|
||||
if (!force && done.isFile && exe.isFile) {
|
||||
log.info("Bundled Kilo CLI ${KiloProps.cliVersion()} ($platform) already extracted at ${exe.absolutePath}; skipping extraction")
|
||||
if (!SystemInfo.isWindows) exe.setExecutable(true)
|
||||
if (cleanup) prune(root)
|
||||
return@withContext exe
|
||||
}
|
||||
log.info("Extracting bundled Kilo CLI ${KiloProps.cliVersion()} ($platform) into ${root.absolutePath}")
|
||||
|
||||
if (root.exists() && !root.deleteRecursively()) {
|
||||
throw IllegalStateException("Failed to delete local repo CLI under ${root.absolutePath}")
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ import ai.kilocode.backend.app.LoadProgress
|
||||
import ai.kilocode.backend.app.ProfileResult
|
||||
import ai.kilocode.backend.cli.KiloCliPlatform
|
||||
import ai.kilocode.backend.cli.KiloProps
|
||||
import ai.kilocode.backend.cli.KiloRepoCli
|
||||
import ai.kilocode.jetbrains.api.model.KiloProfile200Response
|
||||
import ai.kilocode.rpc.dto.ConfigPatchDto
|
||||
import ai.kilocode.rpc.KiloAppRpcApi
|
||||
@@ -57,6 +58,8 @@ class KiloAppRpcApiImpl : KiloAppRpcApi {
|
||||
|
||||
override suspend fun cliPlatform(): String = KiloCliPlatform.current()
|
||||
|
||||
override suspend fun cliBundled(): Boolean = KiloRepoCli.available()
|
||||
|
||||
override suspend fun retry() = app.retry()
|
||||
|
||||
override suspend fun restart() = app.restart()
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ class KiloCliDownloaderTest {
|
||||
assertEquals(cli.absolutePath, cached.absolutePath)
|
||||
assertEquals(1, server.requestCount)
|
||||
assertTrue(cachedProgress.isEmpty())
|
||||
assertContains(log.messages, "INFO: Using cached Kilo CLI 1.2.3 for ${KiloCliPlatform.current()} at ${cli.absolutePath}")
|
||||
assertContains(log.messages, "INFO: Kilo CLI 1.2.3 (${KiloCliPlatform.current()}) already cached at ${cli.absolutePath}; skipping download and extraction")
|
||||
|
||||
File(cli.parentFile.parentFile, ".complete").writeText("ok\n")
|
||||
server.enqueue(MockResponse().setResponseCode(200).setBody(Buffer().write(bytes)))
|
||||
|
||||
+3
-1
@@ -15,8 +15,10 @@ class CoreInfoAction : AnAction(), DumbAware {
|
||||
val app = service<KiloAppService>()
|
||||
val info = app.core
|
||||
if (info == null) app.fetchCoreInfoAsync()
|
||||
app.fetchBundledAsync()
|
||||
val key = if (app.bundled == true) "action.Kilo.CoreInfo.bundled" else "action.Kilo.CoreInfo.text"
|
||||
e.presentation.text = info?.let {
|
||||
KiloBundle.message("action.Kilo.CoreInfo.text", it.version, it.platform)
|
||||
KiloBundle.message(key, it.version, it.platform)
|
||||
} ?: KiloBundle.message("action.Kilo.CoreInfo.loading")
|
||||
e.presentation.description = KiloBundle.message("action.Kilo.CoreInfo.description")
|
||||
e.presentation.isEnabled = false
|
||||
|
||||
+33
@@ -58,6 +58,18 @@ class KiloAppService internal constructor(
|
||||
|
||||
val version: String? get() = info?.version
|
||||
|
||||
/**
|
||||
* Whether the running Core is bundled in the plugin (true) or downloaded (false).
|
||||
* Null until fetched. This is a static property of the plugin build, so it is
|
||||
* fetched once via RPC independently of the download-progress state.
|
||||
*/
|
||||
@Volatile
|
||||
private var bundledFlag: Boolean? = null
|
||||
private val bundledLock = Any()
|
||||
private var bundledJob: Job? = null
|
||||
|
||||
val bundled: Boolean? get() = bundledFlag
|
||||
|
||||
/**
|
||||
* App-lifetime scope for fire-and-forget work that must outlive transient UIs such as the
|
||||
* settings dialog (whose own scope is cancelled the moment it closes on OK).
|
||||
@@ -152,6 +164,7 @@ class KiloAppService internal constructor(
|
||||
platform = call { cliPlatform() },
|
||||
)
|
||||
info = next
|
||||
bundledFlag = call { cliBundled() }
|
||||
next
|
||||
} catch (e: Exception) {
|
||||
LOG.warn("core info failed", e)
|
||||
@@ -200,6 +213,26 @@ class KiloAppService internal constructor(
|
||||
fetchCoreInfoAsync { done(it?.version) }
|
||||
}
|
||||
|
||||
/** Fetch whether the running Core is bundled and cache it. Deduped and fetched once. */
|
||||
fun fetchBundledAsync() {
|
||||
if (bundledFlag != null) return
|
||||
synchronized(bundledLock) {
|
||||
if (bundledFlag != null || bundledJob != null) return
|
||||
bundledJob = cs.launch {
|
||||
val value = try {
|
||||
call { cliBundled() }
|
||||
} catch (e: Exception) {
|
||||
LOG.warn("core bundled check failed", e)
|
||||
null
|
||||
}
|
||||
synchronized(bundledLock) {
|
||||
if (value != null) bundledFlag = value
|
||||
bundledJob = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshModelFavoritesAsync() {
|
||||
cs.launch {
|
||||
try {
|
||||
|
||||
@@ -731,6 +731,7 @@ action.Kilo.Reinstall.text=Reinstall
|
||||
action.Kilo.Reinstall.cli.text=Reinstall Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.text=Core v{0} • Architecture: {1}
|
||||
action.Kilo.CoreInfo.bundled=Bundled Core v{0} • Architecture: {1}
|
||||
action.Kilo.CoreInfo.loading=Core details loading...
|
||||
action.Kilo.CoreInfo.description=Kilo Core version and architecture
|
||||
action.Kilo.Session.Open.text=Open
|
||||
|
||||
+15
@@ -126,6 +126,21 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
|
||||
assertEquals("Core v1.2.3 • Architecture: darwin-arm64", event.presentation.text)
|
||||
}
|
||||
|
||||
fun `test core info action marks bundled core`() {
|
||||
appRpc.cliVersion = "1.2.3"
|
||||
appRpc.cliPlatform = "darwin-arm64"
|
||||
appRpc.cliBundled = true
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
runBlocking { app().coreInfo() }
|
||||
}.get()
|
||||
val action = CoreInfoAction()
|
||||
val event = event(action)
|
||||
|
||||
update(action, event)
|
||||
|
||||
assertEquals("Bundled Core v1.2.3 • Architecture: darwin-arm64", event.presentation.text)
|
||||
}
|
||||
|
||||
fun `test local config action says open when target exists`() {
|
||||
rpc.localConfigPath = "/test/.kilo/kilo.jsonc"
|
||||
rpc.localConfigDisplayPath = "~/.kilo/kilo.jsonc"
|
||||
|
||||
+6
@@ -37,6 +37,7 @@ class FakeAppRpcApi : KiloAppRpcApi {
|
||||
var health = HealthDto(healthy = true, version = "1.0.0")
|
||||
var cliVersion = "1.0.0"
|
||||
var cliPlatform = "darwin-arm64"
|
||||
var cliBundled = false
|
||||
var cliInfoGate: CompletableDeferred<Unit>? = null
|
||||
var cliInfoError: Exception? = null
|
||||
var cliVersionCalls = 0
|
||||
@@ -94,6 +95,11 @@ class FakeAppRpcApi : KiloAppRpcApi {
|
||||
return cliPlatform
|
||||
}
|
||||
|
||||
override suspend fun cliBundled(): Boolean {
|
||||
assertNotEdt("cliBundled")
|
||||
return cliBundled
|
||||
}
|
||||
|
||||
override suspend fun retry() {
|
||||
assertNotEdt("retry")
|
||||
retries += 1
|
||||
|
||||
@@ -45,6 +45,9 @@ interface KiloAppRpcApi : RemoteApi<Unit> {
|
||||
/** Core platform downloaded by the backend process. */
|
||||
suspend fun cliPlatform(): String
|
||||
|
||||
/** Whether the running Core is bundled in the plugin (true) or downloaded (false). */
|
||||
suspend fun cliBundled(): Boolean
|
||||
|
||||
/** Retry app connection or loading after a failure. */
|
||||
suspend fun retry()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user