mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge pull request #12895 from Kilo-Org/rapid-empress
feat(jetbrains): visible CLI download/bundled mode + skill to pin/unpin/update
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.
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
name: jetbrains-cli-pin
|
||||
description: Use when pinning or unpinning the CLI version the Kilo JetBrains plugin uses, or fresh-regenerating the local repo CLI. Cleans all leftover CLI binaries and build artifacts in the current worktree so every operation starts from a fresh, artifact-free state.
|
||||
---
|
||||
|
||||
# JetBrains CLI Pin
|
||||
|
||||
Pin the Kilo JetBrains plugin to the latest released CLI, unpin it to use the local
|
||||
repo CLI, or fresh-regenerate the local CLI while unpinned. Every command first cleans
|
||||
all CLI/pin build artifacts and binaries in the current worktree so the result never
|
||||
carries state from a previous run.
|
||||
|
||||
Run all commands from the repository root of the worktree you want to affect. Paths are
|
||||
relative, so they resolve to the current worktree, not the main checkout.
|
||||
|
||||
## Two Controls
|
||||
|
||||
The plugin's CLI behavior is governed by two independent values:
|
||||
|
||||
| Control | Location | Meaning |
|
||||
|---|---|---|
|
||||
| Pin mode | `packages/kilo-jetbrains/gradle.properties` -> `kilo.cli.pinned` | `true` = download the released CLI at build/connect time. `false` = build and bundle the local repo CLI. |
|
||||
| Pinned version | `packages/kilo-jetbrains/package.json` -> `version` | Which GitHub CLI release is downloaded and generated from when `pinned=true`. |
|
||||
|
||||
"Pin to latest" means `kilo.cli.pinned=true` **and** `package.json` set to the latest
|
||||
stable CLI release. "Unpin" means `kilo.cli.pinned=false` with a freshly built local CLI
|
||||
bundled.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts <command> [--no-verify]
|
||||
```
|
||||
|
||||
| Command | Steps |
|
||||
|---|---|
|
||||
| `pin` | Clean -> set `kilo.cli.pinned=true` -> remove the repo-CLI Bun path hint -> bump `package.json` to latest release (via `set-pin.ts --latest`, which validates release assets) -> verify with a cold `gradlew clean typecheck`. |
|
||||
| `unpin` | Clean -> set `kilo.cli.pinned=false` -> write the repo-CLI Bun path hint -> `:backend:buildRepoCli` (fresh CLI) -> `:backend:stageRepoCli` -> assert staged `kilo-cli.zip` -> verify with `gradlew typecheck`. |
|
||||
| `regen` | Fast dev loop while unpinned: refresh the repo-CLI Bun path hint -> `rm -rf dist` -> `buildRepoCli` -> `stageRepoCli`. Refuses to run unless `kilo.cli.pinned=false`. |
|
||||
| `clean` | Run the shared artifact clean only. |
|
||||
|
||||
`--no-verify` skips the gradle verification build (rewrites + clean only). Use it when
|
||||
offline or without Java 21.
|
||||
|
||||
## Cleaned Artifacts
|
||||
|
||||
`clean()` runs `./gradlew clean` plus targeted deletes. All paths are gitignored, so
|
||||
tracked files are never touched. The clean removes the stale artifacts that otherwise
|
||||
leak across a pin/unpin flip:
|
||||
|
||||
| Artifact | Path |
|
||||
|---|---|
|
||||
| Repo CLI binaries | `packages/opencode/dist/` |
|
||||
| Staged CLI archive | `packages/kilo-jetbrains/backend/build/generated/kilo-cli-res/kilo-cli.zip` |
|
||||
| Generated props / checksums / OpenAPI client | `packages/kilo-jetbrains/backend/build/generated/` |
|
||||
| Compiled resources (bundled zip on classpath) | `packages/kilo-jetbrains/backend/build/resources/` |
|
||||
| CLI download cache | `packages/kilo-jetbrains/backend/build/cli-cache/` |
|
||||
|
||||
The staged `kilo-cli.zip` is the nastiest leak: once it lands in `backend/build/resources/main/`
|
||||
from an unpinned build, runtime prefers the bundled zip over downloading. A full clean is
|
||||
the only reliable reset.
|
||||
|
||||
## Bun Path Hint
|
||||
|
||||
In repo CLI mode, Gradle's `generateOpenApiSpec` task runs the local CLI source through
|
||||
`bun run --conditions=browser ./src/index.ts generate`. IDE-launched Gradle runs can have
|
||||
a stripped `PATH` where `bun` isn't resolvable. The `unpin`/`regen` commands write an ignored, worktree-local hint:
|
||||
|
||||
```text
|
||||
packages/kilo-jetbrains/.gradle/kilo-cli-pin.properties
|
||||
```
|
||||
|
||||
The file contains `bun.path=<absolute path>` and is consumed by `backend/build.gradle.kts`
|
||||
for repo CLI tasks. `pin` removes it because pinned mode should not depend on local Bun.
|
||||
|
||||
## Notes
|
||||
|
||||
- Verification builds pass `--no-configuration-cache` so the changed `kilo.cli.pinned`
|
||||
value is re-read instead of served from the on-disk Gradle configuration cache.
|
||||
- The `pin` verification is a cold build: it downloads the pinned CLI release via
|
||||
`generateOpenApiSpec` and needs network access plus Java 21. Use `--no-verify` offline.
|
||||
- `kilo.cli.pinned=false` is dev-only and not releasable. Production Gradle builds,
|
||||
`script/build-version.sh`, and the release scripts hard-fail on `false` -- run `pin`
|
||||
before releasing.
|
||||
|
||||
## Related
|
||||
|
||||
- Version-bump and release-gating logic lives in the `release-jetbrains` skill
|
||||
(`.kilo/skills/release-jetbrains/SKILL.md`); this skill reuses its `set-pin.ts` and
|
||||
`pin-common.ts` helpers.
|
||||
- Background on the build wiring: the "CLI Pinning, Unpinning, and Bumping" and "CLI
|
||||
Integration" sections of `packages/kilo-jetbrains/AGENTS.md`.
|
||||
@@ -0,0 +1,23 @@
|
||||
import { $ } from "bun"
|
||||
|
||||
// Single source of truth for every CLI/pin artifact that can leak across a mode
|
||||
// flip in the current worktree. Everything here is gitignored (dist, backend/build,
|
||||
// .gradle), so cleaning never touches tracked files.
|
||||
//
|
||||
// The build's conditional sourceSets/dependsOn wiring in backend/build.gradle.kts only
|
||||
// produces a correct package from a clean build/ directory. Incremental builds are what
|
||||
// let a stale kilo-cli.zip survive a pin<->unpin flip, and runtime prefers a bundled
|
||||
// zip over downloading -- so a full gradle clean is the reliable reset.
|
||||
export async function clean(jb = "packages/kilo-jetbrains") {
|
||||
// gradle clean wipes each project's build directory (including backend/build).
|
||||
await $`./gradlew clean --quiet`.cwd(jb).nothrow()
|
||||
|
||||
// Stale per-platform CLI binaries. build.ts only rm -rf dist for the platforms it
|
||||
// builds, so old platform dirs can survive; wipe the whole tree.
|
||||
await $`rm -rf packages/opencode/dist`
|
||||
|
||||
// Belt-and-suspenders in case gradle clean was skipped or ran offline.
|
||||
await $`rm -rf ${jb}/backend/build/generated`.nothrow()
|
||||
await $`rm -rf ${jb}/backend/build/resources`.nothrow()
|
||||
await $`rm -rf ${jb}/backend/build/cli-cache`.nothrow()
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import { parseArgs } from "util"
|
||||
import { clean } from "./clean"
|
||||
|
||||
const jb = "packages/kilo-jetbrains"
|
||||
const props = `${jb}/gradle.properties`
|
||||
const pkg = `${jb}/package.json`
|
||||
const zip = `${jb}/backend/build/generated/kilo-cli-res/kilo-cli.zip`
|
||||
const hint = `${jb}/.gradle/kilo-cli-pin.properties`
|
||||
|
||||
const arg = Bun.argv[2]
|
||||
const cmd = arg && !arg.startsWith("-") ? arg : undefined
|
||||
const { values } = parseArgs({
|
||||
args: cmd ? Bun.argv.slice(3) : Bun.argv.slice(2),
|
||||
options: {
|
||||
"no-verify": { type: "boolean", default: false },
|
||||
help: { type: "boolean", short: "h", default: false },
|
||||
},
|
||||
})
|
||||
|
||||
if (values.help || !cmd) {
|
||||
console.log(`
|
||||
Usage: bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts <command> [--no-verify]
|
||||
|
||||
Commands:
|
||||
pin Pin the JetBrains plugin to the latest released CLI. Cleans artifacts,
|
||||
sets kilo.cli.pinned=true, bumps package.json to the latest release,
|
||||
then verifies with a cold gradle build (needs network + Java 21).
|
||||
unpin Use the local repo CLI. Cleans artifacts, sets kilo.cli.pinned=false,
|
||||
fresh-builds and stages the repo CLI, then verifies with typecheck.
|
||||
regen Fast dev loop: rebuild + restage the local repo CLI (requires unpinned).
|
||||
clean Remove all CLI/pin build artifacts and binaries in the current worktree.
|
||||
|
||||
Options:
|
||||
--no-verify Skip the gradle verification build (rewrites + clean only).
|
||||
|
||||
Run from the repository root of the worktree you want to affect.
|
||||
`)
|
||||
process.exit(values.help ? 0 : 1)
|
||||
}
|
||||
|
||||
async function pinned() {
|
||||
const text = await Bun.file(props).text()
|
||||
const line = text.split(/\r?\n/).find((l) => l.startsWith("kilo.cli.pinned="))
|
||||
return (line?.split("=", 2)[1]?.trim().toLowerCase() ?? "true") === "true"
|
||||
}
|
||||
|
||||
async function setPinned(value: boolean) {
|
||||
const text = await Bun.file(props).text()
|
||||
if (!/^kilo\.cli\.pinned=.*$/m.test(text)) throw new Error(`kilo.cli.pinned not found in ${props}`)
|
||||
await Bun.write(props, text.replace(/^kilo\.cli\.pinned=.*$/m, `kilo.cli.pinned=${value}`))
|
||||
}
|
||||
|
||||
function bunPath() {
|
||||
return Bun.which("bun") ?? process.execPath
|
||||
}
|
||||
|
||||
async function writeBunHint() {
|
||||
const path = bunPath()
|
||||
await $`mkdir -p ${jb}/.gradle`
|
||||
await Bun.write(hint, `# Generated by jetbrains-cli-pin so IDE-launched Gradle can find Bun in repo CLI mode.\nbun.path=${path}\n`)
|
||||
console.log(`Wrote Bun path hint for repo CLI mode: ${path}`)
|
||||
}
|
||||
|
||||
async function removeBunHint() {
|
||||
await $`rm -f ${hint}`.nothrow()
|
||||
}
|
||||
|
||||
async function report() {
|
||||
const version = (await Bun.file(pkg).json()).version
|
||||
console.log(`\nState: kilo.cli.pinned=${await pinned()}, package.json version=${version}`)
|
||||
}
|
||||
|
||||
if (cmd === "pin") {
|
||||
await clean()
|
||||
await setPinned(true)
|
||||
await removeBunHint()
|
||||
// set-pin.ts bumps package.json to the latest release and refuses versions with
|
||||
// missing runtime assets, so we do not reimplement release/asset validation.
|
||||
await $`bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest`
|
||||
if (!values["no-verify"]) {
|
||||
// Cold pinned build downloads the pinned CLI release via generateOpenApiSpec.
|
||||
await $`./gradlew clean typecheck --no-configuration-cache`.cwd(jb)
|
||||
}
|
||||
await report()
|
||||
} else if (cmd === "unpin") {
|
||||
await clean()
|
||||
await setPinned(false)
|
||||
await writeBunHint()
|
||||
// build.ts does rm -rf dist internally, producing a fresh single-platform binary.
|
||||
await $`./gradlew :backend:buildRepoCli --no-configuration-cache`.cwd(jb)
|
||||
// stageRepoCli has upToDateWhen{false}; force it so the staged zip matches this build.
|
||||
await $`./gradlew :backend:stageRepoCli --no-configuration-cache`.cwd(jb)
|
||||
if (!(await Bun.file(zip).exists())) throw new Error(`Expected staged CLI at ${zip} after unpin`)
|
||||
if (!values["no-verify"]) {
|
||||
await $`./gradlew typecheck --no-configuration-cache`.cwd(jb)
|
||||
}
|
||||
await report()
|
||||
} else if (cmd === "regen") {
|
||||
if (await pinned()) throw new Error("regen requires the unpinned state; run 'unpin' first")
|
||||
await writeBunHint()
|
||||
await $`rm -rf packages/opencode/dist`
|
||||
await $`./gradlew :backend:buildRepoCli --no-configuration-cache`.cwd(jb)
|
||||
await $`./gradlew :backend:stageRepoCli --no-configuration-cache`.cwd(jb)
|
||||
if (!(await Bun.file(zip).exists())) throw new Error(`Expected staged CLI at ${zip} after regen`)
|
||||
await report()
|
||||
} else if (cmd === "clean") {
|
||||
await clean()
|
||||
await report()
|
||||
} else {
|
||||
throw new Error(`Unknown command '${cmd}'. Run with --help for usage.`)
|
||||
}
|
||||
@@ -170,6 +170,8 @@ For blocking I/O in coroutines, move the dispatcher switch inside the callee usi
|
||||
|
||||
The JetBrains plugin has two independent CLI controls. Use the commands below directly when asked to change either one; do not hand-edit versions by guesswork.
|
||||
|
||||
For a one-shot pin/unpin/regen that also cleans every leftover CLI binary and build artifact in the current worktree, use the `jetbrains-cli-pin` skill (`.kilo/skills/jetbrains-cli-pin/SKILL.md`): `bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts <pin|unpin|regen|clean>`.
|
||||
|
||||
**Pin mode** (`kilo.cli.pinned` in `packages/kilo-jetbrains/gradle.properties`) controls release CLI vs local repo CLI.
|
||||
|
||||
| Ask | Do |
|
||||
|
||||
@@ -26,6 +26,13 @@ val repoCli = pinned.map { !it }
|
||||
val bundled = providers.gradleProperty("kilo.cli.bundled").map { it.trim().toBoolean() }.orElse(false)
|
||||
val downloadsCli = repoCli.zip(bundled) { repo, bundle -> !repo && !bundle }
|
||||
val repoRootDir = rootProject.layout.projectDirectory.dir("../opencode")
|
||||
val local = rootProject.layout.projectDirectory.file(".gradle/kilo-cli-pin.properties")
|
||||
val bunPathProvider = providers.fileContents(local).asText.map { text ->
|
||||
text.lineSequence().firstNotNullOfOrNull { line ->
|
||||
val pair = line.split("=", limit = 2)
|
||||
if (pair.getOrNull(0)?.trim() == "bun.path") pair.getOrNull(1)?.trim()?.takeIf { it.isNotEmpty() } else null
|
||||
} ?: "bun"
|
||||
}.orElse("bun")
|
||||
|
||||
val pinnedCliVersion = providers.fileContents(rootProject.layout.projectDirectory.file("package.json")).asText.map { text ->
|
||||
Regex("\"version\"\\s*:\\s*\"([^\"]+)\"").find(text)?.groupValues?.get(1)
|
||||
@@ -64,12 +71,13 @@ val generateOpenApiSpec by tasks.registering(GenerateOpenApiSpecTask::class) {
|
||||
)
|
||||
cacheDir.set(layout.buildDirectory.dir("cli-cache"))
|
||||
spec.set(rawSpec)
|
||||
bunPath.set(bunPathProvider)
|
||||
}
|
||||
|
||||
val buildRepoCli by tasks.registering(Exec::class) {
|
||||
description = "Build the local repo CLI for the current platform"
|
||||
workingDir = repoRootDir.asFile
|
||||
commandLine("bun", "run", "script/build.ts", "--single", "--skip-install")
|
||||
commandLine(bunPathProvider.get(), "run", "script/build.ts", "--single", "--skip-install")
|
||||
}
|
||||
|
||||
fun platform(): String {
|
||||
|
||||
+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)))
|
||||
|
||||
@@ -49,6 +49,9 @@ abstract class GenerateOpenApiSpecTask : DefaultTask() {
|
||||
@get:Internal
|
||||
abstract val cacheDir: DirectoryProperty
|
||||
|
||||
@get:Internal
|
||||
abstract val bunPath: Property<String>
|
||||
|
||||
@get:OutputFile
|
||||
abstract val spec: RegularFileProperty
|
||||
|
||||
@@ -76,7 +79,7 @@ abstract class GenerateOpenApiSpecTask : DefaultTask() {
|
||||
val err = ByteArrayOutputStream()
|
||||
val result = exec.exec {
|
||||
workingDir = root
|
||||
commandLine("bun", "run", "--conditions=browser", "./src/index.ts", "generate")
|
||||
commandLine(bunPath.get(), "run", "--conditions=browser", "./src/index.ts", "generate")
|
||||
standardOutput = out
|
||||
errorOutput = err
|
||||
isIgnoreExitValue = true
|
||||
|
||||
+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
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=إنهاء عملية Core وإعادة تشغي
|
||||
action.Kilo.Reinstall.text=إعادة تثبيت Kilo
|
||||
action.Kilo.Reinstall.cli.text=إعادة تثبيت Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Core المُضمّن v{0} • البنية: {1}
|
||||
action.Kilo.Session.Open.text=فتح
|
||||
action.Kilo.Session.Open.description=فتح الجلسة المحددة
|
||||
action.Kilo.Session.Rename.text=إعادة تسمية
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Ugasite i ponovo pokrenite Core proces
|
||||
action.Kilo.Reinstall.text=Ponovo instalirajte Kilo
|
||||
action.Kilo.Reinstall.cli.text=Ponovo instalirajte Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Ugrađeni Core v{0} • Arhitektura: {1}
|
||||
action.Kilo.Session.Open.text=Otvori
|
||||
action.Kilo.Session.Open.description=Otvorite odabranu sesiju
|
||||
action.Kilo.Session.Rename.text=Preimenuj
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Afslut og genstart Core-processen
|
||||
action.Kilo.Reinstall.text=Geninstaller Kilo
|
||||
action.Kilo.Reinstall.cli.text=Geninstaller Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Medfølgende Core v{0} • Arkitektur: {1}
|
||||
action.Kilo.Session.Open.text=Åbn
|
||||
action.Kilo.Session.Open.description=Åbn den valgte session
|
||||
action.Kilo.Session.Rename.text=Omdøb
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Core-Prozess beenden und neu starten
|
||||
action.Kilo.Reinstall.text=Kilo neu installieren
|
||||
action.Kilo.Reinstall.cli.text=Core neu installieren
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Gebündelter Core v{0} • Architektur: {1}
|
||||
action.Kilo.Session.Open.text=Öffnen
|
||||
action.Kilo.Session.Open.description=Ausgewählte Sitzung öffnen
|
||||
action.Kilo.Session.Rename.text=Umbenennen
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Terminar y reiniciar el proceso Core
|
||||
action.Kilo.Reinstall.text=Reinstalar Kilo
|
||||
action.Kilo.Reinstall.cli.text=Reinstalar Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Core incluido v{0} • Arquitectura: {1}
|
||||
action.Kilo.Session.Open.text=Abrir
|
||||
action.Kilo.Session.Open.description=Abrir la sesión seleccionada
|
||||
action.Kilo.Session.Rename.text=Renombrar
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Tuer et redémarrer le processus Core
|
||||
action.Kilo.Reinstall.text=Réinstaller Kilo
|
||||
action.Kilo.Reinstall.cli.text=Réinstaller Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Core intégré v{0} • Architecture : {1}
|
||||
action.Kilo.Session.Open.text=Ouvrir
|
||||
action.Kilo.Session.Open.description=Ouvrir la session sélectionnée
|
||||
action.Kilo.Session.Rename.text=Renommer
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Coreプロセスを終了して再起動
|
||||
action.Kilo.Reinstall.text=Kiloを再インストール
|
||||
action.Kilo.Reinstall.cli.text=Coreを再インストール
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=バンドルされた Core v{0} • アーキテクチャ: {1}
|
||||
action.Kilo.Session.Open.text=開く
|
||||
action.Kilo.Session.Open.description=選択したセッションを開く
|
||||
action.Kilo.Session.Rename.text=変名
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Core 프로세스 종료 후 재시작
|
||||
action.Kilo.Reinstall.text=Kilo 재설치
|
||||
action.Kilo.Reinstall.cli.text=Core 재설치
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=번들 Core v{0} • 아키텍처: {1}
|
||||
action.Kilo.Session.Open.text=열기
|
||||
action.Kilo.Session.Open.description=선택한 세션 열기
|
||||
action.Kilo.Session.Rename.text=이름 바꾸기
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Core-proces beëindigen en herstarten
|
||||
action.Kilo.Reinstall.text=Kilo herinstalleren
|
||||
action.Kilo.Reinstall.cli.text=Core herinstalleren
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Gebundelde Core v{0} • Architectuur: {1}
|
||||
action.Kilo.Session.Open.text=Openen
|
||||
action.Kilo.Session.Open.description=Geselecteerde sessie openen
|
||||
action.Kilo.Session.Rename.text=Hernoemen
|
||||
|
||||
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Avslutt og start Core-prosessen på nytt
|
||||
action.Kilo.Reinstall.text=Installer Kilo på nytt
|
||||
action.Kilo.Reinstall.cli.text=Installer Core på nytt
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Innebygd Core v{0} • Arkitektur: {1}
|
||||
action.Kilo.Session.Open.text=Åpne
|
||||
action.Kilo.Session.Open.description=Åpne valgt økt
|
||||
action.Kilo.Session.Rename.text=Gi nytt navn
|
||||
|
||||
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Zakończ i uruchom ponownie proces Core
|
||||
action.Kilo.Reinstall.text=Zainstaluj ponownie Kilo
|
||||
action.Kilo.Reinstall.cli.text=Zainstaluj ponownie Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Wbudowany Core v{0} • Architektura: {1}
|
||||
action.Kilo.Session.Open.text=Otwórz
|
||||
action.Kilo.Session.Open.description=Otwórz wybraną sesję
|
||||
action.Kilo.Session.Rename.text=Zmień nazwę
|
||||
|
||||
+1
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Encerrar e reiniciar o processo Core
|
||||
action.Kilo.Reinstall.text=Reinstalar Kilo
|
||||
action.Kilo.Reinstall.cli.text=Reinstalar Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Core incluído v{0} • Arquitetura: {1}
|
||||
action.Kilo.Session.Open.text=Abrir
|
||||
action.Kilo.Session.Open.description=Abrir a sessão selecionada
|
||||
action.Kilo.Session.Rename.text=Renomear
|
||||
|
||||
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Завершить и перезапустить
|
||||
action.Kilo.Reinstall.text=Переустановить Kilo
|
||||
action.Kilo.Reinstall.cli.text=Переустановить Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Встроенный Core v{0} • Архитектура: {1}
|
||||
action.Kilo.Session.Open.text=Открыть
|
||||
action.Kilo.Session.Open.description=Открыть выбранную сессию
|
||||
action.Kilo.Session.Rename.text=Переименовать
|
||||
|
||||
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=สิ้นสุดและรีสตาร
|
||||
action.Kilo.Reinstall.text=ติดตั้ง Kilo ใหม่
|
||||
action.Kilo.Reinstall.cli.text=ติดตั้ง Core ใหม่
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Core แบบรวมมาในตัว v{0} • สถาปัตยกรรม: {1}
|
||||
action.Kilo.Session.Open.text=เปิด
|
||||
action.Kilo.Session.Open.description=เปิดเซสชันที่เลือก
|
||||
action.Kilo.Session.Rename.text=เปลี่ยนชื่อ
|
||||
|
||||
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Core sürecini sonlandır ve yeniden başlat
|
||||
action.Kilo.Reinstall.text=Kilo’yu yeniden yükle
|
||||
action.Kilo.Reinstall.cli.text=Core’yi yeniden yükle
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Paketlenmiş Core v{0} • Mimari: {1}
|
||||
action.Kilo.Session.Open.text=Aç
|
||||
action.Kilo.Session.Open.description=Seçilen oturumu aç
|
||||
action.Kilo.Session.Rename.text=Yeniden adlandır
|
||||
|
||||
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Завершити і перезапустити
|
||||
action.Kilo.Reinstall.text=Перевстановити Kilo
|
||||
action.Kilo.Reinstall.cli.text=Перевстановити Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=Вбудований Core v{0} • Архітектура: {1}
|
||||
action.Kilo.Session.Open.text=Відкрити
|
||||
action.Kilo.Session.Open.description=Відкрити вибрану сесію
|
||||
action.Kilo.Session.Rename.text=Перейменувати
|
||||
|
||||
+1
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=终止并重启 Core 进程
|
||||
action.Kilo.Reinstall.text=重新安装 Kilo
|
||||
action.Kilo.Reinstall.cli.text=重新安装 Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=捆绑的 Core v{0} • 架构:{1}
|
||||
action.Kilo.Session.Open.text=打开
|
||||
action.Kilo.Session.Open.description=打开所选会话
|
||||
action.Kilo.Session.Rename.text=重命名
|
||||
|
||||
+1
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=終止並重啟 Core 程序
|
||||
action.Kilo.Reinstall.text=重新安裝 Kilo
|
||||
action.Kilo.Reinstall.cli.text=重新安裝 Core
|
||||
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
|
||||
action.Kilo.CoreInfo.bundled=捆綁的 Core v{0} • 架構:{1}
|
||||
action.Kilo.Session.Open.text=開啟
|
||||
action.Kilo.Session.Open.description=開啟所選工作階段
|
||||
action.Kilo.Session.Rename.text=重命名
|
||||
|
||||
+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