mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
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:
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Allow agents to stop and remove a targeted Agent Manager session.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Improve CLI sidebar usage sections with cent-formatted costs, collapsible details, and aligned model totals.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Respect the Terminal Command Blocks display setting for background process tool cards.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Display here-document content as plain text in terminal approval prompts.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": minor
|
||||
---
|
||||
|
||||
Support viewing, opening, editing, deleting, and configuring JetBrains skill sources.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Keep the CLI sidebar branch label in sync when Git branches change outside Kilo.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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,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,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,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",
|
||||
|
||||
@@ -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,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,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.4.11",
|
||||
"version": "7.4.13",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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,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,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,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",
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
@@ -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)
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
+6
-1
@@ -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> =
|
||||
|
||||
+159
-1
@@ -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,
|
||||
|
||||
+2
@@ -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(
|
||||
|
||||
+1
-1
@@ -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? {
|
||||
|
||||
+1
@@ -254,6 +254,7 @@ class KiloBackendWorkspace(
|
||||
name = s.name,
|
||||
description = s.description,
|
||||
location = s.location,
|
||||
content = s.content,
|
||||
)
|
||||
})
|
||||
} catch (e: CancellationException) {
|
||||
|
||||
+1
@@ -143,4 +143,5 @@ data class SkillInfo(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val location: String,
|
||||
val content: String?,
|
||||
)
|
||||
|
||||
+11
-3
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+152
@@ -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"]}}}"""
|
||||
|
||||
+11
@@ -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" ->
|
||||
|
||||
+9
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -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) } }
|
||||
|
||||
+9
@@ -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) }
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+586
@@ -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
|
||||
+5
@@ -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()
|
||||
|
||||
+3
@@ -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
|
||||
|
||||
+50
-6
@@ -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)
|
||||
|
||||
+8
@@ -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 à l’agent 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 s’ouvrira après la fermeture des paramètres.
|
||||
settings.agentBehavior.skills.openInEditor.failed=Impossible d’ouvrir 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 l’URL 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 URL’s 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ść
|
||||
|
||||
+27
@@ -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=Aç
|
||||
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}
|
||||
|
||||
+27
@@ -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} 条消息
|
||||
|
||||
+27
@@ -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} 則訊息
|
||||
|
||||
+31
@@ -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"))
|
||||
|
||||
|
||||
+2
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+615
@@ -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
|
||||
}
|
||||
+33
@@ -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
|
||||
|
||||
+42
-2
@@ -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 {
|
||||
|
||||
+6
@@ -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,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,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,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,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.4.11",
|
||||
"version": "7.4.13",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"version": "7.4.11",
|
||||
"version": "7.4.13",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"peerDependencies": {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-web-ui",
|
||||
"version": "7.4.11",
|
||||
"version": "7.4.13",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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,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,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": {
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"version": "7.4.11",
|
||||
"version": "7.4.13",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -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,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
Reference in New Issue
Block a user