Merge remote-tracking branch 'origin/main' into gigantic-fighter

# Conflicts:
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsListView.kt
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsPanel.kt
#	packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/SettingsListViewTest.kt
This commit is contained in:
kirillk
2026-07-20 17:46:27 -04:00
104 changed files with 2469 additions and 183 deletions
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": minor
"kilo-code": minor
---
Allow agents to stop and remove a targeted Agent Manager session.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Improve CLI sidebar usage sections with cent-formatted costs, collapsible details, and aligned model totals.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Respect the Terminal Command Blocks display setting for background process tool cards.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Display here-document content as plain text in terminal approval prompts.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Support bidirectional text in chat messages so right-to-left and mixed-language conversations render in the correct direction.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Clarify when reverting a conversation does not restore workspace changes and link disabled snapshots to the Checkpoints setting.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": patch
"@kilocode/kilo-gateway": patch
---
Fix Cloud Agent session imports in installed CLI builds and prevent malformed exports or write failures from leaving partial imports.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Support choosing a reasoning effort per model in the Agent Manager Compare Models picker, so compared worktrees can run the same prompt at different effort levels. The selected effort is shown next to the model name in the collapsed selector.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Open the Agent Manager new-worktree dialog from primary actions and keyboard shortcuts, with an explicit quick-create action in its dropdown.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Fix diff content painting through and above the sticky headers of expanded Edit, Write, and Apply Patch tool cards when scrolling large diffs.
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Keep sessions usable after deprecated local review commands complete without a session status transition.
-7
View File
@@ -1,7 +0,0 @@
---
"@kilocode/cli": minor
"@kilocode/kilo-indexing": minor
"kilo-code": minor
---
Configure a custom file extension allowlist for codebase indexing to limit scans to relevant project files and support additional text formats.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Add JetBrains Context settings for compaction and file watcher ignore patterns.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Use Kilo Core for JetBrains @ file completion.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": minor
---
Support viewing, opening, editing, deleting, and configuring JetBrains skill sources.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Keep the CLI sidebar branch label in sync when Git branches change outside Kilo.
-6
View File
@@ -1,6 +0,0 @@
---
"kilo-code": patch
"@kilocode/cli": patch
---
Include image-output models in the Kilo Gateway chat model list.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-indexing": patch
---
Allow remote embedder validation timeouts to retry before indexing reports a connection failure.
@@ -1,5 +0,0 @@
---
"@kilocode/cli": minor
---
Remote CLI: expose slash command discovery, execution, and `/new` session creation over the relay. `list_commands` and `send_command` (including the built-in `compact` flow) are scoped to the current session's directory. `create_session` creates a root session in that directory, attaches it to the relay heartbeat set, and returns the new session id only after the heartbeat completes so the mobile client can navigate immediately. Failures are sanitized; the command is not auto-retried and the user may retry manually after a transient relay failure.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Cycle reasoning effort variants with Shift+Tab in prompt inputs. Works in the sidebar chat, Agent Manager, and the New Worktree dialog. The variant selector tooltip shows the shortcut on hover, and the behavior can be turned off with the `kilo-code.new.chat.shiftTabCyclesVariant` setting (also available under Settings > Display) to restore Shift+Tab focus navigation.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Fix double scrollbars in the Agent Manager new-worktree prompt field and widen the dialog so longer prompts stay readable. The prompt box now grows with its content like the sidebar chat input, the textarea is the only element that scrolls, and manual resize of the prompt area keeps working.
+25 -25
View File
@@ -32,7 +32,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "7.4.11",
"version": "7.4.13",
"bin": {
"opencode": "./bin/opencode",
},
@@ -127,7 +127,7 @@
},
"packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -141,7 +141,7 @@
},
"packages/effect-sqlite-node": {
"name": "@opencode-ai/effect-sqlite-node",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"effect": "catalog:",
},
@@ -153,7 +153,7 @@
},
"packages/http-recorder": {
"name": "@opencode-ai/http-recorder",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@effect/platform-node": "4.0.0-beta.74",
"@effect/platform-node-shared": "4.0.0-beta.74",
@@ -174,7 +174,7 @@
},
"packages/kilo-console": {
"name": "@kilocode/kilo-console",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@kilocode/kilo-indexing": "workspace:*",
"@kilocode/kilo-web-ui": "workspace:*",
@@ -197,7 +197,7 @@
},
"packages/kilo-docs": {
"name": "@kilocode/kilo-docs",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@docsearch/css": "^4",
"@docsearch/js": "^4",
@@ -227,7 +227,7 @@
},
"packages/kilo-gateway": {
"name": "@kilocode/kilo-gateway",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/anthropic": "3.0.71",
@@ -263,7 +263,7 @@
},
"packages/kilo-i18n": {
"name": "@kilocode/kilo-i18n",
"version": "7.4.11",
"version": "7.4.13",
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
@@ -273,7 +273,7 @@
},
"packages/kilo-indexing": {
"name": "@kilocode/kilo-indexing",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
"@aws-sdk/credential-provider-ini": "3.972.31",
@@ -309,7 +309,7 @@
},
"packages/kilo-memory": {
"name": "@kilocode/kilo-memory",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"effect": "catalog:",
"zod": "catalog:",
@@ -323,7 +323,7 @@
},
"packages/kilo-sandbox": {
"name": "@kilocode/sandbox",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@anthropic-ai/sandbox-runtime": "catalog:",
"effect": "catalog:",
@@ -338,7 +338,7 @@
},
"packages/kilo-telemetry": {
"name": "@kilocode/kilo-telemetry",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@kilocode/kilo-gateway": "workspace:*",
"posthog-node": "4.4.0",
@@ -352,7 +352,7 @@
},
"packages/kilo-ui": {
"name": "@kilocode/kilo-ui",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "0.13.11",
@@ -389,7 +389,7 @@
},
"packages/kilo-vscode": {
"name": "kilo-code",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@kilocode/kilo-gateway": "workspace:*",
@@ -458,7 +458,7 @@
},
"packages/kilo-web-ui": {
"name": "@kilocode/kilo-web-ui",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@kilocode/kilo-ui": "workspace:*",
"@kobalte/core": "catalog:",
@@ -475,7 +475,7 @@
},
"packages/llm": {
"name": "@opencode-ai/llm",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
@@ -493,7 +493,7 @@
},
"packages/opencode": {
"name": "@kilocode/cli",
"version": "7.4.11",
"version": "7.4.13",
"bin": {
"kilo": "./bin/kilo",
"kilocode": "./bin/kilo",
@@ -660,7 +660,7 @@
},
"packages/plugin": {
"name": "@kilocode/plugin",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"effect": "catalog:",
@@ -688,7 +688,7 @@
},
"packages/plugin-atomic-chat": {
"name": "@kilocode/plugin-atomic-chat",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@kilocode/plugin": "workspace:*",
},
@@ -702,7 +702,7 @@
},
"packages/script": {
"name": "@opencode-ai/script",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"semver": "^7.6.3",
},
@@ -713,7 +713,7 @@
},
"packages/sdk/js": {
"name": "@kilocode/sdk",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -728,7 +728,7 @@
},
"packages/server": {
"name": "@opencode-ai/server",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"drizzle-orm": "catalog:",
@@ -742,7 +742,7 @@
},
"packages/storybook": {
"name": "@opencode-ai/storybook",
"version": "7.4.11",
"version": "7.4.13",
"devDependencies": {
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
@@ -765,7 +765,7 @@
},
"packages/tui": {
"name": "@opencode-ai/tui",
"version": "7.4.8",
"version": "7.4.13",
"dependencies": {
"@kilocode/plugin": "workspace:*",
"@kilocode/sdk": "workspace:*",
@@ -792,7 +792,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "catalog:",
+1 -1
View File
@@ -164,6 +164,6 @@
"pacote@21.5.1": "patches/pacote@21.5.1.patch",
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch"
},
"version": "7.4.11",
"version": "7.4.13",
"peerDependencies": {}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.4.11",
"version": "7.4.13",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.4.11",
"version": "7.4.13",
"name": "@opencode-ai/effect-drizzle-sqlite",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.4.11",
"version": "7.4.13",
"name": "@opencode-ai/effect-sqlite-node",
"type": "module",
"license": "MIT",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "kilo"
name = "Kilo"
description = "The open source coding agent."
version = "7.4.11"
version = "7.4.13"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/Kilo-Org/kilocode"
@@ -11,26 +11,26 @@ name = "Kilo"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-darwin-arm64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.13/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-darwin-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.13/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-linux-arm64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.13/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-linux-x64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.13/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-windows-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.13/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.4.11",
"version": "7.4.13",
"name": "@opencode-ai/http-recorder",
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-console",
"version": "7.4.11",
"version": "7.4.13",
"private": true,
"type": "module",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-docs",
"version": "7.4.11",
"version": "7.4.13",
"private": true,
"scripts": {
"dev": "next dev --webpack --port 3002",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-gateway",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-i18n",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"description": "Kilo-specific i18n translations and overrides",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-indexing",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"description": "Standalone indexing engine and host helpers for Kilo Code",
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 7.4.12
### Patch Changes
- [#12191](https://github.com/Kilo-Org/kilocode/pull/12191) [`4d676b6`](https://github.com/Kilo-Org/kilocode/commit/4d676b68d2d0dd025c7d1a6684f49f3d03e9d12d) - Use Kilo Core for JetBrains @ file completion.
## 7.4.10
### Patch Changes
@@ -820,11 +820,13 @@ class KiloBackendAppService private constructor(
}
}
"global.disposed" -> {
logSessionDisposalRisk("global.disposed")
log.info("SSE global.disposed — triggering full application reload")
val current = _appState.value
if (current is KiloAppState.Ready) load()
}
"server.instance.disposed" -> {
logSessionDisposalRisk("server.instance.disposed")
log.info("SSE server.instance.disposed — triggering full application reload")
val current = _appState.value
if (current is KiloAppState.Ready) load()
@@ -835,6 +837,12 @@ class KiloBackendAppService private constructor(
}
}
private fun logSessionDisposalRisk(event: String) {
val active = sessions.statuses.value.filterValues { it.type != "idle" }
if (active.isEmpty()) return
log.warn("SSE $event while sessions are active; sessions may be cancelled count=${active.size} statuses=${active.values.map { it.type }.distinct()}")
}
private suspend fun clear() {
synchronized(loadLock) {
val jobs = listOfNotNull(loader, eventWatcher)
@@ -86,6 +86,10 @@ class KiloBackendSessionManager(
}
fun stop() {
val active = _statuses.value.filterValues { it.type != "idle" }
if (active.isNotEmpty()) {
log.warn("Session manager stopping with active sessions count=${active.size} statuses=${active.values.map { it.type }.distinct()}")
}
watcher?.cancel()
watcher = null
client = null
@@ -649,7 +649,12 @@ object KiloCliDataParser {
val obj = item.obj() ?: return@mapNotNull null
val name = obj.str("name") ?: return@mapNotNull null
val location = obj.str("location") ?: return@mapNotNull null
SkillDto(name = name, description = obj.str("description"), location = location)
SkillDto(
name = name,
description = obj.str("description"),
location = location,
content = obj.str("content"),
)
}
fun parseAgentBehaviorCommands(raw: String): List<CommandDto> =
@@ -14,6 +14,7 @@ import ai.kilocode.rpc.dto.ConfigPatchDto
import ai.kilocode.rpc.dto.McpConfigDto
import ai.kilocode.rpc.dto.McpServerConfigDto
import ai.kilocode.rpc.dto.PermissionRuleItemDto
import ai.kilocode.rpc.dto.SkillDto
import com.intellij.openapi.components.service
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -22,7 +23,11 @@ import kotlinx.serialization.json.JsonPrimitive
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import com.intellij.openapi.util.SystemInfo
import java.net.URLEncoder
import java.nio.file.Files
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.charset.StandardCharsets
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
@@ -33,6 +38,7 @@ class KiloAgentBehaviorRpcApiImpl(private val backend: KiloBackendAppService? =
private val JSON = "application/json".toMediaType()
private val saved = ConcurrentHashMap<String, SavedMcp>()
private val port = AtomicInteger(-1)
private val extensions = setOf("md", "markdown", "txt", "text", "html", "htm")
}
private val app: KiloBackendAppService get() = backend ?: service()
@@ -56,11 +62,59 @@ class KiloAgentBehaviorRpcApiImpl(private val backend: KiloBackendAppService? =
}
}
override suspend fun skills(directory: String) = KiloCliDataParser.parseAgentBehaviorSkills(request(directory, "/skill", null))
override suspend fun skills(directory: String): List<SkillDto> {
val items = KiloCliDataParser.parseAgentBehaviorSkills(request(directory, "/skill", null))
return items.map { item ->
val editable = editable(item)
item.copy(content = skillContent(item) ?: item.content, editable = editable)
}
}
override suspend fun removeSkill(directory: String, location: String): Boolean =
post(directory, "/kilocode/skill/remove", JsonObject(mapOf("location" to JsonPrimitive(location))))
override suspend fun reloadSkills(directory: String): Boolean {
LOG.info("Skills reload requested dir=$directory")
if (hasActiveSession(directory)) {
LOG.warn("Skills reload blocked by active session dir=$directory")
return false
}
runCatching { post(directory, "/instance/reload") }.onFailure { err ->
LOG.warn("Skills reload failed dir=$directory", err)
}.getOrThrow()
LOG.info("Skills reload succeeded dir=$directory")
return true
}
override suspend fun saveSkill(directory: String, location: String, content: String): Boolean {
LOG.info("Skill save requested dir=$directory location=$location")
app.requireReady()
val paths = knownSkills(directory)
val path = writablePath(directory, location, paths) ?: return false
withContext(Dispatchers.IO) {
Files.writeString(path, content, StandardCharsets.UTF_8)
}
LOG.info("Skill file saved dir=$directory path=$path bytes=${content.toByteArray(StandardCharsets.UTF_8).size}")
LOG.info("Skill save reload deferred dir=$directory path=$path")
return true
}
override suspend fun saveSkills(directory: String, edits: Map<String, String>): Boolean {
LOG.info("Skills save requested dir=$directory count=${edits.size}")
app.requireReady()
val known = knownSkills(directory)
val paths = edits.mapNotNull { (location, content) ->
val path = writablePath(directory, location, known) ?: return false
path to content
}
withContext(Dispatchers.IO) {
for ((path, content) in paths) Files.writeString(path, content, StandardCharsets.UTF_8)
}
LOG.info("Skill files saved dir=$directory count=${paths.size}")
LOG.info("Skills save reload deferred dir=$directory count=${paths.size}")
return true
}
override suspend fun removeAgent(directory: String, name: String): Boolean =
post(directory, "/kilocode/agent/remove", JsonObject(mapOf("name" to JsonPrimitive(name))))
@@ -139,6 +193,104 @@ class KiloAgentBehaviorRpcApiImpl(private val backend: KiloBackendAppService? =
return true
}
private fun hasActiveSession(directory: String): Boolean {
val active = app.sessions.statuses.value.filterValues { it.type != "idle" }
if (active.isNotEmpty()) {
LOG.info("Skills reload active statuses dir=$directory count=${active.size} types=${active.values.map { it.type }.distinct()}")
return true
}
val permissions = runCatching { app.chat.pendingPermissions(directory) }.onFailure { err ->
LOG.warn("Skills reload pending permission check failed dir=$directory", err)
}.getOrDefault(emptyList())
if (permissions.isNotEmpty()) {
LOG.info("Skills reload pending permissions dir=$directory count=${permissions.size}")
return true
}
val questions = runCatching { app.chat.pendingQuestions(directory) }.onFailure { err ->
LOG.warn("Skills reload pending question check failed dir=$directory", err)
}.getOrDefault(emptyList())
if (questions.isNotEmpty()) {
LOG.info("Skills reload pending questions dir=$directory count=${questions.size}")
return true
}
return false
}
private suspend fun skillContent(skill: SkillDto): String? {
val path = resolveSkillPath(skill.location) ?: return null
return runCatching {
withContext(Dispatchers.IO) {
if (!Files.isRegularFile(path)) null else Files.readString(path, StandardCharsets.UTF_8)
}
}.onFailure { err ->
LOG.warn("Skill content read failed: $path", err)
}.getOrNull()
}
private fun editable(skill: SkillDto): Boolean {
val path = resolveSkillPath(skill.location) ?: return false
if (urlCached(path)) return false
return true
}
private suspend fun knownSkills(directory: String): Set<Path> {
val items = KiloCliDataParser.parseAgentBehaviorSkills(request(directory, "/skill", null))
return items.mapNotNull { item -> resolveEditablePath(item) }.toSet()
}
private fun writablePath(directory: String, location: String, known: Set<Path>): Path? {
val path = resolveSkillPath(location)
if (path == null) {
LOG.warn("Skill save rejected: invalid location dir=$directory location=$location")
return null
}
if (path !in known) {
LOG.warn("Skill save rejected: unknown skill dir=$directory path=$path")
return null
}
return path
}
private fun resolveEditablePath(skill: SkillDto): Path? {
val path = resolveSkillPath(skill.location) ?: return null
if (urlCached(path)) return null
return path
}
private fun resolveSkillPath(location: String): Path? {
val raw = normalizeWorkspacePath(location) ?: return null
val path = try {
Path.of(raw).normalize()
} catch (_: InvalidPathException) {
return null
}
if (!path.isAbsolute || !isSkillFile(path)) return null
return path
}
private fun urlCached(path: Path): Boolean {
return cacheRoots().any { root -> path.startsWith(root.resolve("kilo").resolve("skills").normalize()) }
}
private fun cacheRoots(): Set<Path> = buildSet {
val home = System.getProperty("user.home")
add(Path.of(cacheRoot()).normalize())
add(Path.of(home, ".cache").normalize())
add(Path.of(home, "Library", "Caches").normalize())
System.getenv("LOCALAPPDATA")?.takeIf { it.isNotBlank() }?.let { add(Path.of(it).normalize()) }
add(Path.of(home, "AppData", "Local").normalize())
}
private fun cacheRoot(): String {
val xdg = System.getenv("XDG_CACHE_HOME")?.takeIf { it.isNotBlank() }
if (xdg != null) return xdg
val home = System.getProperty("user.home")
if (SystemInfo.isMac) return Path.of(home, "Library", "Caches").toString()
if (SystemInfo.isWindows) return System.getenv("LOCALAPPDATA")?.takeIf { it.isNotBlank() }
?: Path.of(home, "AppData", "Local").toString()
return Path.of(home, ".cache").toString()
}
private suspend fun patchConfig(path: String, body: String): Unit = withContext(Dispatchers.IO) {
val http = app.http ?: throw IllegalStateException("Kilo HTTP client is unavailable")
val url = "http://127.0.0.1:${app.port}$path"
@@ -234,6 +386,12 @@ class KiloAgentBehaviorRpcApiImpl(private val backend: KiloBackendAppService? =
private fun encodePath(value: String): String = encode(value).replace("+", "%20")
private fun isSkillFile(path: Path): Boolean {
val name = path.fileName?.toString() ?: return false
if (name == "SKILL.md") return true
return name.substringAfterLast('.', "").lowercase() in extensions
}
private data class SavedMcp(
val directory: String,
val name: String,
@@ -64,6 +64,8 @@ internal object KiloWorkspaceDtoMapper {
name = s.name,
description = s.description,
location = s.location,
content = s.content,
editable = false,
)
private fun provider(p: ProviderInfo) = ProviderDto(
@@ -348,7 +348,7 @@ class KiloWorkspaceRpcApiImpl internal constructor(
}
descriptor.navigate(true)
if (cont.isActive) cont.resume(Unit)
}, ModalityState.any())
}, ModalityState.nonModal())
}
private fun project(path: Path): Project? {
@@ -254,6 +254,7 @@ class KiloBackendWorkspace(
name = s.name,
description = s.description,
location = s.location,
content = s.content,
)
})
} catch (e: CancellationException) {
@@ -143,4 +143,5 @@ data class SkillInfo(
val name: String,
val description: String?,
val location: String,
val content: String?,
)
@@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import okhttp3.Request
import okhttp3.sse.EventSource
import okhttp3.sse.EventSourceListener
@@ -36,6 +37,10 @@ import kotlin.test.assertTrue
class KiloConnectionServiceTest {
private companion object {
const val WAIT_MS = 15_000L
}
private val mock = MockCliServer()
private val fake = FakeCliServer(mock)
private val log = TestLog()
@@ -101,20 +106,23 @@ class KiloConnectionServiceTest {
val svc = KiloConnectionService(scope, server, {}, log)
val job = scope.launch { svc.connect() }
val downloading = withTimeout(5_000) {
val downloading = withTimeout(WAIT_MS) {
svc.state.first { it is ConnectionState.Downloading }
}
assertEquals(ConnectionState.Downloading(42, "1.2.3", "darwin-arm64"), downloading)
resolved.complete(Unit)
withTimeout(5_000) {
withTimeout(WAIT_MS) {
svc.state.first { it == ConnectionState.Connecting }
}
ready.complete(Unit)
withTimeout(5_000) {
val connected = withTimeoutOrNull(WAIT_MS) {
svc.state.first { it is ConnectionState.Connected }
}
if (connected == null) {
error("Timed out waiting for Connected after CLI ready; state=${svc.state.value}; logs=${log.messages.joinToString("\n")}")
}
job.join()
}
@@ -14,6 +14,8 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import java.nio.file.Files
import java.nio.file.Path
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertContains
@@ -78,6 +80,156 @@ class KiloAgentBehaviorRpcApiImplTest {
assertContains(err.message.orEmpty(), "HTTP 400")
}
@Test
fun `skills and remove skill call CLI endpoints`() = runBlocking {
val dir = Files.createTempDirectory("kilo-skill-test")
val file = Files.createDirectories(dir.resolve("plan")).resolve("SKILL.md")
val content = """---
|name: plan
|description: Plan work
|---
|
|# Fresh Plan
""".trimMargin()
Files.writeString(file, content)
mock.skills = """[
{"name":"plan","description":"Plan work","location":"$file","content":"# Stale Plan"},
{"name":"builtin","location":"builtin"}
]""".trimIndent()
val rpc = rpc()
val skills = rpc.skills("/test project")
assertEquals(listOf("plan", "builtin"), skills.map { it.name })
assertEquals("Plan work", skills.single { it.name == "plan" }.description)
assertEquals(content, skills.single { it.name == "plan" }.content)
assertEquals(true, skills.single { it.name == "plan" }.editable)
assertEquals(false, skills.single { it.name == "builtin" }.editable)
assertTrue(rpc.removeSkill("/test project", file.toString()))
assertEquals("{\"location\":\"$file\"}", mock.lastSkillRemoveBody)
assertEquals(1, mock.requestCount("/kilocode/skill/remove"))
mock.skillRemoveStatus = 400
val err = assertFailsWith<RuntimeException> {
rpc.removeSkill("/test", "/tmp/missing/SKILL.md")
}
assertContains(err.message.orEmpty(), "HTTP 400")
assertTrue(rpc.reloadSkills("/test project"))
assertEquals(1, mock.requestCount("/instance/reload"))
}
@Test
fun `url cached skills are read only`() = runBlocking {
val cache = Path.of(System.getProperty("user.home"), ".cache", "kilo", "skills", "remote")
val file = Files.createDirectories(cache).resolve("SKILL.md")
Files.writeString(file, "# Remote")
mock.skills = """[
{"name":"remote","description":"Remote","location":"$file","content":"# Remote"}
]""".trimIndent()
val skill = rpc().skills("/test project").single()
assertEquals(false, skill.editable)
assertEquals("# Remote", skill.content)
}
@Test
fun `custom skills under non cache paths remain editable`() = runBlocking {
val dir = Files.createTempDirectory("kilo-skill-test")
val file = Files.createDirectories(dir.resolve("cache/kilo/skills/custom")).resolve("SKILL.md")
Files.writeString(file, "# Custom")
mock.skills = """[
{"name":"custom","description":"Custom","location":"$file","content":"# Custom"}
]""".trimIndent()
val skill = rpc().skills("/test project").single()
assertEquals(true, skill.editable)
}
@Test
fun `save skill supports configured markdown text and html files without reload`() = runBlocking {
val dir = Files.createTempDirectory("kilo-skill-test")
val file = dir.resolve("test.md")
Files.writeString(file, "old")
mock.skills = """[
{"name":"test","description":"Test","location":"$file","content":"old"}
]""".trimIndent()
val rpc = rpc()
assertTrue(rpc.saveSkill("/test project", file.toString(), "new content"))
assertEquals("new content", Files.readString(file))
assertEquals(0, mock.requestCount("/instance/reload"))
}
@Test
fun `save skill writes content without reloading instance`() = runBlocking {
val dir = Files.createTempDirectory("kilo-skill-test")
val file = Files.createDirectories(dir.resolve("plan")).resolve("SKILL.md")
Files.writeString(file, "old")
mock.skills = """[
{"name":"plan","description":"Plan work","location":"$file","content":"old"}
]""".trimIndent()
val rpc = rpc()
assertTrue(rpc.saveSkill("/test project", file.toString(), "new content"))
assertEquals("new content", Files.readString(file))
assertEquals(0, mock.requestCount("/instance/reload"))
assertFalse(rpc.saveSkill("/test project", "builtin", "nope"))
}
@Test
fun `save skills validates known paths once for multiple edits`() = runBlocking {
val dir = Files.createTempDirectory("kilo-skill-test")
val plan = Files.createDirectories(dir.resolve("plan")).resolve("SKILL.md")
val review = Files.createDirectories(dir.resolve("review")).resolve("SKILL.md")
Files.writeString(plan, "old plan")
Files.writeString(review, "old review")
mock.skills = """[
{"name":"plan","description":"Plan work","location":"$plan","content":"old plan"},
{"name":"review","description":"Review work","location":"$review","content":"old review"}
]""".trimIndent()
val rpc = rpc()
mock.resetCounts()
assertTrue(rpc.saveSkills("/test project", mapOf(plan.toString() to "new plan", review.toString() to "new review")))
assertEquals("new plan", Files.readString(plan))
assertEquals("new review", Files.readString(review))
assertEquals(1, mock.requestCount("/skill"))
}
@Test
fun `save skill rejects unknown absolute skill files`() = runBlocking {
val dir = Files.createTempDirectory("kilo-skill-test")
val known = Files.createDirectories(dir.resolve("known")).resolve("SKILL.md")
val other = Files.createDirectories(dir.resolve("other")).resolve("SKILL.md")
Files.writeString(known, "known")
Files.writeString(other, "old")
mock.skills = """[
{"name":"known","description":"Known","location":"$known","content":"known"}
]""".trimIndent()
assertFalse(rpc().saveSkill("/test project", other.toString(), "new content"))
assertEquals("old", Files.readString(other))
}
@Test
fun `reload skills is blocked by pending permissions`() = runBlocking {
mock.pendingPermissions = """[
{"id":"per_test","sessionID":"ses_test","permission":"bash","patterns":["*"],"metadata":{}}
]""".trimIndent()
val rpc = rpc()
assertFalse(rpc.reloadSkills("/test project"))
assertEquals(1, mock.requestCount("/permission"))
assertEquals(0, mock.requestCount("/instance/reload"))
}
@Test
fun `mcp config writes global and workspace patches`() = runBlocking {
mock.config = """{"mcp":{"global":{"type":"local","command":["node","g.js"]}}}"""
@@ -68,9 +68,11 @@ class MockCliServer : AutoCloseable {
@Volatile var mcpStatus = 200
@Volatile var mcpActionStatus = 200
@Volatile var agentRemoveStatus = 200
@Volatile var skillRemoveStatus = 200
@Volatile var agentBuilderStatus = 200
@Volatile var lastMcpActionPath: String? = null
@Volatile var lastAgentRemoveBody: String? = null
@Volatile var lastSkillRemoveBody: String? = null
@Volatile var lastAgentBuilderPath: String? = null
@Volatile var lastAgentBuilderBody: String? = null
@Volatile var lastAgentBuilderMethod: String? = null
@@ -135,6 +137,8 @@ class MockCliServer : AutoCloseable {
@Volatile var lastSessionRenamePath: String? = null
@Volatile var lastSessionRenameBody: String? = null
@Volatile var lastSessionRenameMethod: String? = null
@Volatile var pendingPermissions = "[]"
@Volatile var pendingQuestions = "[]"
/** Configurable delay for all endpoint responses (ms). 0 = no delay. */
@Volatile var responseDelay: Long = 0
@@ -375,6 +379,11 @@ class MockCliServer : AutoCloseable {
lastAgentRemoveBody = body
respond(output, agentRemoveStatus, if (agentRemoveStatus == 200) "true" else """{"error":"Agent not found"}""")
}
bare == "/kilocode/skill/remove" && method == "POST" -> {
lastSkillRemoveBody = body
respond(output, skillRemoveStatus, if (skillRemoveStatus == 200) "true" else """{"error":"Skill not found"}""")
}
bare == "/instance/reload" && method == "POST" -> respond(output, 200, "true")
bare == "/command" -> respond(output, commandsStatus, commands)
bare == "/skill" -> respond(output, skillsStatus, skills)
bare == "/find/file" -> {
@@ -405,6 +414,8 @@ class MockCliServer : AutoCloseable {
respond(output, cloudSessionImportStatus, cloudSessionImport)
}
bare == "/session/status" -> respond(output, sessionStatusesStatus, sessionStatuses)
bare == "/permission" && method == "GET" -> respond(output, 200, pendingPermissions)
bare == "/question" && method == "GET" -> respond(output, 200, pendingQuestions)
bare == "/session" && method == "GET" -> respond(output, sessionsStatus, sessions)
bare == "/session" && method == "POST" -> respond(output, sessionCreateStatus, sessionCreate)
bare.matches(Regex("/session/ses_[^/]+")) && method == "GET" ->
@@ -21,4 +21,13 @@ object KiloNotifications {
?: Notification(GROUP, title, content ?: "", NotificationType.ERROR)
notification.notify(project)
}
fun info(title: String, content: String? = null) {
val project = ProjectManager.getInstance().openProjects.firstOrNull { !it.isDefault }
val notification = NotificationGroupManager.getInstance()
.getNotificationGroup(GROUP)
?.createNotification(title, content ?: "", NotificationType.INFORMATION)
?: Notification(GROUP, title, content ?: "", NotificationType.INFORMATION)
notification.notify(project)
}
}
@@ -33,7 +33,9 @@ class KiloAgentBehaviorService internal constructor(
suspend fun agents(directory: String): List<AgentDetailDto> = safe(emptyList()) { call { agents(directory) } }
suspend fun skills(directory: String): List<SkillDto> = safe(emptyList()) { call { skills(directory) } }
suspend fun loadSkills(directory: String): List<SkillDto> = call { skills(directory) }
suspend fun refreshSkills(directory: String, fallback: List<SkillDto>): List<SkillDto> = safe(fallback) { call { skills(directory) } }
suspend fun commands(directory: String): List<CommandDto> = safe(emptyList()) { call { commands(directory) } }
@@ -52,6 +54,11 @@ class KiloAgentBehaviorService internal constructor(
suspend fun removeSkill(directory: String, location: String): Boolean = safe(false) { call { removeSkill(directory, location) } }
suspend fun reloadSkills(directory: String): Boolean = safe(false) { call { reloadSkills(directory) } }
suspend fun saveSkills(directory: String, edits: Map<String, String>): Boolean =
safe(false) { call { saveSkills(directory, edits) } }
suspend fun removeAgent(directory: String, name: String): Boolean = safe(false) { call { removeAgent(directory, name) } }
suspend fun createAgent(directory: String, input: AgentCreateDto): Boolean = safe(false) { call { createAgent(directory, input) } }
@@ -169,6 +169,15 @@ class KiloWorkspaceService internal constructor(
}
}
suspend fun openFile(path: String, line: Int? = null, column: Int? = null): Boolean {
return try {
call { openFile(path, line, column) }
} catch (e: Exception) {
LOG.warn("workspace file open failed for path=$path", e)
false
}
}
suspend fun localConfigTarget(directory: String): ConfigTargetDto? {
return try {
val target = call { this.localConfigTarget(directory) }
@@ -25,6 +25,7 @@ class AgentBehaviorConfigurable : SearchableConfigurable {
listOf(
KiloBundle.message("settings.agentBehavior.agents.displayName") to AgentsConfigurable.ID,
KiloBundle.message("settings.agentBehavior.mcp.displayName") to McpConfigurable.ID,
KiloBundle.message("settings.agentBehavior.skills.displayName") to SkillsConfigurable.ID,
).forEach { (label, id) ->
panel.next(ActionLink(label) { e ->
val src = e.source as? JComponent ?: return@ActionLink
@@ -0,0 +1,586 @@
package ai.kilocode.client.settings.agents
import ai.kilocode.client.app.KiloAgentBehaviorService
import ai.kilocode.client.app.KiloAppService
import ai.kilocode.client.app.KiloWorkspaceService
import ai.kilocode.client.KiloNotifications
import ai.kilocode.client.plugin.KiloBundle
import ai.kilocode.client.session.ui.style.SessionUiStyle
import ai.kilocode.client.settings.base.SettingsBadge
import ai.kilocode.client.settings.base.SettingsDraftPage
import ai.kilocode.client.settings.base.SettingsDraftState
import ai.kilocode.client.settings.base.SettingsListCell
import ai.kilocode.client.settings.base.SettingsListConfig
import ai.kilocode.client.settings.base.SettingsListItem
import ai.kilocode.client.settings.base.SettingsListPanel
import ai.kilocode.client.settings.base.SettingsListSelection
import ai.kilocode.client.settings.base.SettingsListView
import ai.kilocode.client.settings.base.SettingsMessageException
import ai.kilocode.client.ui.UiStyle
import ai.kilocode.client.ui.layout.Stack
import ai.kilocode.log.KiloLog
import ai.kilocode.rpc.dto.ConfigPatchDto
import ai.kilocode.rpc.dto.SkillsConfigDto
import ai.kilocode.rpc.dto.SkillsPatchDto
import ai.kilocode.rpc.dto.SkillDto
import com.intellij.CommonBundle
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileChooser.FileChooser
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.openapi.fileTypes.UnknownFileType
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.EditorTextField
import com.intellij.ui.TitledSeparator
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.components.JBTextField
import com.intellij.util.ui.JBUI
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import java.awt.BorderLayout
import javax.swing.JButton
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.ScrollPaneConstants
import javax.swing.ListSelectionModel
private val edt = Dispatchers.EDT + ModalityState.any().asContextElement()
class SkillsConfigurable : AgentBehaviorConfigurableBase<JComponent>() {
override fun getId(): String = ID
override fun getDisplayName(): String = KiloBundle.message("settings.agentBehavior.skills.displayName")
override fun create(cs: CoroutineScope, dir: String): JComponent = SkillsSettingsUi(cs, dir)
override fun update(ui: JComponent, dir: String) {
(ui as? SkillsSettingsUi)?.setDirectory(dir)
}
override fun scrollReadyShell() = false
companion object { const val ID = "ai.kilocode.jetbrains.settings.agentBehavior.skills" }
}
internal class SkillsSettingsUi(
scope: CoroutineScope,
dir: String,
private val choose: (JComponent) -> String? = ::chooseSkillPath,
private val input: (String, String) -> String? = ::inputSkillUrl,
private val edit: (SkillDto, Boolean) -> SkillEditDialogHandle = ::SkillEditDialog,
) : SettingsListPanel(scope, SettingsListConfig.Equal.copy(tooltip = false)), SettingsDraftPage {
private val cs = scope
private var dir = dir
private var skills = emptyMap<String, SkillDto>()
private val app get() = service<KiloAppService>()
private val state = SettingsDraftState(skillsDraft(app.state.value.config?.skills ?: SkillsConfigDto()), ::saved)
private var draft: SkillsDraft
get() = state.draft
set(value) {
state.draft = value
}
internal val sources = SkillSourcesView(this, choose, input)
init {
start()
setCenter(skillScroll())
content.add(sources, BorderLayout.SOUTH)
}
fun setDirectory(value: String) {
if (value == dir) return
dir = value
reload()
}
override suspend fun fetch(): List<SettingsListItem> {
val items = withTimeoutOrNull(SKILL_LOAD_TIMEOUT_MS) {
service<KiloAgentBehaviorService>().loadSkills(dir)
} ?: throw SettingsMessageException(KiloBundle.message("settings.agentBehavior.skills.load.timeout"))
withContext(edt) {
val dirty = state.modified()
val edit = draft
state.accept(skillsDraft(config()))
if (dirty) draft = state.draft.copy(edited = edit.edited, deleted = edit.deleted)
skills = items.associateBy { key(it) }
sources.refresh(draft.sources)
}
LOG.info("skills settings fetch dir=$dir total=${items.size}")
return rows(items)
}
override fun afterApply() {
sources.refresh(draft.sources)
}
override fun onCell(key: String, cellId: String) {
val skill = skills[key] ?: return
when (cellId) {
OPEN_CELL -> open(skill)
EDIT_CELL -> edit(skill)
DELETE_CELL -> remove(skill)
}
}
override fun searchPlaceholder() = KiloBundle.message("settings.agentBehavior.skills.search")
override fun emptyText() = KiloBundle.message("settings.agentBehavior.skills.empty")
internal fun updateSources(paths: List<String>, urls: List<String>) {
state.update { copy(sources = SkillsConfigDto(paths = paths, urls = urls)) }
sources.refresh(draft.sources)
}
override fun modified(): Boolean = state.modified()
override fun resetDraft() {
state.reset()
sources.refresh(draft.sources)
view.update(rows())
clearProgress()
}
override fun applyDraft() {
val token = state.start() ?: return
val fallback = skillFallback(token.target)
if (!launch("apply") { id ->
val target = token.target
var failed: String? = null
val behavior = service<KiloAgentBehaviorService>()
LOG.info("skills settings apply start dir=$dir edited=${target.edited.size} deleted=${target.deleted.size} paths=${target.sources.paths.size} urls=${target.sources.urls.size}")
if (target.edited.isNotEmpty() && !behavior.saveSkills(dir, target.edited)) {
failed = KiloBundle.message("settings.agentBehavior.save.failed")
}
if (failed == null) {
for (location in target.deleted) {
if (!behavior.removeSkill(dir, location)) {
failed = KiloBundle.message("settings.agentBehavior.skills.delete.failed")
break
}
}
}
if (failed == null && target.sources != token.previous.sources) {
val patch = ConfigPatchDto(skills = SkillsPatchDto(paths = target.sources.paths, urls = target.sources.urls))
if (app.updateConfig(patch) == null) failed = KiloBundle.message("settings.agentBehavior.save.failed")
}
val reloaded = if (failed == null) behavior.reloadSkills(dir) else true
val items = behavior.refreshSkills(dir, fallback)
withContext(edt) {
if (!active(id)) {
if (failed == null) KiloNotifications.info(KiloBundle.message("settings.agentBehavior.skills.saved.notification"))
else KiloNotifications.error(failed)
return@withContext
}
if (failed == null) {
skills = items.associateBy { key(it) }
val next = skillsDraft(config())
state.complete(token, next)
sources.refresh(draft.sources)
view.update(rows(items))
if (reloaded) clearProgress() else showProgress(KiloBundle.message("settings.agentBehavior.skills.reload.blocked"))
LOG.info("skills settings apply succeeded dir=$dir")
} else {
state.fail(token, failed)
sources.refresh(draft.sources)
view.update(rows(items))
showError(failed)
LOG.warn("skills settings apply failed dir=$dir message=$failed")
}
setBusy(false)
}
}) return
showProgress(KiloBundle.message("settings.agentBehavior.saving"))
}
private fun skillScroll() = JBScrollPane(view).apply {
border = null
horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
}
private fun rows(items: List<SkillDto> = skills.values.toList()): List<SettingsListItem> = items.mapNotNull { skill ->
if (skill.location in draft.deleted) return@mapNotNull null
item(skill)
}
private fun skillFallback(target: SkillsDraft): List<SkillDto> = skills.values.mapNotNull { skill ->
if (skill.location in target.deleted) return@mapNotNull null
target.edited[skill.location]?.let { skill.copy(content = it) } ?: skill
}
private fun item(skill: SkillDto) = object : SettingsListItem {
override val key = key(skill)
override val title = skill.name
override val note = skill.location.takeUnless { builtin(it) }
override val description = skill.description
override val doubleClick = EDIT_CELL
override val badges = listOf(
SettingsBadge(KiloBundle.message("settings.agentBehavior.badge.builtin"), UiStyle.Badge.Secondary),
).takeIf { builtin(skill.location) } ?: emptyList()
override val cells = listOfNotNull(
SettingsListCell(
OPEN_CELL,
KiloBundle.message("settings.agentBehavior.skills.openInEditor"),
primary = true,
).takeIf { skill.editable },
SettingsListCell(
EDIT_CELL,
KiloBundle.message(if (skill.editable) "settings.agentBehavior.edit" else "common.open"),
primary = !skill.editable,
),
SettingsListCell(
DELETE_CELL,
KiloBundle.message("common.delete"),
icon = AllIcons.Actions.GC,
iconOnly = true,
).takeIf { skill.editable },
)
}
private fun edit(skill: SkillDto) {
val current = skill.copy(content = content(skill))
val dialog = edit(current, skill.editable)
if (!skill.editable) {
dialog.showAndGet()
return
}
if (!dialog.showAndGet()) return
state.update { copy(edited = edited + (skill.location to dialog.content())) }
view.update(rows(), SettingsListSelection.Key(key(skill)))
}
private fun open(skill: SkillDto) {
if (!skill.editable) return
showProgress(KiloBundle.message("settings.agentBehavior.skills.openInEditor.pending"))
cs.launch {
val opened = service<KiloWorkspaceService>().openFile(skill.location)
if (opened) return@launch
withContext(edt) { KiloNotifications.error(KiloBundle.message("settings.agentBehavior.skills.openInEditor.failed")) }
}
}
private fun remove(skill: SkillDto) {
val result = Messages.showYesNoDialog(
KiloBundle.message("settings.agentBehavior.skills.delete.message", skill.name),
KiloBundle.message("settings.agentBehavior.skills.delete.title"),
KiloBundle.message("common.delete"),
Messages.getCancelButton(),
Messages.getQuestionIcon(),
)
if (result != Messages.YES) return
state.update { copy(deleted = deleted + skill.location, edited = edited - skill.location) }
view.update(rows(), selectionIndex())
}
private fun content(skill: SkillDto) = draft.edited[skill.location] ?: skill.content
private fun config() = app.state.value.config?.skills ?: SkillsConfigDto()
private companion object {
const val EDIT_CELL = "edit"
const val OPEN_CELL = "open"
const val DELETE_CELL = "delete"
const val BUILTIN = "builtin"
const val LEGACY_BUILTIN = "<built-in>"
val LOG = KiloLog.create(SkillsSettingsUi::class.java)
fun key(skill: SkillDto) = skill.location.ifBlank { skill.name }
fun builtin(location: String) = location == BUILTIN || location == LEGACY_BUILTIN
}
}
internal interface SkillEditDialogHandle {
fun showAndGet(): Boolean
fun content(): String
}
private data class SkillsDraft(
val sources: SkillsConfigDto,
val edited: Map<String, String> = emptyMap(),
val deleted: Set<String> = emptySet(),
)
private fun skillsDraft(sources: SkillsConfigDto) = SkillsDraft(sources)
private fun saved(base: SkillsDraft, draft: SkillsDraft): Boolean = base == draft
internal class SkillEditDialog(private val skill: SkillDto, private val savable: Boolean) : DialogWrapper(true), SkillEditDialogHandle {
private val base = initial()
private val editor = SkillEditor(base, skill.location, savable)
init {
title = skill.name
setOKButtonText(CommonBundle.getOkButtonText())
setCancelButtonText(CommonBundle.getCloseButtonText())
init()
isOKActionEnabled = false
editor.document.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
isOKActionEnabled = savable && editor.text != base
}
})
}
override fun createCenterPanel(): JComponent = JBScrollPane(editor).apply {
viewportBorder = editorPad()
horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
preferredSize = JBUI.size(720, 520)
}
override fun createActions() = if (savable) arrayOf(okAction, cancelAction) else arrayOf(cancelAction)
override fun content() = editor.text
private fun initial() = skill.content?.takeIf { it.isNotBlank() }
?: skill.description?.takeIf { it.isNotBlank() }
?: KiloBundle.message("settings.agentBehavior.skills.content.empty")
private class SkillEditor(value: String, location: String, editable: Boolean) : EditorTextField(
EditorFactory.getInstance().createDocument(value),
ProjectManager.getInstance().defaultProject,
skillFileType(location, value),
false,
!editable,
) {
init {
border = JBUI.Borders.empty()
setOneLineMode(false)
addSettingsProvider { ed ->
ed.setBorder(JBUI.Borders.empty())
ed.scrollPane.border = JBUI.Borders.empty()
ed.scrollPane.viewportBorder = JBUI.Borders.empty()
ed.settings.isUseSoftWraps = true
ed.settings.isPaintSoftWraps = false
ed.settings.isAdditionalPageAtBottom = false
ed.scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
ed.scrollPane.verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
}
}
}
}
internal class SkillSourcesView(
private val parent: SkillsSettingsUi,
private val choose: (JComponent) -> String?,
private val input: (String, String) -> String?,
) : Stack(ai.kilocode.client.ui.layout.StackAxis.VERTICAL, UiStyle.Gap.sm()) {
private val view = SettingsListView(
KiloBundle.message("settings.agentBehavior.skills.sources.empty"),
SettingsListConfig.Preferred.copy(description = false, selection = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION),
) { key, id ->
if (id == EDIT_CELL) edit(key)
}
private var cfg = SkillsConfigDto()
internal fun sourceList() = view.list
init {
border = JBUI.Borders.empty(UiStyle.Gap.pad(), 0, 0, 0)
next(TitledSeparator(KiloBundle.message("settings.agentBehavior.skills.sources.title")))
next(toolbar())
next(JBScrollPane(view).apply {
border = null
horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
preferredSize = JBUI.size(0, JBUI.scale(160))
maximumSize = JBUI.size(Int.MAX_VALUE, JBUI.scale(160))
})
}
fun refresh(config: SkillsConfigDto) {
cfg = config
view.update(rows(config))
}
private fun toolbar(): JComponent {
val add = DefaultActionGroup(KiloBundle.message("settings.agentBehavior.skills.sources.add"), true).apply {
templatePresentation.icon = AllIcons.General.Add
add(AddPathAction())
add(AddUrlAction())
}
val group = DefaultActionGroup(add, RemoveAction())
val toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLBAR, group, true)
toolbar.targetComponent = this
toolbar.updateActionsImmediately()
return toolbar.component
}
internal fun addPath() {
val path = choose(parent)?.trim()?.takeIf { it.isNotBlank() } ?: return
if (path in cfg.paths) return
parent.updateSources(cfg.paths + path, cfg.urls)
}
internal fun addUrl() {
val url = input(
KiloBundle.message("settings.agentBehavior.skills.sources.addUrl.title"),
KiloBundle.message("settings.agentBehavior.skills.sources.addUrl.prompt"),
)?.trim()?.takeIf { it.isNotBlank() } ?: return
if (url in cfg.urls) return
parent.updateSources(cfg.paths, cfg.urls + url)
}
private fun rows(config: SkillsConfigDto): List<SettingsListItem> {
val paths = config.paths.map { source(PATH_PREFIX, it) }
val urls = config.urls.map { source(URL_PREFIX, it) }
return paths + urls
}
private fun source(prefix: String, value: String) = object : SettingsListItem {
override val key = prefix + value
override val title = value
override val doubleClick = EDIT_CELL
}
internal fun removeSelected() {
val keys = view.selectedItems().map { it.key }.toSet()
if (keys.isEmpty()) return
val paths = cfg.paths.filterNot { PATH_PREFIX + it in keys }
val urls = cfg.urls.filterNot { URL_PREFIX + it in keys }
parent.updateSources(paths, urls)
}
private fun edit(key: String) {
val path = key.startsWith(PATH_PREFIX)
val old = key.removePrefix(if (path) PATH_PREFIX else URL_PREFIX)
val dialog = SkillSourceDialog(old, path, choose)
if (!dialog.showAndGet()) return
val next = dialog.value().trim().takeIf { it.isNotBlank() } ?: return
if (path) {
parent.updateSources(cfg.paths.map { if (it == old) next else it }.distinct(), cfg.urls)
return
}
parent.updateSources(cfg.paths, cfg.urls.map { if (it == old) next else it }.distinct())
}
private inner class AddPathAction : DumbAwareAction(
KiloBundle.message("settings.agentBehavior.skills.sources.addPath"),
null,
null,
) {
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun actionPerformed(e: AnActionEvent) = addPath()
}
private inner class AddUrlAction : DumbAwareAction(
KiloBundle.message("settings.agentBehavior.skills.sources.addUrl"),
null,
null,
) {
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun actionPerformed(e: AnActionEvent) = addUrl()
}
private inner class RemoveAction : DumbAwareAction(
KiloBundle.message("common.delete"),
null,
AllIcons.General.Remove,
) {
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun update(e: AnActionEvent) {
e.presentation.isEnabled = view.selectedItems().isNotEmpty()
}
override fun actionPerformed(e: AnActionEvent) = removeSelected()
}
private companion object {
const val EDIT_CELL = "edit"
const val PATH_PREFIX = "path:"
const val URL_PREFIX = "url:"
}
}
private class SkillSourceDialog(
value: String,
private val path: Boolean,
private val choose: (JComponent) -> String?,
) : DialogWrapper(true) {
private val field = JBTextField(value)
init {
title = if (path) KiloBundle.message("settings.agentBehavior.skills.sources.editPath.title")
else KiloBundle.message("settings.agentBehavior.skills.sources.editUrl.title")
setOKButtonText(KiloBundle.message("common.save"))
init()
}
override fun createCenterPanel(): JComponent {
if (!path) return field.apply { columns = SOURCE_COLUMNS }
return JPanel(BorderLayout(UiStyle.Gap.sm(), 0)).apply {
add(field.apply { columns = SOURCE_COLUMNS }, BorderLayout.CENTER)
add(JButton("...").apply {
addActionListener {
choose(this)?.let { field.text = it }
}
}, BorderLayout.EAST)
}
}
fun value() = field.text
}
private fun chooseSkillPath(parent: JComponent): String? {
return FileChooser.chooseFile(skillPathDescriptor(), parent, null, null as VirtualFile?)?.path
}
internal fun skillPathDescriptor() = FileChooserDescriptor(false, true, false, false, false, false).apply {
title = KiloBundle.message("settings.agentBehavior.skills.sources.addPath.title")
description = KiloBundle.message("settings.agentBehavior.skills.sources.addPath.prompt")
}
internal fun skillFileType(location: String, content: String? = null): FileType {
val syntax = content?.syntaxName()
val name = syntax ?: location.substringAfterLast('/').substringAfterLast('\\').ifBlank { SKILL_FILE }
val type = FileTypeManager.getInstance().getFileTypeByFileName(name)
if (type == UnknownFileType.INSTANCE) return PlainTextFileType.INSTANCE
return type
}
private fun String.syntaxName(): String? {
val text = trimStart()
if (text.isBlank()) return null
if (text.looksHtml()) return "index.html"
if (text.looksMarkdown()) return SKILL_FILE
return null
}
private fun String.looksHtml() = contains(Regex("^\\s*(<!doctype\\s+html|<html\\b|<body\\b|</?(h[1-6]|p|pre|code|ul|ol|li|blockquote|br)\\b)", RegexOption.IGNORE_CASE))
private fun String.looksMarkdown() = lineSequence().any { line ->
line.matches(Regex("\\s{0,3}(#{1,6}\\s+.+|[-*+]\\s+.+|\\d+\\.\\s+.+|```.*|>\\s+.+)")) ||
line.contains(Regex("(`[^`]+`|\\[[^]]+][(][^)]+[)])"))
}
private fun inputSkillUrl(title: String, prompt: String): String? = Messages.showInputDialog(
prompt,
title,
Messages.getQuestionIcon(),
)
private fun editorPad() = JBUI.Borders.empty(
JBUI.scale(SessionUiStyle.View.Prompt.SHELL_VERTICAL_PADDING),
JBUI.scale(SessionUiStyle.View.Prompt.SHELL_HORIZONTAL_PADDING),
JBUI.scale(SessionUiStyle.View.Prompt.SHELL_VERTICAL_PADDING),
JBUI.scale(SessionUiStyle.View.Prompt.SHELL_HORIZONTAL_PADDING),
)
private const val SOURCE_COLUMNS = 60
private const val SKILL_FILE = "SKILL.md"
private const val SKILL_LOAD_TIMEOUT_MS = 10_000L
@@ -8,6 +8,7 @@ import java.awt.Point
import java.awt.Rectangle
import javax.swing.Icon
import javax.swing.JList
import javax.swing.ListSelectionModel
import javax.swing.ListCellRenderer
import javax.swing.SwingUtilities
@@ -21,6 +22,8 @@ internal data class SettingsListConfig(
val height: SettingsListRowHeight,
val description: Boolean = true,
val descriptionIndent: Boolean = true,
val tooltip: Boolean = true,
val selection: Int = ListSelectionModel.SINGLE_SELECTION,
) {
companion object {
val Equal = SettingsListConfig(SettingsListRowHeight.EQUAL)
@@ -41,7 +44,9 @@ internal data class SettingsListCell(
internal interface SettingsListItem {
val key: String
val title: String
val note: String? get() = null
val description: String? get() = null
val doubleClick: String? get() = null
val icon: Icon? get() = null
val section: String? get() = null
val badges: List<SettingsBadge> get() = emptyList()
@@ -83,6 +83,9 @@ internal class SettingsListRenderer(
title.clear()
title.append(value.title, SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, fg))
value.note?.takeIf { it.isNotBlank() }?.let {
title.append(" $it", SimpleTextAttributes.GRAYED_ATTRIBUTES)
}
syncBadges(value)
icon.icon = value.icon
mark.isVisible = value.icon != null
@@ -1,6 +1,7 @@
package ai.kilocode.client.settings.base
import ai.kilocode.client.session.ui.model.ModelSearch
import ai.kilocode.client.ui.UiStyle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.JBPopupListener
@@ -10,6 +11,8 @@ import com.intellij.ui.ScrollingUtil
import com.intellij.ui.components.JBList
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.UIUtil
import java.awt.Dimension
import java.awt.Rectangle
import java.awt.event.KeyEvent
import java.awt.event.FocusAdapter
import java.awt.event.FocusEvent
@@ -18,18 +21,20 @@ import java.awt.event.MouseEvent
import javax.swing.JComponent
import javax.swing.KeyStroke
import javax.swing.ListSelectionModel
import javax.swing.Scrollable
import javax.swing.SwingConstants
import javax.swing.event.ListSelectionEvent
internal class SettingsListView(
empty: String,
private val cfg: SettingsListConfig = SettingsListConfig.Equal,
private val onCell: (String, String) -> Unit,
) : BaseContentPanel() {
) : BaseContentPanel(), Scrollable {
private val model = CollectionListModel<SettingsListItem>()
internal val list: JBList<SettingsListItem> = object : JBList<SettingsListItem>(model), SettingsListActive {
override fun active(): Boolean = popups > 0
}.apply {
selectionMode = ListSelectionModel.SINGLE_SELECTION
selectionMode = cfg.selection
setExpandableItemsEnabled(false)
emptyText.text = empty
}
@@ -64,6 +69,11 @@ internal class SettingsListView(
val hit = hit(e, enabled = false) ?: return
if (hit.id != null) return
val item = hit.item
item.doubleClick?.let { id ->
onCell(item.key, id)
e.consume()
return
}
primary(item)
e.consume()
}
@@ -96,6 +106,12 @@ internal class SettingsListView(
return list.selectedValue
}
@RequiresEdt
fun selectedItems(): List<SettingsListItem> {
checkEdt()
return list.selectedValuesList
}
@RequiresEdt
fun selectedIndex(): Int {
checkEdt()
@@ -125,6 +141,7 @@ internal class SettingsListView(
@RequiresEdt
fun setBusy(value: Boolean) {
checkEdt()
list.setPaintBusy(value)
if (list.isEnabled == !value) return
list.isEnabled = !value
list.repaint()
@@ -218,9 +235,15 @@ internal class SettingsListView(
private fun primary(item: SettingsListItem) {
val cells = settingsListVisibleCells(item, true)
val cell = cells.firstOrNull { it.enabled && it.primary }
?: cells.firstOrNull { it.enabled }
?: return
onCell(item.key, cell.id)
if (cell != null) {
onCell(item.key, cell.id)
return
}
item.doubleClick?.let { id ->
onCell(item.key, id)
return
}
cells.firstOrNull { it.enabled }?.let { onCell(item.key, it.id) }
}
private fun hit(e: MouseEvent, enabled: Boolean = true): Hit? {
@@ -228,7 +251,7 @@ internal class SettingsListView(
val bounds = idx.takeIf { it >= 0 }?.let { list.getCellBounds(it, it) } ?: return null
if (!bounds.contains(e.point)) return null
val item = model.getElementAt(idx)
val selected = idx == list.selectedIndex
val selected = list.isSelectedIndex(idx)
val id = if (enabled) {
settingsListCellAt(list, idx, e.point, selected)
} else {
@@ -244,6 +267,27 @@ internal class SettingsListView(
check(ApplicationManager.getApplication().isDispatchThread) { "Settings list updates must run on EDT" }
}
override fun getScrollableTracksViewportWidth() = true
override fun getScrollableTracksViewportHeight() = false
override fun getPreferredScrollableViewportSize(): Dimension = preferredSize
override fun getScrollableUnitIncrement(
visibleRect: Rectangle,
orientation: Int,
direction: Int,
): Int {
if (orientation != SwingConstants.VERTICAL) return UiStyle.Gap.pad()
return list.fixedCellHeight.takeIf { it > 0 } ?: UiStyle.Gap.xl()
}
override fun getScrollableBlockIncrement(
visibleRect: Rectangle,
orientation: Int,
direction: Int,
) = if (orientation == SwingConstants.VERTICAL) visibleRect.height else visibleRect.width
private data class Hit(val item: SettingsListItem, val id: String?)
private data class Press(val key: String, val id: String)
@@ -59,6 +59,14 @@ internal open class SettingsPanel(scroll: Boolean = true, pad: Boolean = true) :
content.add(header, BorderLayout.NORTH)
}
protected fun setCenter(component: JComponent) {
val layout = content.layout as? BorderLayout
layout?.getLayoutComponent(BorderLayout.CENTER)?.let { content.remove(it) }
content.add(component, BorderLayout.CENTER)
revalidate()
repaint()
}
}
private class SettingsBody : Stack(StackAxis.VERTICAL), Scrollable {
@@ -92,6 +92,13 @@
bundle="messages.KiloBundle"
key="settings.agentBehavior.mcp.displayName"/>
<applicationConfigurable
parentId="ai.kilocode.jetbrains.settings.agentBehavior"
id="ai.kilocode.jetbrains.settings.agentBehavior.skills"
instance="ai.kilocode.client.settings.agents.SkillsConfigurable"
bundle="messages.KiloBundle"
key="settings.agentBehavior.skills.displayName"/>
<registryKey key="kilo.session.condense"
description="Enable event condensing in the session update queue (merges redundant snapshots before model delivery)."
defaultValue="true"
@@ -1,4 +1,6 @@
common.delete=Delete
common.open=Open
common.save=Save
session.action.cancel=Cancel
session.connection.connecting=Loading...
@@ -507,6 +509,31 @@ settings.agentBehavior.mcp.status.failed=failed
settings.agentBehavior.mcp.status.needsAuth=needs auth
settings.agentBehavior.mcp.status.needsRegistration=needs registration
settings.agentBehavior.mcp.status.disabled=disabled
settings.agentBehavior.skills.displayName=Skills
settings.agentBehavior.skills.search=Filter skills
settings.agentBehavior.skills.empty=No skills found.
settings.agentBehavior.skills.content.empty=No skill content available.
settings.agentBehavior.skills.load.timeout=Skill loading timed out. Existing skills were kept; remove slow or unreachable URLs and refresh.
settings.agentBehavior.skills.reload.deferred=Skills source saved. Reload the core after active sessions finish to apply new skills.
settings.agentBehavior.skills.reload.blocked=Skills settings saved, but active sessions are present. Reload the core after those sessions finish to apply the new skills.
settings.agentBehavior.skills.saved.notification=Skills settings saved
settings.agentBehavior.skills.delete.title=Delete Skill
settings.agentBehavior.skills.delete.message=Delete skill {0}? This removes the skill file and cannot be undone.
settings.agentBehavior.skills.delete.failed=Could not delete the skill.
settings.agentBehavior.skills.openInEditor=Open in Editor
settings.agentBehavior.skills.openInEditor.pending=The skill file will open after you close Settings.
settings.agentBehavior.skills.openInEditor.failed=Could not open the skill file in the editor.
settings.agentBehavior.skills.sources.empty=No skill sources configured.
settings.agentBehavior.skills.sources.title=Additional Skill Sources
settings.agentBehavior.skills.sources.add=Add
settings.agentBehavior.skills.sources.addPath=Add path
settings.agentBehavior.skills.sources.addPath.title=Add Skill Path
settings.agentBehavior.skills.sources.addPath.prompt=Choose a folder containing Kilo skills.
settings.agentBehavior.skills.sources.addUrl=Add URL
settings.agentBehavior.skills.sources.addUrl.title=Add Skill URL
settings.agentBehavior.skills.sources.addUrl.prompt=Enter a skill source URL.
settings.agentBehavior.skills.sources.editPath.title=Edit Skill Path
settings.agentBehavior.skills.sources.editUrl.title=Edit Skill URL
settings.providers.loading=Loading providers...
settings.providers.connected=Connected providers
settings.providers.available=Available providers
@@ -1,4 +1,6 @@
common.delete=حذف
common.open=فتح
common.save=حفظ
session.action.cancel=إلغاء
session.connection.connecting=جاري التحميل…
session.connection.error.app=فشل الاتصال
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=لإضافة خادم MCP، اطلب من الوكيل إضافته.
settings.agentBehavior.skills.displayName=المهارات
settings.agentBehavior.skills.search=تصفية المهارات
settings.agentBehavior.skills.empty=لم يتم العثور على مهارات.
settings.agentBehavior.skills.content.empty=لا يوجد محتوى مهارة متاح.
settings.agentBehavior.skills.load.timeout=انتهت مهلة تحميل المهارات. تم الاحتفاظ بالمهارات الحالية؛ أزل عناوين URL البطيئة أو غير المتاحة ثم حدّث.
settings.agentBehavior.skills.reload.deferred=تم حفظ مصدر المهارات. أعد تحميل Core بعد انتهاء الجلسات النشطة لتطبيق المهارات الجديدة.
settings.agentBehavior.skills.reload.blocked=تم حفظ إعدادات المهارات، لكن توجد جلسات نشطة. أعد تحميل Core بعد انتهاء تلك الجلسات لتطبيق المهارات الجديدة.
settings.agentBehavior.skills.saved.notification=تم حفظ إعدادات المهارات
settings.agentBehavior.skills.delete.title=حذف المهارة
settings.agentBehavior.skills.delete.message=هل تريد حذف المهارة {0}؟ سيؤدي ذلك إلى إزالة ملف المهارة ولا يمكن التراجع عنه.
settings.agentBehavior.skills.delete.failed=تعذر حذف المهارة.
settings.agentBehavior.skills.openInEditor=فتح في المحرر
settings.agentBehavior.skills.openInEditor.pending=سيتم فتح ملف المهارة بعد إغلاق الإعدادات.
settings.agentBehavior.skills.openInEditor.failed=تعذر فتح ملف المهارة في المحرر.
settings.agentBehavior.skills.sources.empty=لم يتم تكوين مصادر مهارات.
settings.agentBehavior.skills.sources.title=مصادر مهارات إضافية
settings.agentBehavior.skills.sources.add=إضافة
settings.agentBehavior.skills.sources.addPath=إضافة مسار
settings.agentBehavior.skills.sources.addPath.title=إضافة مسار مهارات
settings.agentBehavior.skills.sources.addPath.prompt=اختر مجلداً يحتوي على مهارات Kilo.
settings.agentBehavior.skills.sources.addUrl=إضافة URL
settings.agentBehavior.skills.sources.addUrl.title=إضافة URL لمصدر مهارات
settings.agentBehavior.skills.sources.addUrl.prompt=أدخل URL لمصدر مهارات.
settings.agentBehavior.skills.sources.editPath.title=تعديل مسار المهارات
settings.agentBehavior.skills.sources.editUrl.title=تعديل URL المهارات
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one=تم التراجع عن رسالة واحدة
@@ -1,4 +1,6 @@
common.delete=Obriši
common.open=Otvori
common.save=Sačuvaj
session.action.cancel=Otkaži
session.connection.connecting=Učitavanje…
session.connection.error.app=Greška pri spajanju
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Da dodate MCP server, zamolite agenta da ga doda.
settings.agentBehavior.skills.displayName=Vještine
settings.agentBehavior.skills.search=Filtriraj vještine
settings.agentBehavior.skills.empty=Nema pronađenih vještina.
settings.agentBehavior.skills.content.empty=Nema dostupnog sadržaja vještine.
settings.agentBehavior.skills.load.timeout=Učitavanje vještina je isteklo. Postojeće vještine su zadržane; uklonite spore ili nedostupne URL-ove i osvježite.
settings.agentBehavior.skills.reload.deferred=Izvor vještina je sačuvan. Ponovo učitajte Core nakon što aktivne sesije završe da primijenite nove vještine.
settings.agentBehavior.skills.reload.blocked=Postavke vještina su sačuvane, ali postoje aktivne sesije. Ponovo učitajte Core nakon što te sesije završe da primijenite nove vještine.
settings.agentBehavior.skills.saved.notification=Postavke vještina su sačuvane
settings.agentBehavior.skills.delete.title=Izbriši vještinu
settings.agentBehavior.skills.delete.message=Izbrisati vještinu {0}? Ovo uklanja datoteku vještine i ne može se poništiti.
settings.agentBehavior.skills.delete.failed=Nije moguće izbrisati vještinu.
settings.agentBehavior.skills.openInEditor=Otvori u editoru
settings.agentBehavior.skills.openInEditor.pending=Datoteka vještine će se otvoriti nakon što zatvorite Postavke.
settings.agentBehavior.skills.openInEditor.failed=Nije moguće otvoriti datoteku vještine u editoru.
settings.agentBehavior.skills.sources.empty=Nema konfigurisanih izvora vještina.
settings.agentBehavior.skills.sources.title=Dodatni izvori vještina
settings.agentBehavior.skills.sources.add=Dodaj
settings.agentBehavior.skills.sources.addPath=Dodaj putanju
settings.agentBehavior.skills.sources.addPath.title=Dodaj putanju vještina
settings.agentBehavior.skills.sources.addPath.prompt=Odaberite folder koji sadrži Kilo vještine.
settings.agentBehavior.skills.sources.addUrl=Dodaj URL
settings.agentBehavior.skills.sources.addUrl.title=Dodaj URL izvora vještina
settings.agentBehavior.skills.sources.addUrl.prompt=Unesite URL izvora vještina.
settings.agentBehavior.skills.sources.editPath.title=Uredi putanju vještina
settings.agentBehavior.skills.sources.editUrl.title=Uredi URL vještina
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} poruka vraćena
@@ -1,4 +1,6 @@
common.delete=Slet
common.open=Åbn
common.save=Gem
session.action.cancel=Annuller
session.connection.connecting=Indlæser…
session.connection.error.app=Forbindelsesfejl
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=For at tilføje en MCP-server skal du bede agenten om at gøre det.
settings.agentBehavior.skills.displayName=Færdigheder
settings.agentBehavior.skills.search=Filtrer færdigheder
settings.agentBehavior.skills.empty=Ingen færdigheder fundet.
settings.agentBehavior.skills.content.empty=Intet færdighedsindhold tilgængeligt.
settings.agentBehavior.skills.load.timeout=Indlæsning af færdigheder fik timeout. Eksisterende færdigheder blev bevaret; fjern langsomme eller utilgængelige URL'er og opdater.
settings.agentBehavior.skills.reload.deferred=Færdighedskilden blev gemt. Genindlæs Core, når aktive sessioner er afsluttet, for at anvende nye færdigheder.
settings.agentBehavior.skills.reload.blocked=Færdighedsindstillingerne blev gemt, men der er aktive sessioner. Genindlæs Core, når disse sessioner er afsluttet, for at anvende de nye færdigheder.
settings.agentBehavior.skills.saved.notification=Færdighedsindstillinger gemt
settings.agentBehavior.skills.delete.title=Slet færdighed
settings.agentBehavior.skills.delete.message=Slet færdigheden {0}? Dette fjerner færdighedsfilen og kan ikke fortrydes.
settings.agentBehavior.skills.delete.failed=Kunne ikke slette færdigheden.
settings.agentBehavior.skills.openInEditor=Åbn i editor
settings.agentBehavior.skills.openInEditor.pending=Færdighedsfilen åbnes, når du lukker Indstillinger.
settings.agentBehavior.skills.openInEditor.failed=Kunne ikke åbne færdighedsfilen i editoren.
settings.agentBehavior.skills.sources.empty=Ingen færdighedskilder konfigureret.
settings.agentBehavior.skills.sources.title=Yderligere færdighedskilder
settings.agentBehavior.skills.sources.add=Tilføj
settings.agentBehavior.skills.sources.addPath=Tilføj sti
settings.agentBehavior.skills.sources.addPath.title=Tilføj færdighedssti
settings.agentBehavior.skills.sources.addPath.prompt=Vælg en mappe, der indeholder Kilo-færdigheder.
settings.agentBehavior.skills.sources.addUrl=Tilføj URL
settings.agentBehavior.skills.sources.addUrl.title=Tilføj URL til færdighedskilde
settings.agentBehavior.skills.sources.addUrl.prompt=Indtast en URL til en færdighedskilde.
settings.agentBehavior.skills.sources.editPath.title=Rediger færdighedssti
settings.agentBehavior.skills.sources.editUrl.title=Rediger færdigheds-URL
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} besked rullet tilbage
@@ -1,4 +1,6 @@
common.delete=Löschen
common.open=Öffnen
common.save=Speichern
session.action.cancel=Abbrechen
session.connection.connecting=Wird geladen…
session.connection.error.app=Verbindung fehlgeschlagen
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Um einen MCP-Server hinzuzufügen, bitten Sie den Agenten darum.
settings.agentBehavior.skills.displayName=Skills
settings.agentBehavior.skills.search=Skills filtern
settings.agentBehavior.skills.empty=Keine Skills gefunden.
settings.agentBehavior.skills.content.empty=Keine Skill-Inhalte verfügbar.
settings.agentBehavior.skills.load.timeout=Das Laden der Skills ist abgelaufen. Vorhandene Skills wurden beibehalten; entfernen Sie langsame oder nicht erreichbare URLs und aktualisieren Sie.
settings.agentBehavior.skills.reload.deferred=Skill-Quelle gespeichert. Laden Sie Core neu, nachdem aktive Sitzungen beendet sind, um neue Skills anzuwenden.
settings.agentBehavior.skills.reload.blocked=Skill-Einstellungen gespeichert, aber es sind aktive Sitzungen vorhanden. Laden Sie Core neu, nachdem diese Sitzungen beendet sind, um neue Skills anzuwenden.
settings.agentBehavior.skills.saved.notification=Skill-Einstellungen gespeichert
settings.agentBehavior.skills.delete.title=Skill löschen
settings.agentBehavior.skills.delete.message=Skill {0} löschen? Dadurch wird die Skill-Datei entfernt und kann nicht rückgängig gemacht werden.
settings.agentBehavior.skills.delete.failed=Der Skill konnte nicht gelöscht werden.
settings.agentBehavior.skills.openInEditor=Im Editor öffnen
settings.agentBehavior.skills.openInEditor.pending=Die Skill-Datei wird geöffnet, nachdem Sie die Einstellungen geschlossen haben.
settings.agentBehavior.skills.openInEditor.failed=Die Skill-Datei konnte nicht im Editor geöffnet werden.
settings.agentBehavior.skills.sources.empty=Keine Skill-Quellen konfiguriert.
settings.agentBehavior.skills.sources.title=Zusätzliche Skill-Quellen
settings.agentBehavior.skills.sources.add=Hinzufügen
settings.agentBehavior.skills.sources.addPath=Pfad hinzufügen
settings.agentBehavior.skills.sources.addPath.title=Skill-Pfad hinzufügen
settings.agentBehavior.skills.sources.addPath.prompt=Wählen Sie einen Ordner mit Kilo-Skills aus.
settings.agentBehavior.skills.sources.addUrl=URL hinzufügen
settings.agentBehavior.skills.sources.addUrl.title=Skill-Quellen-URL hinzufügen
settings.agentBehavior.skills.sources.addUrl.prompt=Geben Sie eine Skill-Quellen-URL ein.
settings.agentBehavior.skills.sources.editPath.title=Skill-Pfad bearbeiten
settings.agentBehavior.skills.sources.editUrl.title=Skill-URL bearbeiten
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} Nachricht zurückgesetzt
@@ -1,4 +1,6 @@
common.delete=Eliminar
common.open=Abrir
common.save=Guardar
session.action.cancel=Cancelar
session.connection.connecting=Cargando…
session.connection.error.app=Error de conexión
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Para agregar un servidor MCP, pídele al agente que lo haga.
settings.agentBehavior.skills.displayName=Habilidades
settings.agentBehavior.skills.search=Filtrar habilidades
settings.agentBehavior.skills.empty=No se encontraron habilidades.
settings.agentBehavior.skills.content.empty=No hay contenido de habilidad disponible.
settings.agentBehavior.skills.load.timeout=Se agotó el tiempo de carga de habilidades. Se conservaron las habilidades existentes; elimina las URL lentas o inaccesibles y actualiza.
settings.agentBehavior.skills.reload.deferred=Fuente de habilidades guardada. Recarga Core cuando terminen las sesiones activas para aplicar nuevas habilidades.
settings.agentBehavior.skills.reload.blocked=Configuración de habilidades guardada, pero hay sesiones activas. Recarga Core cuando esas sesiones terminen para aplicar las nuevas habilidades.
settings.agentBehavior.skills.saved.notification=Configuración de habilidades guardada
settings.agentBehavior.skills.delete.title=Eliminar habilidad
settings.agentBehavior.skills.delete.message=¿Eliminar la habilidad {0}? Esto elimina el archivo de la habilidad y no se puede deshacer.
settings.agentBehavior.skills.delete.failed=No se pudo eliminar la habilidad.
settings.agentBehavior.skills.openInEditor=Abrir en el editor
settings.agentBehavior.skills.openInEditor.pending=El archivo de la habilidad se abrirá después de cerrar Configuración.
settings.agentBehavior.skills.openInEditor.failed=No se pudo abrir el archivo de la habilidad en el editor.
settings.agentBehavior.skills.sources.empty=No hay fuentes de habilidades configuradas.
settings.agentBehavior.skills.sources.title=Fuentes de habilidades adicionales
settings.agentBehavior.skills.sources.add=Agregar
settings.agentBehavior.skills.sources.addPath=Agregar ruta
settings.agentBehavior.skills.sources.addPath.title=Agregar ruta de habilidades
settings.agentBehavior.skills.sources.addPath.prompt=Elige una carpeta que contenga habilidades de Kilo.
settings.agentBehavior.skills.sources.addUrl=Agregar URL
settings.agentBehavior.skills.sources.addUrl.title=Agregar URL de fuente de habilidades
settings.agentBehavior.skills.sources.addUrl.prompt=Introduce una URL de fuente de habilidades.
settings.agentBehavior.skills.sources.editPath.title=Editar ruta de habilidades
settings.agentBehavior.skills.sources.editUrl.title=Editar URL de habilidades
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} mensaje revertido
@@ -1,4 +1,6 @@
common.delete=Supprimer
common.open=Ouvrir
common.save=Enregistrer
session.action.cancel=Annuler
session.connection.connecting=Chargement…
session.connection.error.app=Échec de la connexion
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Pour ajouter un serveur MCP, demandez à lagent de le faire.
settings.agentBehavior.skills.displayName=Compétences
settings.agentBehavior.skills.search=Filtrer les compétences
settings.agentBehavior.skills.empty=Aucune compétence trouvée.
settings.agentBehavior.skills.content.empty=Aucun contenu de compétence disponible.
settings.agentBehavior.skills.load.timeout=Le chargement des compétences a expiré. Les compétences existantes ont été conservées ; supprimez les URL lentes ou inaccessibles puis actualisez.
settings.agentBehavior.skills.reload.deferred=Source de compétences enregistrée. Rechargez Core après la fin des sessions actives pour appliquer les nouvelles compétences.
settings.agentBehavior.skills.reload.blocked=Paramètres des compétences enregistrés, mais des sessions sont actives. Rechargez Core après la fin de ces sessions pour appliquer les nouvelles compétences.
settings.agentBehavior.skills.saved.notification=Paramètres des compétences enregistrés
settings.agentBehavior.skills.delete.title=Supprimer la compétence
settings.agentBehavior.skills.delete.message=Supprimer la compétence {0} ? Cela supprime le fichier de compétence et ne peut pas être annulé.
settings.agentBehavior.skills.delete.failed=Impossible de supprimer la compétence.
settings.agentBehavior.skills.openInEditor=Ouvrir dans l’éditeur
settings.agentBehavior.skills.openInEditor.pending=Le fichier de compétence souvrira après la fermeture des paramètres.
settings.agentBehavior.skills.openInEditor.failed=Impossible douvrir le fichier de compétence dans l’éditeur.
settings.agentBehavior.skills.sources.empty=Aucune source de compétences configurée.
settings.agentBehavior.skills.sources.title=Sources de compétences supplémentaires
settings.agentBehavior.skills.sources.add=Ajouter
settings.agentBehavior.skills.sources.addPath=Ajouter un chemin
settings.agentBehavior.skills.sources.addPath.title=Ajouter un chemin de compétences
settings.agentBehavior.skills.sources.addPath.prompt=Choisissez un dossier contenant des compétences Kilo.
settings.agentBehavior.skills.sources.addUrl=Ajouter une URL
settings.agentBehavior.skills.sources.addUrl.title=Ajouter une URL de source de compétences
settings.agentBehavior.skills.sources.addUrl.prompt=Saisissez une URL de source de compétences.
settings.agentBehavior.skills.sources.editPath.title=Modifier le chemin des compétences
settings.agentBehavior.skills.sources.editUrl.title=Modifier lURL des compétences
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} message annulé
@@ -1,4 +1,6 @@
common.delete=削除
common.open=開く
common.save=保存
session.action.cancel=キャンセル
session.connection.connecting=読み込み中…
session.connection.error.app=接続に失敗しました
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=MCPサーバーを追加するには、エージェントに依頼してください。
settings.agentBehavior.skills.displayName=スキル
settings.agentBehavior.skills.search=スキルを絞り込み
settings.agentBehavior.skills.empty=スキルが見つかりません。
settings.agentBehavior.skills.content.empty=利用可能なスキル内容がありません。
settings.agentBehavior.skills.load.timeout=スキルの読み込みがタイムアウトしました。既存のスキルは保持されました。遅い、または到達不能な URL を削除して更新してください。
settings.agentBehavior.skills.reload.deferred=スキルソースを保存しました。新しいスキルを適用するには、アクティブなセッションが終了した後に Core を再読み込みしてください。
settings.agentBehavior.skills.reload.blocked=スキル設定を保存しましたが、アクティブなセッションがあります。新しいスキルを適用するには、それらのセッションが終了した後に Core を再読み込みしてください。
settings.agentBehavior.skills.saved.notification=スキル設定を保存しました
settings.agentBehavior.skills.delete.title=スキルを削除
settings.agentBehavior.skills.delete.message=スキル {0} を削除しますか?スキルファイルが削除され、この操作は元に戻せません。
settings.agentBehavior.skills.delete.failed=スキルを削除できませんでした。
settings.agentBehavior.skills.openInEditor=エディターで開く
settings.agentBehavior.skills.openInEditor.pending=設定を閉じるとスキルファイルが開きます。
settings.agentBehavior.skills.openInEditor.failed=エディターでスキルファイルを開けませんでした。
settings.agentBehavior.skills.sources.empty=スキルソースが設定されていません。
settings.agentBehavior.skills.sources.title=追加のスキルソース
settings.agentBehavior.skills.sources.add=追加
settings.agentBehavior.skills.sources.addPath=パスを追加
settings.agentBehavior.skills.sources.addPath.title=スキルパスを追加
settings.agentBehavior.skills.sources.addPath.prompt=Kilo スキルを含むフォルダーを選択してください。
settings.agentBehavior.skills.sources.addUrl=URL を追加
settings.agentBehavior.skills.sources.addUrl.title=スキルソース URL を追加
settings.agentBehavior.skills.sources.addUrl.prompt=スキルソース URL を入力してください。
settings.agentBehavior.skills.sources.editPath.title=スキルパスを編集
settings.agentBehavior.skills.sources.editUrl.title=スキル URL を編集
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} 件のメッセージをロールバックしました
@@ -1,4 +1,6 @@
common.delete=삭제
common.open=열기
common.save=저장
session.action.cancel=취소
session.connection.connecting=로딩 중…
session.connection.error.app=연결 실패
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=MCP 서버를 추가하려면 에이전트에게 요청하세요.
settings.agentBehavior.skills.displayName=스킬
settings.agentBehavior.skills.search=스킬 필터링
settings.agentBehavior.skills.empty=스킬을 찾을 수 없습니다.
settings.agentBehavior.skills.content.empty=사용 가능한 스킬 내용이 없습니다.
settings.agentBehavior.skills.load.timeout=스킬 로드 시간이 초과되었습니다. 기존 스킬은 유지되었습니다. 느리거나 연결할 수 없는 URL을 제거한 뒤 새로 고치세요.
settings.agentBehavior.skills.reload.deferred=스킬 소스가 저장되었습니다. 새 스킬을 적용하려면 활성 세션이 끝난 뒤 Core를 다시 로드하세요.
settings.agentBehavior.skills.reload.blocked=스킬 설정이 저장되었지만 활성 세션이 있습니다. 새 스킬을 적용하려면 해당 세션이 끝난 뒤 Core를 다시 로드하세요.
settings.agentBehavior.skills.saved.notification=스킬 설정이 저장되었습니다
settings.agentBehavior.skills.delete.title=스킬 삭제
settings.agentBehavior.skills.delete.message=스킬 {0}을 삭제할까요? 스킬 파일이 제거되며 되돌릴 수 없습니다.
settings.agentBehavior.skills.delete.failed=스킬을 삭제할 수 없습니다.
settings.agentBehavior.skills.openInEditor=에디터에서 열기
settings.agentBehavior.skills.openInEditor.pending=설정을 닫으면 스킬 파일이 열립니다.
settings.agentBehavior.skills.openInEditor.failed=에디터에서 스킬 파일을 열 수 없습니다.
settings.agentBehavior.skills.sources.empty=구성된 스킬 소스가 없습니다.
settings.agentBehavior.skills.sources.title=추가 스킬 소스
settings.agentBehavior.skills.sources.add=추가
settings.agentBehavior.skills.sources.addPath=경로 추가
settings.agentBehavior.skills.sources.addPath.title=스킬 경로 추가
settings.agentBehavior.skills.sources.addPath.prompt=Kilo 스킬이 포함된 폴더를 선택하세요.
settings.agentBehavior.skills.sources.addUrl=URL 추가
settings.agentBehavior.skills.sources.addUrl.title=스킬 소스 URL 추가
settings.agentBehavior.skills.sources.addUrl.prompt=스킬 소스 URL을 입력하세요.
settings.agentBehavior.skills.sources.editPath.title=스킬 경로 편집
settings.agentBehavior.skills.sources.editUrl.title=스킬 URL 편집
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0}개 메시지가 롤백됨
@@ -1,4 +1,6 @@
common.delete=Verwijderen
common.open=Openen
common.save=Opslaan
session.action.cancel=Annuleren
session.connection.connecting=Laden…
session.connection.error.app=Verbinding mislukt
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Vraag de agent om een MCP-server toe te voegen.
settings.agentBehavior.skills.displayName=Vaardigheden
settings.agentBehavior.skills.search=Vaardigheden filteren
settings.agentBehavior.skills.empty=Geen vaardigheden gevonden.
settings.agentBehavior.skills.content.empty=Geen vaardigheidsinhoud beschikbaar.
settings.agentBehavior.skills.load.timeout=Het laden van vaardigheden is verlopen. Bestaande vaardigheden zijn behouden; verwijder trage of onbereikbare URLs en vernieuw.
settings.agentBehavior.skills.reload.deferred=Vaardigheidsbron opgeslagen. Laad Core opnieuw nadat actieve sessies zijn voltooid om nieuwe vaardigheden toe te passen.
settings.agentBehavior.skills.reload.blocked=Vaardigheidsinstellingen opgeslagen, maar er zijn actieve sessies. Laad Core opnieuw nadat die sessies zijn voltooid om de nieuwe vaardigheden toe te passen.
settings.agentBehavior.skills.saved.notification=Vaardigheidsinstellingen opgeslagen
settings.agentBehavior.skills.delete.title=Vaardigheid verwijderen
settings.agentBehavior.skills.delete.message=Vaardigheid {0} verwijderen? Dit verwijdert het vaardigheidsbestand en kan niet ongedaan worden gemaakt.
settings.agentBehavior.skills.delete.failed=Kon de vaardigheid niet verwijderen.
settings.agentBehavior.skills.openInEditor=Openen in editor
settings.agentBehavior.skills.openInEditor.pending=Het vaardigheidsbestand wordt geopend nadat u Instellingen sluit.
settings.agentBehavior.skills.openInEditor.failed=Kon het vaardigheidsbestand niet openen in de editor.
settings.agentBehavior.skills.sources.empty=Geen vaardigheidsbronnen geconfigureerd.
settings.agentBehavior.skills.sources.title=Extra vaardigheidsbronnen
settings.agentBehavior.skills.sources.add=Toevoegen
settings.agentBehavior.skills.sources.addPath=Pad toevoegen
settings.agentBehavior.skills.sources.addPath.title=Vaardigheidspad toevoegen
settings.agentBehavior.skills.sources.addPath.prompt=Kies een map met Kilo-vaardigheden.
settings.agentBehavior.skills.sources.addUrl=URL toevoegen
settings.agentBehavior.skills.sources.addUrl.title=URL van vaardigheidsbron toevoegen
settings.agentBehavior.skills.sources.addUrl.prompt=Voer een URL van een vaardigheidsbron in.
settings.agentBehavior.skills.sources.editPath.title=Vaardigheidspad bewerken
settings.agentBehavior.skills.sources.editUrl.title=Vaardigheids-URL bewerken
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} bericht teruggedraaid
@@ -1,4 +1,6 @@
common.delete=Slett
common.open=Åpne
common.save=Lagre
session.action.cancel=Avbryt
session.connection.connecting=Laster…
session.connection.error.app=Tilkoblingsfeil
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Be agenten om å legge til en MCP-server.
settings.agentBehavior.skills.displayName=Ferdigheter
settings.agentBehavior.skills.search=Filtrer ferdigheter
settings.agentBehavior.skills.empty=Ingen ferdigheter funnet.
settings.agentBehavior.skills.content.empty=Ingen ferdighetsinnhold tilgjengelig.
settings.agentBehavior.skills.load.timeout=Innlasting av ferdigheter tidsavbrutt. Eksisterende ferdigheter ble beholdt; fjern trege eller utilgjengelige URL-er og oppdater.
settings.agentBehavior.skills.reload.deferred=Ferdighetskilden ble lagret. Last Core på nytt etter at aktive økter er ferdige for å bruke nye ferdigheter.
settings.agentBehavior.skills.reload.blocked=Ferdighetsinnstillinger ble lagret, men det finnes aktive økter. Last Core på nytt etter at disse øktene er ferdige for å bruke de nye ferdighetene.
settings.agentBehavior.skills.saved.notification=Ferdighetsinnstillinger lagret
settings.agentBehavior.skills.delete.title=Slett ferdighet
settings.agentBehavior.skills.delete.message=Slette ferdigheten {0}? Dette fjerner ferdighetsfilen og kan ikke angres.
settings.agentBehavior.skills.delete.failed=Kunne ikke slette ferdigheten.
settings.agentBehavior.skills.openInEditor=Åpne i editor
settings.agentBehavior.skills.openInEditor.pending=Ferdighetsfilen åpnes etter at du lukker Innstillinger.
settings.agentBehavior.skills.openInEditor.failed=Kunne ikke åpne ferdighetsfilen i editoren.
settings.agentBehavior.skills.sources.empty=Ingen ferdighetskilder konfigurert.
settings.agentBehavior.skills.sources.title=Flere ferdighetskilder
settings.agentBehavior.skills.sources.add=Legg til
settings.agentBehavior.skills.sources.addPath=Legg til sti
settings.agentBehavior.skills.sources.addPath.title=Legg til ferdighetssti
settings.agentBehavior.skills.sources.addPath.prompt=Velg en mappe som inneholder Kilo-ferdigheter.
settings.agentBehavior.skills.sources.addUrl=Legg til URL
settings.agentBehavior.skills.sources.addUrl.title=Legg til URL for ferdighetskilde
settings.agentBehavior.skills.sources.addUrl.prompt=Skriv inn en URL for ferdighetskilde.
settings.agentBehavior.skills.sources.editPath.title=Rediger ferdighetssti
settings.agentBehavior.skills.sources.editUrl.title=Rediger ferdighets-URL
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} melding rullet tilbake
@@ -1,4 +1,6 @@
common.delete=Usuń
common.open=Otwórz
common.save=Zapisz
session.action.cancel=Anuluj
session.connection.connecting=Ładowanie…
session.connection.error.app=Błąd połączenia
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Aby dodać serwer MCP, poproś agenta, aby to zrobił.
settings.agentBehavior.skills.displayName=Umiejętności
settings.agentBehavior.skills.search=Filtruj umiejętności
settings.agentBehavior.skills.empty=Nie znaleziono umiejętności.
settings.agentBehavior.skills.content.empty=Brak dostępnej treści umiejętności.
settings.agentBehavior.skills.load.timeout=Przekroczono czas ładowania umiejętności. Istniejące umiejętności zostały zachowane; usuń wolne lub niedostępne adresy URL i odśwież.
settings.agentBehavior.skills.reload.deferred=Źródło umiejętności zapisane. Przeładuj Core po zakończeniu aktywnych sesji, aby zastosować nowe umiejętności.
settings.agentBehavior.skills.reload.blocked=Ustawienia umiejętności zapisane, ale są aktywne sesje. Przeładuj Core po zakończeniu tych sesji, aby zastosować nowe umiejętności.
settings.agentBehavior.skills.saved.notification=Ustawienia umiejętności zapisane
settings.agentBehavior.skills.delete.title=Usuń umiejętność
settings.agentBehavior.skills.delete.message=Usunąć umiejętność {0}? Spowoduje to usunięcie pliku umiejętności i nie można tego cofnąć.
settings.agentBehavior.skills.delete.failed=Nie można usunąć umiejętności.
settings.agentBehavior.skills.openInEditor=Otwórz w edytorze
settings.agentBehavior.skills.openInEditor.pending=Plik umiejętności otworzy się po zamknięciu Ustawień.
settings.agentBehavior.skills.openInEditor.failed=Nie można otworzyć pliku umiejętności w edytorze.
settings.agentBehavior.skills.sources.empty=Nie skonfigurowano źródeł umiejętności.
settings.agentBehavior.skills.sources.title=Dodatkowe źródła umiejętności
settings.agentBehavior.skills.sources.add=Dodaj
settings.agentBehavior.skills.sources.addPath=Dodaj ścieżkę
settings.agentBehavior.skills.sources.addPath.title=Dodaj ścieżkę umiejętności
settings.agentBehavior.skills.sources.addPath.prompt=Wybierz folder zawierający umiejętności Kilo.
settings.agentBehavior.skills.sources.addUrl=Dodaj URL
settings.agentBehavior.skills.sources.addUrl.title=Dodaj URL źródła umiejętności
settings.agentBehavior.skills.sources.addUrl.prompt=Wpisz URL źródła umiejętności.
settings.agentBehavior.skills.sources.editPath.title=Edytuj ścieżkę umiejętności
settings.agentBehavior.skills.sources.editUrl.title=Edytuj URL umiejętności
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one=Cofnięto {0} wiadomość
@@ -1,4 +1,6 @@
common.delete=Excluir
common.open=Abrir
common.save=Salvar
session.action.cancel=Cancelar
session.connection.connecting=Carregando…
session.connection.error.app=Falha na conexão
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Para adicionar um servidor MCP, peça ao agente para fazer isso.
settings.agentBehavior.skills.displayName=Habilidades
settings.agentBehavior.skills.search=Filtrar habilidades
settings.agentBehavior.skills.empty=Nenhuma habilidade encontrada.
settings.agentBehavior.skills.content.empty=Nenhum conteúdo de habilidade disponível.
settings.agentBehavior.skills.load.timeout=O carregamento de habilidades atingiu o tempo limite. As habilidades existentes foram mantidas; remova URLs lentas ou inacessíveis e atualize.
settings.agentBehavior.skills.reload.deferred=Fonte de habilidades salva. Recarregue o Core depois que as sessões ativas terminarem para aplicar novas habilidades.
settings.agentBehavior.skills.reload.blocked=Configurações de habilidades salvas, mas há sessões ativas. Recarregue o Core depois que essas sessões terminarem para aplicar as novas habilidades.
settings.agentBehavior.skills.saved.notification=Configurações de habilidades salvas
settings.agentBehavior.skills.delete.title=Excluir habilidade
settings.agentBehavior.skills.delete.message=Excluir a habilidade {0}? Isso remove o arquivo da habilidade e não pode ser desfeito.
settings.agentBehavior.skills.delete.failed=Não foi possível excluir a habilidade.
settings.agentBehavior.skills.openInEditor=Abrir no editor
settings.agentBehavior.skills.openInEditor.pending=O arquivo da habilidade será aberto depois que você fechar as Configurações.
settings.agentBehavior.skills.openInEditor.failed=Não foi possível abrir o arquivo da habilidade no editor.
settings.agentBehavior.skills.sources.empty=Nenhuma fonte de habilidades configurada.
settings.agentBehavior.skills.sources.title=Fontes de habilidades adicionais
settings.agentBehavior.skills.sources.add=Adicionar
settings.agentBehavior.skills.sources.addPath=Adicionar caminho
settings.agentBehavior.skills.sources.addPath.title=Adicionar caminho de habilidades
settings.agentBehavior.skills.sources.addPath.prompt=Escolha uma pasta contendo habilidades do Kilo.
settings.agentBehavior.skills.sources.addUrl=Adicionar URL
settings.agentBehavior.skills.sources.addUrl.title=Adicionar URL de fonte de habilidades
settings.agentBehavior.skills.sources.addUrl.prompt=Insira uma URL de fonte de habilidades.
settings.agentBehavior.skills.sources.editPath.title=Editar caminho de habilidades
settings.agentBehavior.skills.sources.editUrl.title=Editar URL de habilidades
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} mensagem revertida
@@ -1,4 +1,6 @@
common.delete=Удалить
common.open=Открыть
common.save=Сохранить
session.action.cancel=Отмена
session.connection.connecting=Загрузка…
session.connection.error.app=Ошибка подключения
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Чтобы добавить MCP-сервер, попросите агента сделать это.
settings.agentBehavior.skills.displayName=Навыки
settings.agentBehavior.skills.search=Фильтр навыков
settings.agentBehavior.skills.empty=Навыки не найдены.
settings.agentBehavior.skills.content.empty=Нет доступного содержимого навыка.
settings.agentBehavior.skills.load.timeout=Время загрузки навыков истекло. Существующие навыки сохранены; удалите медленные или недоступные URL и обновите.
settings.agentBehavior.skills.reload.deferred=Источник навыков сохранён. Перезагрузите Core после завершения активных сессий, чтобы применить новые навыки.
settings.agentBehavior.skills.reload.blocked=Настройки навыков сохранены, но есть активные сессии. Перезагрузите Core после завершения этих сессий, чтобы применить новые навыки.
settings.agentBehavior.skills.saved.notification=Настройки навыков сохранены
settings.agentBehavior.skills.delete.title=Удалить навык
settings.agentBehavior.skills.delete.message=Удалить навык {0}? Это удалит файл навыка, и действие нельзя будет отменить.
settings.agentBehavior.skills.delete.failed=Не удалось удалить навык.
settings.agentBehavior.skills.openInEditor=Открыть в редакторе
settings.agentBehavior.skills.openInEditor.pending=Файл навыка откроется после закрытия настроек.
settings.agentBehavior.skills.openInEditor.failed=Не удалось открыть файл навыка в редакторе.
settings.agentBehavior.skills.sources.empty=Источники навыков не настроены.
settings.agentBehavior.skills.sources.title=Дополнительные источники навыков
settings.agentBehavior.skills.sources.add=Добавить
settings.agentBehavior.skills.sources.addPath=Добавить путь
settings.agentBehavior.skills.sources.addPath.title=Добавить путь к навыкам
settings.agentBehavior.skills.sources.addPath.prompt=Выберите папку, содержащую навыки Kilo.
settings.agentBehavior.skills.sources.addUrl=Добавить URL
settings.agentBehavior.skills.sources.addUrl.title=Добавить URL источника навыков
settings.agentBehavior.skills.sources.addUrl.prompt=Введите URL источника навыков.
settings.agentBehavior.skills.sources.editPath.title=Изменить путь к навыкам
settings.agentBehavior.skills.sources.editUrl.title=Изменить URL навыков
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one=Отменено сообщений: {0}
@@ -1,4 +1,6 @@
common.delete=ลบ
common.open=เปิด
common.save=บันทึก
session.action.cancel=ยกเลิก
session.connection.connecting=กำลังโหลด…
session.connection.error.app=การเชื่อมต่อล้มเหลว
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=หากต้องการเพิ่มเซิร์ฟเวอร์ MCP ให้ขอให้เอเจนต์เพิ่มให้
settings.agentBehavior.skills.displayName=ทักษะ
settings.agentBehavior.skills.search=กรองทักษะ
settings.agentBehavior.skills.empty=ไม่พบทักษะ
settings.agentBehavior.skills.content.empty=ไม่มีเนื้อหาทักษะที่พร้อมใช้งาน
settings.agentBehavior.skills.load.timeout=การโหลดทักษะหมดเวลา ระบบเก็บทักษะเดิมไว้แล้ว โปรดลบ URL ที่ช้าหรือเข้าถึงไม่ได้แล้วรีเฟรช
settings.agentBehavior.skills.reload.deferred=บันทึกแหล่งที่มาทักษะแล้ว โหลด Core ใหม่หลังจากเซสชันที่ใช้งานอยู่สิ้นสุดเพื่อใช้ทักษะใหม่
settings.agentBehavior.skills.reload.blocked=บันทึกการตั้งค่าทักษะแล้ว แต่ยังมีเซสชันที่ใช้งานอยู่ โหลด Core ใหม่หลังจากเซสชันเหล่านั้นสิ้นสุดเพื่อใช้ทักษะใหม่
settings.agentBehavior.skills.saved.notification=บันทึกการตั้งค่าทักษะแล้ว
settings.agentBehavior.skills.delete.title=ลบทักษะ
settings.agentBehavior.skills.delete.message=ลบทักษะ {0} หรือไม่ การดำเนินการนี้จะลบไฟล์ทักษะและไม่สามารถย้อนกลับได้
settings.agentBehavior.skills.delete.failed=ไม่สามารถลบทักษะได้
settings.agentBehavior.skills.openInEditor=เปิดในตัวแก้ไข
settings.agentBehavior.skills.openInEditor.pending=ไฟล์ทักษะจะเปิดหลังจากคุณปิดการตั้งค่า
settings.agentBehavior.skills.openInEditor.failed=ไม่สามารถเปิดไฟล์ทักษะในตัวแก้ไขได้
settings.agentBehavior.skills.sources.empty=ไม่ได้กำหนดค่าแหล่งที่มาทักษะ
settings.agentBehavior.skills.sources.title=แหล่งที่มาทักษะเพิ่มเติม
settings.agentBehavior.skills.sources.add=เพิ่ม
settings.agentBehavior.skills.sources.addPath=เพิ่มเส้นทาง
settings.agentBehavior.skills.sources.addPath.title=เพิ่มเส้นทางทักษะ
settings.agentBehavior.skills.sources.addPath.prompt=เลือกโฟลเดอร์ที่มีทักษะ Kilo
settings.agentBehavior.skills.sources.addUrl=เพิ่ม URL
settings.agentBehavior.skills.sources.addUrl.title=เพิ่ม URL แหล่งที่มาทักษะ
settings.agentBehavior.skills.sources.addUrl.prompt=ป้อน URL แหล่งที่มาทักษะ
settings.agentBehavior.skills.sources.editPath.title=แก้ไขเส้นทางทักษะ
settings.agentBehavior.skills.sources.editUrl.title=แก้ไข URL ทักษะ
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one=ย้อนกลับข้อความ {0} รายการแล้ว
@@ -1,4 +1,6 @@
common.delete=Sil
common.open=
common.save=Kaydet
session.action.cancel=İptal
session.connection.connecting=Yükleniyor…
session.connection.error.app=Bağlantı hatası
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=MCP sunucusu eklemek için ajandan bunu yapmasını isteyin.
settings.agentBehavior.skills.displayName=Beceriler
settings.agentBehavior.skills.search=Becerileri filtrele
settings.agentBehavior.skills.empty=Beceri bulunamadı.
settings.agentBehavior.skills.content.empty=Kullanılabilir beceri içeriği yok.
settings.agentBehavior.skills.load.timeout=Beceriler yüklenirken zaman aşımına uğradı. Mevcut beceriler korundu; yavaş veya ulaşılamayan URL'leri kaldırıp yenileyin.
settings.agentBehavior.skills.reload.deferred=Beceri kaynağı kaydedildi. Yeni becerileri uygulamak için etkin oturumlar bittikten sonra Core'u yeniden yükleyin.
settings.agentBehavior.skills.reload.blocked=Beceri ayarları kaydedildi, ancak etkin oturumlar var. Yeni becerileri uygulamak için bu oturumlar bittikten sonra Core'u yeniden yükleyin.
settings.agentBehavior.skills.saved.notification=Beceri ayarları kaydedildi
settings.agentBehavior.skills.delete.title=Beceriyi Sil
settings.agentBehavior.skills.delete.message={0} becerisi silinsin mi? Bu işlem beceri dosyasını kaldırır ve geri alınamaz.
settings.agentBehavior.skills.delete.failed=Beceri silinemedi.
settings.agentBehavior.skills.openInEditor=Düzenleyicide Aç
settings.agentBehavior.skills.openInEditor.pending=Beceri dosyası Ayarlar kapatıldıktan sonra açılacak.
settings.agentBehavior.skills.openInEditor.failed=Beceri dosyası düzenleyicide açılamadı.
settings.agentBehavior.skills.sources.empty=Yapılandırılmış beceri kaynağı yok.
settings.agentBehavior.skills.sources.title=Ek Beceri Kaynakları
settings.agentBehavior.skills.sources.add=Ekle
settings.agentBehavior.skills.sources.addPath=Yol ekle
settings.agentBehavior.skills.sources.addPath.title=Beceri Yolu Ekle
settings.agentBehavior.skills.sources.addPath.prompt=Kilo becerilerini içeren bir klasör seçin.
settings.agentBehavior.skills.sources.addUrl=URL ekle
settings.agentBehavior.skills.sources.addUrl.title=Beceri Kaynağı URL'si Ekle
settings.agentBehavior.skills.sources.addUrl.prompt=Bir beceri kaynağı URL'si girin.
settings.agentBehavior.skills.sources.editPath.title=Beceri Yolunu Düzenle
settings.agentBehavior.skills.sources.editUrl.title=Beceri URL'sini Düzenle
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one={0} mesaj geri alındı
@@ -1,4 +1,6 @@
common.delete=Видалити
common.open=Відкрити
common.save=Зберегти
session.action.cancel=Скасувати
session.connection.connecting=Завантаження…
session.connection.error.app=Помилка з'єднання
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=Щоб додати сервер MCP, попросіть агента зробити це.
settings.agentBehavior.skills.displayName=Навички
settings.agentBehavior.skills.search=Фільтрувати навички
settings.agentBehavior.skills.empty=Навички не знайдено.
settings.agentBehavior.skills.content.empty=Немає доступного вмісту навички.
settings.agentBehavior.skills.load.timeout=Час завантаження навичок минув. Наявні навички збережено; видаліть повільні або недоступні URL-адреси й оновіть.
settings.agentBehavior.skills.reload.deferred=Джерело навичок збережено. Перезавантажте Core після завершення активних сеансів, щоб застосувати нові навички.
settings.agentBehavior.skills.reload.blocked=Налаштування навичок збережено, але є активні сеанси. Перезавантажте Core після завершення цих сеансів, щоб застосувати нові навички.
settings.agentBehavior.skills.saved.notification=Налаштування навичок збережено
settings.agentBehavior.skills.delete.title=Видалити навичку
settings.agentBehavior.skills.delete.message=Видалити навичку {0}? Це видалить файл навички, і дію не можна буде скасувати.
settings.agentBehavior.skills.delete.failed=Не вдалося видалити навичку.
settings.agentBehavior.skills.openInEditor=Відкрити в редакторі
settings.agentBehavior.skills.openInEditor.pending=Файл навички відкриється після закриття Налаштувань.
settings.agentBehavior.skills.openInEditor.failed=Не вдалося відкрити файл навички в редакторі.
settings.agentBehavior.skills.sources.empty=Джерела навичок не налаштовано.
settings.agentBehavior.skills.sources.title=Додаткові джерела навичок
settings.agentBehavior.skills.sources.add=Додати
settings.agentBehavior.skills.sources.addPath=Додати шлях
settings.agentBehavior.skills.sources.addPath.title=Додати шлях до навичок
settings.agentBehavior.skills.sources.addPath.prompt=Виберіть папку з навичками Kilo.
settings.agentBehavior.skills.sources.addUrl=Додати URL
settings.agentBehavior.skills.sources.addUrl.title=Додати URL джерела навичок
settings.agentBehavior.skills.sources.addUrl.prompt=Введіть URL джерела навичок.
settings.agentBehavior.skills.sources.editPath.title=Редагувати шлях до навичок
settings.agentBehavior.skills.sources.editUrl.title=Редагувати URL навичок
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one=Відкочено повідомлень: {0}
@@ -1,4 +1,6 @@
common.delete=删除
common.open=打开
common.save=保存
session.action.cancel=取消
session.connection.connecting=加载中…
session.connection.error.app=连接失败
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=要添加 MCP 服务器,请让代理为你添加。
settings.agentBehavior.skills.displayName=技能
settings.agentBehavior.skills.search=筛选技能
settings.agentBehavior.skills.empty=未找到技能。
settings.agentBehavior.skills.content.empty=没有可用的技能内容。
settings.agentBehavior.skills.load.timeout=技能加载超时。已保留现有技能;请移除缓慢或无法访问的 URL 后刷新。
settings.agentBehavior.skills.reload.deferred=技能源已保存。请在活动会话结束后重新加载 Core,以应用新技能。
settings.agentBehavior.skills.reload.blocked=技能设置已保存,但仍有活动会话。请在这些会话结束后重新加载 Core,以应用新技能。
settings.agentBehavior.skills.saved.notification=技能设置已保存
settings.agentBehavior.skills.delete.title=删除技能
settings.agentBehavior.skills.delete.message=要删除技能 {0} 吗?这会移除技能文件,且无法撤销。
settings.agentBehavior.skills.delete.failed=无法删除该技能。
settings.agentBehavior.skills.openInEditor=在编辑器中打开
settings.agentBehavior.skills.openInEditor.pending=关闭设置后将打开技能文件。
settings.agentBehavior.skills.openInEditor.failed=无法在编辑器中打开技能文件。
settings.agentBehavior.skills.sources.empty=未配置技能源。
settings.agentBehavior.skills.sources.title=其他技能源
settings.agentBehavior.skills.sources.add=添加
settings.agentBehavior.skills.sources.addPath=添加路径
settings.agentBehavior.skills.sources.addPath.title=添加技能路径
settings.agentBehavior.skills.sources.addPath.prompt=选择一个包含 Kilo 技能的文件夹。
settings.agentBehavior.skills.sources.addUrl=添加 URL
settings.agentBehavior.skills.sources.addUrl.title=添加技能源 URL
settings.agentBehavior.skills.sources.addUrl.prompt=输入技能源 URL。
settings.agentBehavior.skills.sources.editPath.title=编辑技能路径
settings.agentBehavior.skills.sources.editUrl.title=编辑技能 URL
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one=已回滚 {0} 条消息
@@ -1,4 +1,6 @@
common.delete=刪除
common.open=開啟
common.save=儲存
session.action.cancel=取消
session.connection.connecting=載入中…
session.connection.error.app=連線失敗
@@ -372,6 +374,31 @@ settings.agentBehavior.undo=Undo
settings.agentBehavior.agents.create.failed=Could not create the agent.
settings.agentBehavior.mcp.addHint=若要新增 MCP 伺服器,請請代理為你新增。
settings.agentBehavior.skills.displayName=技能
settings.agentBehavior.skills.search=篩選技能
settings.agentBehavior.skills.empty=找不到技能。
settings.agentBehavior.skills.content.empty=沒有可用的技能內容。
settings.agentBehavior.skills.load.timeout=技能載入逾時。已保留現有技能;請移除緩慢或無法連線的 URL 後重新整理。
settings.agentBehavior.skills.reload.deferred=技能來源已儲存。請在作用中工作階段結束後重新載入 Core,以套用新技能。
settings.agentBehavior.skills.reload.blocked=技能設定已儲存,但仍有作用中工作階段。請在這些工作階段結束後重新載入 Core,以套用新技能。
settings.agentBehavior.skills.saved.notification=技能設定已儲存
settings.agentBehavior.skills.delete.title=刪除技能
settings.agentBehavior.skills.delete.message=要刪除技能 {0} 嗎?這會移除技能檔案,且無法復原。
settings.agentBehavior.skills.delete.failed=無法刪除該技能。
settings.agentBehavior.skills.openInEditor=在編輯器中開啟
settings.agentBehavior.skills.openInEditor.pending=關閉設定後將開啟技能檔案。
settings.agentBehavior.skills.openInEditor.failed=無法在編輯器中開啟技能檔案。
settings.agentBehavior.skills.sources.empty=未設定技能來源。
settings.agentBehavior.skills.sources.title=其他技能來源
settings.agentBehavior.skills.sources.add=新增
settings.agentBehavior.skills.sources.addPath=新增路徑
settings.agentBehavior.skills.sources.addPath.title=新增技能路徑
settings.agentBehavior.skills.sources.addPath.prompt=選擇包含 Kilo 技能的資料夾。
settings.agentBehavior.skills.sources.addUrl=新增 URL
settings.agentBehavior.skills.sources.addUrl.title=新增技能來源 URL
settings.agentBehavior.skills.sources.addUrl.prompt=輸入技能來源 URL。
settings.agentBehavior.skills.sources.editPath.title=編輯技能路徑
settings.agentBehavior.skills.sources.editUrl.title=編輯技能 URL
session.file.missing=Couldn''t find ''{0}'' in this repository.
revert.banner.filesNotRestored=Snapshots are off - only the conversation was reverted; your files were not changed.
revert.banner.count.one=已回復 {0} 則訊息
@@ -3,6 +3,7 @@ package ai.kilocode.client.app
import ai.kilocode.client.testing.FakeAgentBehaviorRpcApi
import ai.kilocode.rpc.dto.AgentCreateDto
import ai.kilocode.rpc.dto.McpStatusDto
import ai.kilocode.rpc.dto.SkillDto
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -10,6 +11,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.test.assertFailsWith
class KiloAgentBehaviorServiceTest : BasePlatformTestCase() {
private lateinit var scope: CoroutineScope
@@ -67,6 +69,35 @@ class KiloAgentBehaviorServiceTest : BasePlatformTestCase() {
assertTrue(rpc.removals.isEmpty())
}
fun `test loadSkills propagates rpc failure`() = runBlocking {
rpc.skillsError = RuntimeException("boom")
assertFailsWith<RuntimeException> {
withContext(Dispatchers.Default) { service.loadSkills("/test") }
}
}
fun `test refreshSkills returns previous rows on rpc failure`() = runBlocking {
val fallback = listOf(SkillDto("plan", location = "/test/SKILL.md"))
rpc.skillsError = RuntimeException("boom")
val items = withContext(Dispatchers.Default) { service.refreshSkills("/test", fallback) }
assertEquals(fallback, items)
}
fun `test saveSkills forwards all edits`() = runBlocking {
rpc.skills = listOf(SkillDto("plan", location = "/test/plan/SKILL.md"))
val ok = withContext(Dispatchers.Default) {
service.saveSkills("/test", mapOf("/test/plan/SKILL.md" to "# Saved"))
}
assertTrue(ok)
assertEquals(listOf(Triple("/test", "/test/plan/SKILL.md", "# Saved")), rpc.skillSaves)
assertEquals("# Saved", rpc.skills.single().content)
}
fun `test mcpStatus forwards directory`() = runBlocking {
rpc.mcps = listOf(McpStatusDto("filesystem", "connected"))
@@ -18,6 +18,7 @@ class AgentBehaviorConfigurableTest : BasePlatformTestCase() {
fun `test child ids match xml registration`() {
assertEquals("ai.kilocode.jetbrains.settings.agentBehavior.agents", AgentsConfigurable.ID)
assertEquals("ai.kilocode.jetbrains.settings.agentBehavior.mcp", McpConfigurable.ID)
assertEquals("ai.kilocode.jetbrains.settings.agentBehavior.skills", SkillsConfigurable.ID)
}
fun `test createComponent contains child links in order`() {
@@ -26,7 +27,7 @@ class AgentBehaviorConfigurableTest : BasePlatformTestCase() {
edt {
val panel = cfg.createComponent()
val labels = links(panel as Container).map { it.text }
assertEquals(listOf("Agents", "MCP Servers"), labels)
assertEquals(listOf("Agents", "MCP Servers", "Skills"), labels)
}
}
@@ -0,0 +1,615 @@
package ai.kilocode.client.settings.agents
import ai.kilocode.client.app.KiloAgentBehaviorService
import ai.kilocode.client.app.KiloAppService
import ai.kilocode.client.app.KiloWorkspaceService
import ai.kilocode.client.settings.base.SettingsListItem
import ai.kilocode.client.settings.base.settingsListCellBounds
import ai.kilocode.client.testing.FakeAgentBehaviorRpcApi
import ai.kilocode.client.testing.FakeAppRpcApi
import ai.kilocode.client.testing.FakeWorkspaceRpcApi
import ai.kilocode.client.testing.fire
import ai.kilocode.rpc.dto.ConfigDto
import ai.kilocode.rpc.dto.KiloAppStateDto
import ai.kilocode.rpc.dto.KiloAppStatusDto
import ai.kilocode.rpc.dto.SkillDto
import ai.kilocode.rpc.dto.SkillsConfigDto
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.openapi.fileTypes.UnknownFileType
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.TestDialog
import com.intellij.openapi.ui.TestDialogManager
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.testFramework.replaceService
import com.intellij.ui.TitledSeparator
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBScrollPane
import com.intellij.util.ui.UIUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.awt.BorderLayout
import java.awt.Container
import java.awt.Dimension
import java.awt.Point
import java.awt.event.InputEvent
import java.awt.event.MouseEvent
import javax.swing.JComponent
import javax.swing.ScrollPaneConstants
import javax.swing.Scrollable
import javax.swing.JTextField
class SkillsSettingsUiTest : BasePlatformTestCase() {
private var scope: CoroutineScope? = null
private var ui: SkillsSettingsUi? = null
private lateinit var app: KiloAppService
private lateinit var appRpc: FakeAppRpcApi
private lateinit var agentRpc: FakeAgentBehaviorRpcApi
private lateinit var workspaceRpc: FakeWorkspaceRpcApi
private var shown = 0
override fun tearDown() {
try {
TestDialogManager.setTestDialog(TestDialog.DEFAULT)
ui?.let { panel -> edt { panel.dispose(); true } }
ui = null
scope?.cancel()
scope = null
} finally {
super.tearDown()
}
}
fun `test loads skills with location note and builtins have no actions`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
edt {
val rows = rows(panel)
val custom = rows.single { it.key == CUSTOM }
assertEquals("plan", custom.title)
assertEquals(CUSTOM, custom.note)
assertEquals("Plan work", custom.description)
assertEquals("edit", custom.doubleClick)
assertEquals(listOf("open", "edit", "delete"), custom.cells.map { it.id })
assertTrue(custom.cells.single { it.id == "open" }.primary)
assertFalse(custom.cells.single { it.id == "edit" }.primary)
assertEquals("Edit", custom.cells.single { it.id == "edit" }.label)
assertTrue(custom.cells.single { it.id == "delete" }.iconOnly)
val builtin = rows.single { it.key == "builtin" }
assertEquals("thinking", builtin.title)
assertNull(builtin.note)
assertEquals("edit", builtin.doubleClick)
assertEquals(listOf("built-in"), builtin.badges.map { it.text })
assertEquals(listOf("edit"), builtin.cells.map { it.id })
assertEquals("Open", builtin.cells.single().label)
val remote = rows.single { it.key == REMOTE }
assertEquals(listOf("edit"), remote.cells.map { it.id })
assertEquals("Open", remote.cells.single().label)
assertEquals(listOf(DIR), agentRpc.skillCalls)
true
}
}
fun `test skills list is vertically scrolled without horizontal scrollbar`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
edt {
val pane = scrollFor(panel, skillsList(panel))
val view = pane.viewport.view
val layout = panel.content.layout as BorderLayout
assertEquals(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER, pane.horizontalScrollBarPolicy)
assertTrue((view as Scrollable).getScrollableTracksViewportWidth())
assertFalse(view.getScrollableTracksViewportHeight())
assertSame(pane, layout.getLayoutComponent(BorderLayout.CENTER))
assertSame(panel.sources, layout.getLayoutComponent(BorderLayout.SOUTH))
true
}
}
fun `test sources section has additional sources title`() {
val panel = panel()
flushUntil { sourceRows(panel).size == 2 }
assertTrue(edt {
components(panel).filterIsInstance<TitledSeparator>().any { it.text == "Additional Skill Sources" }
})
}
fun `test skills list does not show description tooltips`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
edt {
val list = skillsList(panel)
list.size = Dimension(520, 320)
list.doLayout()
val bounds = list.getCellBounds(0, 0)
assertNull(list.getToolTipText(mouse(list, MouseEvent.MOUSE_MOVED, Point(bounds.x + 8, bounds.y + 8))))
true
}
}
fun `test renderer puts location on first line and description on preview line`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
edt {
val list = skillsList(panel)
val row = rows(panel).single { it.key == CUSTOM }
val idx = rows(panel).indexOf(row)
val comp = list.cellRenderer.getListCellRendererComponent(list, row, idx, true, true)
comp.setSize(520, list.fixedCellHeight)
layout(comp)
val title = components(comp).filterIsInstance<SimpleColoredComponent>().single()
val labels = components(comp).filterIsInstance<JBLabel>().filter { it.isVisible }.map { it.text }
assertEquals("plan $CUSTOM", title.toString())
assertTrue(labels.contains("Plan work"))
true
}
}
fun `test double click stages skill content until apply`() {
val panel = panel(edit = { _, _ -> FakeSkillDialog("# Saved") })
flushUntil { rows(panel).size == 3 }
doubleClick(skillsList(panel), panel, CUSTOM)
assertTrue(edt { panel.modified() })
assertTrue(agentRpc.skillSaves.isEmpty())
edt { panel.applyDraft(); true }
flushUntil { agentRpc.skillSaves.size == 1 }
assertEquals(Triple(DIR, CUSTOM, "# Saved"), agentRpc.skillSaves.single())
}
fun `test edited skill row keeps normal actions`() {
val panel = panel(edit = { _, _ -> FakeSkillDialog("# Draft") })
flushUntil { rows(panel).size == 3 }
doubleClick(skillsList(panel), panel, CUSTOM)
assertEquals(listOf("open", "edit", "delete"), edt { rows(panel).single { it.key == CUSTOM }.cells.map { it.id } })
assertTrue(edt { panel.modified() })
}
fun `test reopening staged skill edit shows draft content before apply`() {
val seen = mutableListOf<String?>()
val panel = panel(edit = { skill, _ ->
seen += skill.content
FakeSkillDialog(if (seen.size == 1) "# Draft" else "# Draft 2")
})
flushUntil { rows(panel).size == 3 }
doubleClick(skillsList(panel), panel, CUSTOM)
doubleClick(skillsList(panel), panel, CUSTOM)
assertEquals(listOf("# Plan\nUse steps", "# Draft"), seen)
assertTrue(agentRpc.skillSaves.isEmpty())
}
fun `test open in editor action opens skill file`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
click(skillsList(panel), panel, CUSTOM, "open")
assertEquals("The skill file will open after you close Settings.", edt { progressText(panel) })
flushUntil { workspaceRpc.openedFiles.size == 1 }
assertEquals(FakeWorkspaceRpcApi.Opened(CUSTOM, null, null), workspaceRpc.openedFiles.single())
}
fun `test read only skills open without staging edits or editor file open`() {
shown = 0
val panel = panel(edit = { _, savable ->
assertFalse(savable)
FakeSkillDialog("# Ignored") { shown += 1 }
})
flushUntil { rows(panel).size == 3 }
click(skillsList(panel), panel, REMOTE, "edit")
assertEquals(1, shown)
assertFalse(edt { panel.modified() })
assertTrue(agentRpc.skillSaves.isEmpty())
assertTrue(workspaceRpc.openedFiles.isEmpty())
}
fun `test skill edit dialog shows content with fallback`() {
edt {
val content = SkillEditDialog(SkillDto("plan", "desc", CUSTOM, "# Plan\nUse steps"), true)
val fallback = SkillEditDialog(SkillDto("plan", "desc", CUSTOM), true)
val readonly = SkillEditDialog(SkillDto("kilo-config", "desc", "builtin", "<h1>Kilo Config</h1>"), false)
try {
assertEquals("# Plan\nUse steps", content.content())
assertEquals("desc", fallback.content())
assertEquals("<h1>Kilo Config</h1>", readonly.content())
assertEquals("OK", content.okText())
} finally {
content.close(DialogWrapper.CANCEL_EXIT_CODE)
fallback.close(DialogWrapper.CANCEL_EXIT_CODE)
readonly.close(DialogWrapper.CANCEL_EXIT_CODE)
}
true
}
}
fun `test skill editor file type follows content syntax before location`() {
assertEquals(
FileTypeManager.getInstance().getFileTypeByFileName("index.html"),
skillFileType("builtin", "<h1>Kilo CLI Configuration Reference</h1><p>All config lives in <code>kilo.json</code>.</p>"),
)
assertEquals(
skillFileType("SKILL.md"),
skillFileType("builtin", "# Kilo CLI Configuration Reference\n\nAll config lives in `kilo.json`."),
)
assertEquals(PlainTextFileType.INSTANCE, skillFileType("builtin", "Plain fallback text"))
}
fun `test delete action stages skill removal until apply`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
TestDialogManager.setTestDialog(TestDialog.YES)
click(skillsList(panel), panel, CUSTOM, "delete")
assertTrue(edt { rows(panel).none { it.key == CUSTOM } })
assertTrue(agentRpc.skillRemovals.isEmpty())
edt { panel.applyDraft(); true }
flushUntil { agentRpc.skillRemovals.size == 1 }
assertEquals(listOf(DIR to CUSTOM), agentRpc.skillRemovals)
}
fun `test delete action requires confirmation`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
TestDialogManager.setTestDialog { Messages.NO }
click(skillsList(panel), panel, CUSTOM, "delete")
edt { UIUtil.dispatchAllInvocationEvents(); true }
assertTrue(agentRpc.skillRemovals.isEmpty())
assertTrue(edt { rows(panel).any { it.key == CUSTOM } })
}
fun `test add path and url write skills config patch on apply`() {
var path = "/extra/skills"
var url = "https://skills.test/index.json"
val panel = panel(choose = { path }, input = { _, _ -> url })
flushUntil { rows(panel).size == 3 }
edt { panel.sources.addPath(); true }
edt { panel.sources.addUrl(); true }
flushUntil { sourceRows(panel).any { it.key == "url:$url" } }
assertTrue(appRpc.configPatches.isEmpty())
edt { panel.applyDraft(); true }
flushUntil { appRpc.configPatches.size == 1 && !edt { panel.modified() } }
val paths = appRpc.configPatches.single().skills!!.paths
val urls = appRpc.configPatches.single().skills!!.urls
assertEquals(listOf("/global/skills", path), paths)
assertEquals(listOf("https://skills.test/base.json", url), urls)
assertEquals(
listOf("path:/global/skills", "path:$path", "url:https://skills.test/base.json", "url:$url"),
edt { sourceRows(panel).map { it.key } },
)
assertEquals(listOf(DIR), agentRpc.skillReloads)
}
fun `test stale config update result keeps added skill sources visible`() {
val path = "/extra/skills"
val url = "https://skills.test/index.json"
val extra = "$path/extra/SKILL.md"
val panel = panel(choose = { path }, input = { _, _ -> url })
appRpc.configUpdateReturnStale = true
appRpc.afterConfig = { agentRpc.skills = agentRpc.skills + SkillDto("extra", "Extra skill", extra) }
flushUntil { rows(panel).size == 3 }
edt {
panel.sources.addPath()
panel.sources.addUrl()
panel.applyDraft()
true
}
flushUntil { appRpc.configPatches.size == 1 && !edt { panel.modified() } }
assertTrue(edt { rows(panel).any { it.key == extra } })
assertEquals(
listOf("path:/global/skills", "path:$path", "url:https://skills.test/base.json", "url:$url"),
edt { sourceRows(panel).map { it.key } },
)
}
fun `test blocked reload completes apply with warning`() {
val path = "/extra/skills"
val panel = panel(choose = { path })
agentRpc.reloadSkillResult = false
flushUntil { rows(panel).size == 3 }
edt {
panel.sources.addPath()
panel.applyDraft()
true
}
flushUntil { appRpc.configPatches.size == 1 && !edt { panel.modified() } }
assertEquals(listOf(DIR), agentRpc.skillReloads)
assertEquals("Skills settings saved, but active sessions are present. Reload the core after those sessions finish to apply the new skills.", edt { progressText(panel) })
}
fun `test post apply skills refresh failure keeps saved rows`() {
val panel = panel(edit = { _, _ -> FakeSkillDialog("# Saved") })
flushUntil { rows(panel).size == 3 }
doubleClick(skillsList(panel), panel, CUSTOM)
agentRpc.skillsError = RuntimeException("timeout")
edt { panel.applyDraft(); true }
flushUntil { agentRpc.skillSaves.size == 1 && !edt { panel.modified() } }
assertEquals(listOf(CUSTOM, "builtin", REMOTE), edt { rows(panel).map { it.key } })
assertEquals("# Saved", agentRpc.skills.single { it.location == CUSTOM }.content)
}
fun `test source reset discards staged changes`() {
val path = "/extra/skills"
val panel = panel(choose = { path })
flushUntil { rows(panel).size == 3 }
edt { panel.sources.addPath(); true }
assertTrue(edt { sourceRows(panel).any { it.key == "path:$path" } })
assertTrue(edt { panel.modified() })
edt { panel.resetDraft(); true }
assertTrue(appRpc.configPatches.isEmpty())
assertEquals(listOf(CUSTOM, "builtin", REMOTE), edt { rows(panel).map { it.key } })
assertFalse(edt { sourceRows(panel).any { it.key == "path:$path" } })
assertTrue(agentRpc.skillReloads.isEmpty())
}
fun `test delete source writes skills config patch`() {
val panel = panel()
flushUntil { rows(panel).size == 3 && sourceRows(panel).size == 2 }
edt {
sourceList(panel).selectedIndices = intArrayOf(0)
panel.sources.removeSelected()
true
}
assertTrue(appRpc.configPatches.isEmpty())
edt { panel.applyDraft(); true }
flushUntil { appRpc.configPatches.size == 1 && !edt { panel.modified() } }
val patch = appRpc.configPatches.single().skills!!
assertEquals(emptyList<String>(), patch.paths)
assertEquals(listOf("https://skills.test/base.json"), patch.urls)
assertEquals(listOf("url:https://skills.test/base.json"), edt { sourceRows(panel).map { it.key } })
assertEquals(listOf(DIR), agentRpc.skillReloads)
}
fun `test stale config update result keeps removed skill sources hidden`() {
val panel = panel()
appRpc.configUpdateReturnStale = true
appRpc.afterConfig = { agentRpc.skills = agentRpc.skills.filterNot { it.location == CUSTOM } }
flushUntil { rows(panel).size == 3 && sourceRows(panel).size == 2 }
edt {
sourceList(panel).selectedIndices = intArrayOf(0)
panel.sources.removeSelected()
panel.applyDraft()
true
}
flushUntil { appRpc.configPatches.size == 1 && !edt { panel.modified() } }
assertEquals(listOf("builtin", REMOTE), edt { rows(panel).map { it.key } })
assertEquals(listOf("url:https://skills.test/base.json"), edt { sourceRows(panel).map { it.key } })
}
fun `test search filters skills by name`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
edt {
components(panel).filterIsInstance<JTextField>().single().text = "think"
UIUtil.dispatchAllInvocationEvents()
true
}
flushUntil { rows(panel).map { it.key } == listOf("builtin") }
}
fun `test skills reload failure keeps existing rows`() {
val panel = panel()
flushUntil { rows(panel).size == 3 }
agentRpc.skillsError = RuntimeException("timeout")
edt { panel.reload(); true }
flushUntil { edt { skillsList(panel).isEnabled } }
assertEquals(listOf(CUSTOM, "builtin", REMOTE), edt { rows(panel).map { it.key } })
}
fun `test skill editor file type follows location extension`() {
assertNotSame(UnknownFileType.INSTANCE, skillFileType("/tmp/skills/plan/SKILL.md"))
assertEquals(
FileTypeManager.getInstance().getFileTypeByFileName("index.html"),
skillFileType("/tmp/skills/index.html"),
)
assertEquals(PlainTextFileType.INSTANCE, skillFileType("/tmp/skills/index.unknown"))
}
fun `test skill path chooser accepts directories only`() {
val descriptor = skillPathDescriptor()
assertTrue(descriptor.isChooseFolders)
assertFalse(descriptor.isChooseFiles)
}
private fun panel(
choose: (JComponent) -> String? = { null },
input: (String, String) -> String? = { _, _ -> null },
edit: (SkillDto, Boolean) -> SkillEditDialogHandle = { _, _ -> FakeSkillDialog("# Plan\nUse steps") },
): SkillsSettingsUi {
install()
val panel = edt { SkillsSettingsUi(scope!!, DIR, choose, input, edit) }
ui = panel
edt { panel.reload(); true }
return panel
}
private fun install() {
val cs = CoroutineScope(SupervisorJob())
scope = cs
appRpc = FakeAppRpcApi()
workspaceRpc = FakeWorkspaceRpcApi()
agentRpc = FakeAgentBehaviorRpcApi().apply {
skills = listOf(
SkillDto("plan", "Plan work", CUSTOM, "# Plan\nUse steps", editable = true),
SkillDto("thinking", "Built in", "builtin", "Built in content"),
SkillDto("remote", "Remote skill", REMOTE, "# Remote skill"),
)
}
app = KiloAppService(cs, appRpc)
val ready = KiloAppStateDto(
KiloAppStatusDto.READY,
config = ConfigDto(skills = SkillsConfigDto(
paths = listOf("/global/skills"),
urls = listOf("https://skills.test/base.json"),
)),
)
app._state.value = ready
appRpc.state.value = ready
ApplicationManager.getApplication().replaceService(KiloAppService::class.java, app, testRootDisposable)
ApplicationManager.getApplication().replaceService(KiloAgentBehaviorService::class.java, KiloAgentBehaviorService(cs, agentRpc), testRootDisposable)
ApplicationManager.getApplication().replaceService(KiloWorkspaceService::class.java, KiloWorkspaceService(cs, workspaceRpc), testRootDisposable)
}
private fun click(list: JBList<SettingsListItem>, panel: SkillsSettingsUi, key: String, id: String) {
edt {
list.size = Dimension(520, 320)
list.doLayout()
val rows = if (list === skillsList(panel)) rows(panel) else sourceRows(panel)
val idx = rows.indexOfFirst { it.key == key }
list.selectedIndex = idx
val area = settingsListCellBounds(list, idx, selected = true).getValue(id)
click(list, center(area))
true
}
}
private fun doubleClick(list: JBList<SettingsListItem>, panel: SkillsSettingsUi, key: String) {
edt {
list.size = Dimension(520, 320)
list.doLayout()
val idx = rows(panel).indexOfFirst { it.key == key }
list.selectedIndex = idx
val area = list.getCellBounds(idx, idx)
fire(list, mouse(list, MouseEvent.MOUSE_CLICKED, center(area), count = 2))
true
}
}
private fun rows(panel: SkillsSettingsUi): List<SettingsListItem> = items(skillsList(panel))
private fun sourceRows(panel: SkillsSettingsUi): List<SettingsListItem> = items(sourceList(panel))
private fun items(list: JBList<SettingsListItem>): List<SettingsListItem> {
val model = list.model
return (0 until model.size).map { model.getElementAt(it) }
}
private fun skillsList(panel: SkillsSettingsUi) = components(panel).filterIsInstance<JBList<SettingsListItem>>().first()
private fun sourceList(panel: SkillsSettingsUi) = components(panel).filterIsInstance<JBList<SettingsListItem>>().last()
private fun scrollFor(panel: SkillsSettingsUi, list: JBList<SettingsListItem>) = components(panel)
.filterIsInstance<JBScrollPane>()
.single { pane -> pane.viewport.view === list.parent }
private fun progressText(panel: SkillsSettingsUi) = components(panel.progress).filterIsInstance<JBLabel>().single().text
private fun SkillEditDialog.okText(): String {
val method = DialogWrapper::class.java.getDeclaredMethod("getOKAction")
method.isAccessible = true
return (method.invoke(this) as javax.swing.Action).getValue(javax.swing.Action.NAME) as String
}
private fun components(root: java.awt.Component): List<java.awt.Component> {
val out = mutableListOf<java.awt.Component>()
fun visit(item: java.awt.Component) {
out += item
if (item is Container) item.components.forEach { visit(it) }
}
visit(root)
return out
}
private fun layout(root: java.awt.Component) {
root.doLayout()
if (root is Container) root.components.filterIsInstance<Container>().forEach { layout(it) }
UIUtil.dispatchAllInvocationEvents()
}
private fun center(rect: java.awt.Rectangle) = Point(rect.x + rect.width / 2, rect.y + rect.height / 2)
private fun click(list: JBList<SettingsListItem>, point: Point) {
fire(list, mouse(list, MouseEvent.MOUSE_PRESSED, point))
fire(list, mouse(list, MouseEvent.MOUSE_RELEASED, point))
}
private fun mouse(list: JBList<SettingsListItem>, id: Int, point: Point, count: Int = 1) = MouseEvent(
list,
id,
System.currentTimeMillis(),
if (id == MouseEvent.MOUSE_PRESSED) InputEvent.BUTTON1_DOWN_MASK else 0,
point.x,
point.y,
count,
false,
MouseEvent.BUTTON1,
)
private fun <T> edt(block: () -> T): T {
var result: T? = null
ApplicationManager.getApplication().invokeAndWait { result = block() }
@Suppress("UNCHECKED_CAST")
return result as T
}
private fun flushUntil(done: () -> Boolean) = runBlocking {
repeat(300) {
delay(10)
edt { UIUtil.dispatchAllInvocationEvents(); true }
if (done()) return@runBlocking
}
edt { UIUtil.dispatchAllInvocationEvents(); true }
assertTrue(done())
}
private companion object {
const val DIR = "/test"
const val CUSTOM = "/home/test/.config/kilo/skill/plan/SKILL.md"
const val REMOTE = "/home/test/.cache/kilo/skills/remote/SKILL.md"
}
}
private class FakeSkillDialog(private val text: String, private val show: () -> Unit = {}) : SkillEditDialogHandle {
override fun showAndGet(): Boolean {
show()
return true
}
override fun content() = text
}
@@ -16,6 +16,9 @@ import java.awt.Dimension
import java.awt.Point
import java.awt.event.InputEvent
import java.awt.event.MouseEvent
import javax.swing.ListSelectionModel
import javax.swing.Scrollable
import javax.swing.SwingConstants
import javax.swing.SwingUtilities
class SettingsListViewTest : BasePlatformTestCase() {
@@ -250,6 +253,25 @@ class SettingsListViewTest : BasePlatformTestCase() {
}
}
fun `test action click invokes on second selected row in multi selection list`() {
edt {
val calls = mutableListOf<String>()
val cfg = SettingsListConfig.Equal.copy(selection = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
val view = SettingsListView("Empty", cfg) { key, id -> calls += "$key:$id" }
view.update(listOf(
item("a", "Alpha", null, SettingsListCell("edit", "Edit", alwaysVisible = false)),
item("b", "Beta", null, SettingsListCell("edit", "Edit", alwaysVisible = false)),
))
layout(view)
view.list.selectedIndices = intArrayOf(0, 1)
val area = settingsListCellBounds(view.list, 1, selected = true).getValue("edit")
click(view, center(area))
assertEquals(listOf("b:edit"), calls)
}
}
fun `test preserve no scroll keeps scroll position after row change`() {
edt {
val view = SettingsListView("Empty") { _, _ -> }
@@ -300,6 +322,17 @@ class SettingsListViewTest : BasePlatformTestCase() {
}
}
fun `test list view tracks viewport width`() {
edt {
val view = SettingsListView("Empty") { _, _ -> }
view.update(listOf(item("long", "Alpha", "A very long description that should wrap instead of scrolling")))
assertTrue((view as Scrollable).getScrollableTracksViewportWidth())
assertFalse(view.getScrollableTracksViewportHeight())
assertEquals(160, view.getScrollableBlockIncrement(java.awt.Rectangle(0, 0, 320, 160), SwingConstants.VERTICAL, 1))
}
}
private fun item(id: String, name: String, note: String?, vararg cells: SettingsListCell) = object : SettingsListItem {
override val key = id
override val title = name
@@ -11,9 +11,14 @@ import ai.kilocode.rpc.dto.SkillDto
class FakeAgentBehaviorRpcApi : KiloAgentBehaviorRpcApi {
var agents = emptyList<AgentDetailDto>()
var skills = emptyList<SkillDto>()
var mcps = emptyList<McpStatusDto>()
var mcpConfigs = emptyMap<String, McpServerConfigDto>()
val agentCalls = mutableListOf<String>()
val skillCalls = mutableListOf<String>()
val skillRemovals = mutableListOf<Pair<String, String>>()
val skillReloads = mutableListOf<String>()
val skillSaves = mutableListOf<Triple<String, String, String>>()
val mcpCalls = mutableListOf<String>()
val mcpConfigCalls = mutableListOf<String>()
val mcpSaves = mutableListOf<Triple<String, String, McpConfigDto?>>()
@@ -27,10 +32,16 @@ class FakeAgentBehaviorRpcApi : KiloAgentBehaviorRpcApi {
var afterRemove: (suspend (String, String) -> Unit)? = null
var afterMcpConnect: (suspend (String, String) -> Unit)? = null
var createError: Exception? = null
var skillsError: Exception? = null
var removeError: Exception? = null
var removeSkillError: Exception? = null
var saveSkillError: Exception? = null
var mcpStatusError: Exception? = null
var mcpConnectError: Exception? = null
var removeResult = true
var removeSkillResult = true
var reloadSkillResult = true
var saveSkillResult = true
var mcpConnectResult = true
var mcpDisconnectResult = true
var mcpAuthenticateResult = true
@@ -43,12 +54,41 @@ class FakeAgentBehaviorRpcApi : KiloAgentBehaviorRpcApi {
override suspend fun skills(directory: String): List<SkillDto> {
assertNotEdt("agentBehavior.skills")
return emptyList()
skillsError?.let { throw it }
skillCalls.add(directory)
return skills
}
override suspend fun removeSkill(directory: String, location: String): Boolean {
assertNotEdt("agentBehavior.removeSkill")
return false
removeSkillError?.let { throw it }
skillRemovals.add(directory to location)
if (removeSkillResult) skills = skills.filterNot { it.location == location }
return removeSkillResult
}
override suspend fun reloadSkills(directory: String): Boolean {
assertNotEdt("agentBehavior.reloadSkills")
skillReloads.add(directory)
return reloadSkillResult
}
override suspend fun saveSkill(directory: String, location: String, content: String): Boolean {
assertNotEdt("agentBehavior.saveSkill")
saveSkillError?.let { throw it }
skillSaves.add(Triple(directory, location, content))
if (saveSkillResult) skills = skills.map { if (it.location == location) it.copy(content = content) else it }
return saveSkillResult
}
override suspend fun saveSkills(directory: String, edits: Map<String, String>): Boolean {
assertNotEdt("agentBehavior.saveSkills")
saveSkillError?.let { throw it }
for ((location, content) in edits) skillSaves.add(Triple(directory, location, content))
if (saveSkillResult) skills = skills.map { skill ->
edits[skill.location]?.let { skill.copy(content = it) } ?: skill
}
return saveSkillResult
}
override suspend fun removeAgent(directory: String, name: String): Boolean {
@@ -26,6 +26,12 @@ interface KiloAgentBehaviorRpcApi : RemoteApi<Unit> {
suspend fun removeSkill(directory: String, location: String): Boolean
suspend fun reloadSkills(directory: String): Boolean
suspend fun saveSkill(directory: String, location: String, content: String): Boolean
suspend fun saveSkills(directory: String, edits: Map<String, String>): Boolean
suspend fun removeAgent(directory: String, name: String): Boolean
suspend fun createAgent(directory: String, input: AgentCreateDto): Boolean
@@ -7,4 +7,6 @@ data class SkillDto(
val name: String,
val description: String? = null,
val location: String,
val content: String? = null,
val editable: Boolean = false,
)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-memory",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"description": "Project memory storage, indexing, recall, and command helpers for Kilo Code",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/sandbox",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"private": true,
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-telemetry",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-ui",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"exports": {
+41
View File
@@ -1,5 +1,46 @@
# kilo-code
## 7.4.13
### Minor Changes
- [#12271](https://github.com/Kilo-Org/kilocode/pull/12271) [`38013f7`](https://github.com/Kilo-Org/kilocode/commit/38013f70fad82f55b9ebe02d8d6883a26d791934) - Allow agents to stop and remove a targeted Agent Manager session.
- [#12401](https://github.com/Kilo-Org/kilocode/pull/12401) [`210a6bb`](https://github.com/Kilo-Org/kilocode/commit/210a6bbf9b89b30b408d744cf0f253482370eed6) - Support choosing a reasoning effort per model in the Agent Manager Compare Models picker, so compared worktrees can run the same prompt at different effort levels. The selected effort is shown next to the model name in the collapsed selector.
- [#12306](https://github.com/Kilo-Org/kilocode/pull/12306) [`c081f58`](https://github.com/Kilo-Org/kilocode/commit/c081f582abecaba98a303069140d014a9ee90ca9) - Configure a custom file extension allowlist for codebase indexing to limit scans to relevant project files and support additional text formats.
- [#12292](https://github.com/Kilo-Org/kilocode/pull/12292) [`6dcaeb3`](https://github.com/Kilo-Org/kilocode/commit/6dcaeb3e97f256e9e98d5093610a0a02aedf84de) - Add JetBrains Context settings for compaction and file watcher ignore patterns.
- [#12403](https://github.com/Kilo-Org/kilocode/pull/12403) [`5819c68`](https://github.com/Kilo-Org/kilocode/commit/5819c689961ccf61f510896c5477a96e9d2a3459) - Cycle reasoning effort variants with Shift+Tab in prompt inputs. Works in the sidebar chat, Agent Manager, and the New Worktree dialog. The variant selector tooltip shows the shortcut on hover, and the behavior can be turned off with the `kilo-code.new.chat.shiftTabCyclesVariant` setting (also available under Settings > Display) to restore Shift+Tab focus navigation.
### Patch Changes
- [#12301](https://github.com/Kilo-Org/kilocode/pull/12301) [`d854c10`](https://github.com/Kilo-Org/kilocode/commit/d854c1077da0b55afe454c86b43c3240cdac053c) - Respect the Terminal Command Blocks display setting for background process tool cards.
- [#12304](https://github.com/Kilo-Org/kilocode/pull/12304) [`79fe757`](https://github.com/Kilo-Org/kilocode/commit/79fe75745fc6abd7bd3aad0c079e8f5150751a2c) - Display here-document content as plain text in terminal approval prompts.
- [#11944](https://github.com/Kilo-Org/kilocode/pull/11944) [`f857248`](https://github.com/Kilo-Org/kilocode/commit/f85724830c6483d22368d3ad75d12639e1488733) Thanks [@mjnaderi](https://github.com/mjnaderi)! - Support bidirectional text in chat messages so right-to-left and mixed-language conversations render in the correct direction.
- [#12302](https://github.com/Kilo-Org/kilocode/pull/12302) [`ad66c90`](https://github.com/Kilo-Org/kilocode/commit/ad66c908a4bec5f29e6d83fa9efbd1f6cc2cd416) - Clarify when reverting a conversation does not restore workspace changes and link disabled snapshots to the Checkpoints setting.
- [#12279](https://github.com/Kilo-Org/kilocode/pull/12279) [`6ce16e7`](https://github.com/Kilo-Org/kilocode/commit/6ce16e7cae1c58b545d5eaf00f1c6c44830e22e6) - Open the Agent Manager new-worktree dialog from primary actions and keyboard shortcuts, with an explicit quick-create action in its dropdown.
- [#12415](https://github.com/Kilo-Org/kilocode/pull/12415) [`a695930`](https://github.com/Kilo-Org/kilocode/commit/a695930d792ce3882cf3d7231ebc9be0e2adfa13) - Fix diff content painting through and above the sticky headers of expanded Edit, Write, and Apply Patch tool cards when scrolling large diffs.
- [#12213](https://github.com/Kilo-Org/kilocode/pull/12213) [`55fe160`](https://github.com/Kilo-Org/kilocode/commit/55fe1608fb24705258232665f1adfff0b4227e24) - Keep sessions usable after deprecated local review commands complete without a session status transition.
- [#12378](https://github.com/Kilo-Org/kilocode/pull/12378) [`b402cc2`](https://github.com/Kilo-Org/kilocode/commit/b402cc2635c1dae836a684f9d7a981c05491a930) - Include image-output models in the Kilo Gateway chat model list.
- [#12401](https://github.com/Kilo-Org/kilocode/pull/12401) [`210a6bb`](https://github.com/Kilo-Org/kilocode/commit/210a6bbf9b89b30b408d744cf0f253482370eed6) - Fix double scrollbars in the Agent Manager new-worktree prompt field and widen the dialog so longer prompts stay readable. The prompt box now grows with its content like the sidebar chat input, the textarea is the only element that scrolls, and manual resize of the prompt area keeps working.
- Updated dependencies [[`084bcea`](https://github.com/Kilo-Org/kilocode/commit/084bceadaedf193568ccf71256bd299c0d11e90c), [`c081f58`](https://github.com/Kilo-Org/kilocode/commit/c081f582abecaba98a303069140d014a9ee90ca9), [`ff703fb`](https://github.com/Kilo-Org/kilocode/commit/ff703fba621e35a4a5d8e4801620502228cca5bf)]:
- @kilocode/kilo-gateway@7.4.12
- @kilocode/kilo-indexing@7.5.0
- @opencode-ai/core@7.4.12
- @kilocode/kilo-ui@7.4.12
- @opencode-ai/ui@7.4.12
## 7.4.11
### Minor Changes
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "kilo-code",
"displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete",
"description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.",
"version": "7.4.11",
"version": "7.4.13",
"icon": "assets/icons/logo-outline-black.png",
"galleryBanner": {
"color": "#FFFFFF",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"type": "module",
"version": "7.4.11",
"version": "7.4.13",
"dependencies": {},
"devDependencies": {},
"peerDependencies": {}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-web-ui",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.4.11",
"version": "7.4.13",
"name": "@opencode-ai/llm",
"type": "module",
"license": "MIT",
+34
View File
@@ -1,5 +1,39 @@
# @kilocode/cli
## 7.4.13
### Minor Changes
- [#12271](https://github.com/Kilo-Org/kilocode/pull/12271) [`38013f7`](https://github.com/Kilo-Org/kilocode/commit/38013f70fad82f55b9ebe02d8d6883a26d791934) - Allow agents to stop and remove a targeted Agent Manager session.
- [#12306](https://github.com/Kilo-Org/kilocode/pull/12306) [`c081f58`](https://github.com/Kilo-Org/kilocode/commit/c081f582abecaba98a303069140d014a9ee90ca9) - Configure a custom file extension allowlist for codebase indexing to limit scans to relevant project files and support additional text formats.
- [#12224](https://github.com/Kilo-Org/kilocode/pull/12224) [`df9e1fb`](https://github.com/Kilo-Org/kilocode/commit/df9e1fb1e95275d600a1eb1e969ab19279e95417) - Remote CLI: expose slash command discovery, execution, and `/new` session creation over the relay. `list_commands` and `send_command` (including the built-in `compact` flow) are scoped to the current session's directory. `create_session` creates a root session in that directory, attaches it to the relay heartbeat set, and returns the new session id only after the heartbeat completes so the mobile client can navigate immediately. Failures are sanitized; the command is not auto-retried and the user may retry manually after a transient relay failure.
### Patch Changes
- [#12303](https://github.com/Kilo-Org/kilocode/pull/12303) [`7c07a47`](https://github.com/Kilo-Org/kilocode/commit/7c07a474660e8a5c9636778ad7d2b2fd8e233157) - Improve CLI sidebar usage sections with cent-formatted costs, collapsible details, and aligned model totals.
- [#12304](https://github.com/Kilo-Org/kilocode/pull/12304) [`79fe757`](https://github.com/Kilo-Org/kilocode/commit/79fe75745fc6abd7bd3aad0c079e8f5150751a2c) - Display here-document content as plain text in terminal approval prompts.
- [#12302](https://github.com/Kilo-Org/kilocode/pull/12302) [`ad66c90`](https://github.com/Kilo-Org/kilocode/commit/ad66c908a4bec5f29e6d83fa9efbd1f6cc2cd416) - Clarify when reverting a conversation does not restore workspace changes and link disabled snapshots to the Checkpoints setting.
- [#12329](https://github.com/Kilo-Org/kilocode/pull/12329) [`084bcea`](https://github.com/Kilo-Org/kilocode/commit/084bceadaedf193568ccf71256bd299c0d11e90c) - Fix Cloud Agent session imports in installed CLI builds and prevent malformed exports or write failures from leaving partial imports.
- [#12314](https://github.com/Kilo-Org/kilocode/pull/12314) [`520679a`](https://github.com/Kilo-Org/kilocode/commit/520679a91f320656cb4dbffe74f0a37e521d45c2) - Keep the CLI sidebar branch label in sync when Git branches change outside Kilo.
- [#12378](https://github.com/Kilo-Org/kilocode/pull/12378) [`b402cc2`](https://github.com/Kilo-Org/kilocode/commit/b402cc2635c1dae836a684f9d7a981c05491a930) - Include image-output models in the Kilo Gateway chat model list.
- [#12335](https://github.com/Kilo-Org/kilocode/pull/12335) [`99227f6`](https://github.com/Kilo-Org/kilocode/commit/99227f67478b44b06a18935792bd655c774f174a) Thanks [@shssoichiro](https://github.com/shssoichiro)! - Start the TUI from standalone CLI installations without requiring project-local OpenTUI dependencies.
- Updated dependencies [[`084bcea`](https://github.com/Kilo-Org/kilocode/commit/084bceadaedf193568ccf71256bd299c0d11e90c), [`c081f58`](https://github.com/Kilo-Org/kilocode/commit/c081f582abecaba98a303069140d014a9ee90ca9), [`ff703fb`](https://github.com/Kilo-Org/kilocode/commit/ff703fba621e35a4a5d8e4801620502228cca5bf)]:
- @kilocode/kilo-gateway@7.4.12
- @kilocode/kilo-indexing@7.5.0
- @kilocode/kilo-telemetry@7.4.12
- @opencode-ai/server@7.4.12
- @opencode-ai/tui@7.4.9
- @opencode-ai/ui@7.4.12
## 7.4.11
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.4.11",
"version": "7.4.13",
"name": "@kilocode/cli",
"type": "module",
"license": "MIT",
+8 -1
View File
@@ -15,6 +15,7 @@ import { importCloudSession, localSessionID, validateCloudFork } from "@/kilocod
import { createKiloClient } from "@kilocode/sdk/v2" // kilocode_change
import { writeHeapSnapshot } from "v8"
import { KiloTuiThreadDaemon, type StartInput } from "@/kilocode/cli/cmd/tui/thread" // kilocode_change
import { preload } from "@/kilocode/cli/cmd/tui" // kilocode_change
import { win32InstallCtrlCGuard } from "@opencode-ai/tui/terminal-win32"
import { validateSession } from "../tui/validate-session"
// kilocode_change start - correlate the TUI worker with its parent process
@@ -203,6 +204,12 @@ export const TuiThreadCommand = cmd({
// chdir so the thread and worker share the same directory key.
const next = resolveThreadDirectory(args.project)
const file = await target()
// kilocode_change start
const preloads = preload(
typeof KILO_WORKER_PATH !== "undefined",
() => import.meta.resolve("@opentui/solid/preload"),
)
// kilocode_change end
try {
process.chdir(next)
} catch {
@@ -223,7 +230,7 @@ export const TuiThreadCommand = cmd({
})
// kilocode_change end
const worker = new Worker(file, {
preload: ["@opentui/solid/preload"], // kilocode_change - Bun workers do not inherit the parent preload
preload: preloads, // kilocode_change
env, // kilocode_change
})
worker.onerror = (e) => {
@@ -0,0 +1,4 @@
export function preload(compiled: boolean, resolve: () => string) {
if (compiled) return []
return [resolve()]
}
@@ -7,6 +7,7 @@ import {
resolveThreadDirectory,
runEmbeddedRemoteExitBridge,
} from "../../../../src/cli/cmd/tui"
import { preload } from "../../../../src/kilocode/cli/cmd/tui"
import { KiloTuiThreadDaemon } from "../../../../src/kilocode/cli/cmd/tui/thread"
import { DaemonClient } from "../../../../src/kilocode/daemon/client"
@@ -15,6 +16,31 @@ afterEach(() => {
})
describe("kilo tui thread", () => {
test("skips preload resolver invocation in compiled mode", () => {
let calls = 0
expect(
preload(true, () => {
calls++
return "/resolved/preload"
}),
).toEqual([])
expect(calls).toBe(0)
})
test("resolves the preload once in source mode", () => {
let calls = 0
const path = "/resolved/preload"
expect(
preload(false, () => {
calls++
return path
}),
).toEqual([path])
expect(calls).toBe(1)
})
test("ignores stale PWD after cwd is changed by a process wrapper", async () => {
await using root = await tmpdir()
const pkg = path.join(root.path, "packages", "opencode")
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/plugin-atomic-chat",
"version": "7.4.11",
"version": "7.4.13",
"description": "Kilo Code plugin for Atomic Chat: auto-detection and dynamic model discovery (OpenAI-compatible local API)",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/plugin",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -12,6 +12,6 @@
"exports": {
".": "./src/index.ts"
},
"version": "7.4.11",
"version": "7.4.13",
"peerDependencies": {}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/sdk",
"version": "7.4.11",
"version": "7.4.13",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/server",
"version": "7.4.11",
"version": "7.4.13",
"private": true,
"type": "module",
"license": "MIT",

Some files were not shown because too many files have changed in this diff Show More