diff --git a/bun.lock b/bun.lock
index 8aa2da8f82c..b85fb6a3954 100644
--- a/bun.lock
+++ b/bun.lock
@@ -27,7 +27,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@kilocode/kilo-i18n": "workspace:*",
"@kilocode/kilo-ui": "workspace:*",
@@ -79,7 +79,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -112,7 +112,7 @@
},
"packages/desktop-electron": {
"name": "@opencode-ai/desktop-electron",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -142,7 +142,7 @@
},
"packages/kilo-docs": {
"name": "@kilocode/kilo-docs",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@docsearch/css": "^4",
"@docsearch/js": "^4",
@@ -171,7 +171,7 @@
},
"packages/kilo-gateway": {
"name": "@kilocode/kilo-gateway",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@clack/prompts": "1.0.0-alpha.1",
"@kilocode/plugin": "workspace:*",
@@ -203,7 +203,7 @@
},
"packages/kilo-i18n": {
"name": "@kilocode/kilo-i18n",
- "version": "7.0.48",
+ "version": "7.0.49",
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
@@ -213,7 +213,7 @@
},
"packages/kilo-telemetry": {
"name": "@kilocode/kilo-telemetry",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@kilocode/kilo-gateway": "workspace:*",
"@opentelemetry/api": "1.9.0",
@@ -233,7 +233,7 @@
},
"packages/kilo-ui": {
"name": "@kilocode/kilo-ui",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@kobalte/core": "0.13.11",
"@opencode-ai/util": "workspace:*",
@@ -263,7 +263,7 @@
},
"packages/kilo-vscode": {
"name": "kilo-code",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@kilocode/kilo-i18n": "workspace:*",
@@ -315,7 +315,7 @@
},
"packages/opencode": {
"name": "@kilocode/cli",
- "version": "7.0.48",
+ "version": "7.0.49",
"bin": {
"opencode": "./bin/opencode",
},
@@ -435,7 +435,7 @@
},
"packages/plugin": {
"name": "@kilocode/plugin",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"zod": "catalog:",
@@ -449,14 +449,14 @@
},
"packages/script": {
"name": "@opencode-ai/script",
- "version": "7.0.48",
+ "version": "7.0.49",
"devDependencies": {
"@types/bun": "catalog:",
},
},
"packages/sdk/js": {
"name": "@kilocode/sdk",
- "version": "7.0.48",
+ "version": "7.0.49",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -467,7 +467,7 @@
},
"packages/storybook": {
"name": "@opencode-ai/storybook",
- "version": "7.0.48",
+ "version": "7.0.49",
"devDependencies": {
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
@@ -490,7 +490,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "catalog:",
@@ -537,7 +537,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "7.0.48",
+ "version": "7.0.49",
"dependencies": {
"zod": "catalog:",
},
@@ -581,7 +581,7 @@
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@types/semver": "7.7.1",
- "@typescript/native-preview": "7.0.0-dev.20251207.1",
+ "@typescript/native-preview": "7.0.0-dev.20260316.1",
"ai": "5.0.124",
"diff": "8.0.2",
"dompurify": "3.3.1",
@@ -2062,21 +2062,21 @@
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.1", "", { "dependencies": { "@typescript-eslint/types": "8.57.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A=="],
- "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251207.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251207.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-4QcRnzB0pi9rS0AOvg8kWbmuwHv5X7B2EXHbgcms9+56hsZ8SZrZjNgBJb2rUIodJ4kU5mrkj/xlTTT4r9VcpQ=="],
+ "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260316.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260316.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260316.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260316.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260316.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260316.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260316.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260316.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-s+QGNx+3zxTZBuZw3oNOFlHqpbmg0cTgBd/b6SRZ5mo3vFChkhflYqRW2IvTvU9a3PPX3bQAkQ/gWbDZCmNC3Q=="],
- "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251207.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-waWJnuuvkXh4WdpbTjYf7pyahJzx0ycesV2BylyHrE9OxU9FSKcD/cRLQYvbq3YcBSdF7sZwRLDBer7qTeLsYA=="],
+ "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260316.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TjeMEMabLsc5VNYy8WVlu1oHBVqibwSbkIRSyqANFxyD6iWnCFquDvliwErVo8TFIu0c8C+C+tgFSvYkhVZMMw=="],
- "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20251207.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-3bkD9QuIjxETtp6J1l5X2oKgudJ8z+8fwUq0izCjK1JrIs2vW1aQnbzxhynErSyHWH7URGhHHzcsXHbikckAsg=="],
+ "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260316.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Lv/JmtMfNbMJiIEZlByQ5zSR1t9WoE8rFuZxU0vpiyfUEjSbuBMG8pt+Ryqj6uiylR3XThlV3EaVYsJ7Um6n8w=="],
- "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20251207.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OjrZBq8XJkB7uCQvT1AZ1FPsp+lT0cHxY5SisE+ZTAU6V0IHAZMwJ7J/mnwlGsBcCKRLBT+lX3hgEuOTSwHr9w=="],
+ "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260316.1", "", { "os": "linux", "cpu": "arm" }, "sha512-vItkqjOuVY9OfqdovSyEjnAbNMM+QGM9AqzGRknX1nZjGlWXsUTL3IPuv5by69SOqw5TLi8ddx82cyu6F3ZRVQ=="],
- "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20251207.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qhp06OObkwy5B+PlAhAmq+Ls3GVt4LHAovrTRcpLB3Mk3yJ0h9DnIQwPQiayp16TdvTsGHI3jdIX4MGm5L/ghA=="],
+ "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260316.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-xA4DekkAesjnWyp8p0iF79Rf0q2NVszxedd9M2Ztb0WBSDQFiECVYJSQMFd4+FKNiSq9DnadPy68Dly+B1r17A=="],
- "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20251207.1", "", { "os": "linux", "cpu": "x64" }, "sha512-fPRw0zfTBeVmrkgi5Le+sSwoeAz6pIdvcsa1OYZcrspueS9hn3qSC5bLEc5yX4NJP1vItadBqyGLUQ7u8FJjow=="],
+ "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260316.1", "", { "os": "linux", "cpu": "x64" }, "sha512-osY+4HCIpi9Bu4jNz49k8BVOB9A04BG6mWF7WltmAQWBIAeosa4n/qtKokfAZDTD5/moHSn20p7hZAlGI8JWjw=="],
- "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20251207.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-KxY1i+HxeSFfzZ+HVsKwMGBM79laTRZv1ibFqHu22CEsfSPDt4yiV1QFis8Nw7OBXswNqJG/UGqY47VP8FeTvw=="],
+ "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260316.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-DcWceiTXClIakJhk0+8KjQ+pBp435HaA6uw9EtDTo75uWUEPVf9D489KKbylRChci/paYX8uPKlROo9+6N8M9g=="],
- "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20251207.1", "", { "os": "win32", "cpu": "x64" }, "sha512-5l51HlXjX7lXwo65DEl1IaCFLjmkMtL6K3NrSEamPNeNTtTQwZRa3pQ9V65dCglnnCQ0M3+VF1RqzC7FU0iDKg=="],
+ "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260316.1", "", { "os": "win32", "cpu": "x64" }, "sha512-LvpV1hyQS0U9yMLHgWexhC7oSeBpcNbIJtYC6Iyvu63Mb6J/cP0k2fQmnAVB2yesMMQFtuY6v2YIx17vE0Ymfw=="],
"@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="],
diff --git a/package.json b/package.json
index ae4d503a043..319046d444b 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
"marked-shiki": "1.2.1",
"@playwright/test": "1.51.0",
"typescript": "5.8.2",
- "@typescript/native-preview": "7.0.0-dev.20251207.1",
+ "@typescript/native-preview": "7.0.0-dev.20260316.1",
"zod": "4.1.8",
"remeda": "2.26.0",
"shiki": "3.20.0",
@@ -111,6 +111,6 @@
"@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch",
"ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch"
},
- "version": "7.0.48",
+ "version": "7.0.49",
"peerDependencies": {}
}
diff --git a/packages/app/package.json b/packages/app/package.json
index fd5448f6ebd..8437ca211c6 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "7.0.48",
+ "version": "7.0.49",
"description": "",
"type": "module",
"exports": {
diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json
index e2a27dd5d8a..74db2030324 100644
--- a/packages/app/tsconfig.json
+++ b/packages/app/tsconfig.json
@@ -17,6 +17,7 @@
"emitDeclarationOnly": true,
"outDir": "node_modules/.ts-dist",
"isolatedModules": true,
+ "types": ["bun", "node"],
"paths": {
"@/*": ["./src/*"]
}
diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json
index b2ab498bef6..a1bd829df8b 100644
--- a/packages/desktop-electron/package.json
+++ b/packages/desktop-electron/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop-electron",
"private": true,
- "version": "7.0.48",
+ "version": "7.0.49",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index b5963b83fa7..dcdd9e87cf0 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
- "version": "7.0.48",
+ "version": "7.0.49",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index 02cfad03ef1..d6f807d1294 100644
--- a/packages/extensions/zed/extension.toml
+++ b/packages/extensions/zed/extension.toml
@@ -1,7 +1,7 @@
id = "kilo"
name = "Kilo"
description = "The open source coding agent."
-version = "1.2.21"
+version = "7.0.49"
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/v1.2.21/opencode-darwin-arm64.zip"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.49/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.21/opencode-darwin-x64.zip"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.49/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.21/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.49/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/v1.2.21/opencode-linux-x64.tar.gz"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.49/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/v1.2.21/opencode-windows-x64.zip"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.49/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json
index 56aca25365d..ee4bd54463a 100644
--- a/packages/kilo-docs/package.json
+++ b/packages/kilo-docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-docs",
- "version": "7.0.48",
+ "version": "7.0.49",
"private": true,
"scripts": {
"dev": "next dev --webpack --port 3002",
diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json
index 2acf10f9f6d..7cc37d52c04 100644
--- a/packages/kilo-gateway/package.json
+++ b/packages/kilo-gateway/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-gateway",
- "version": "7.0.48",
+ "version": "7.0.49",
"type": "module",
"license": "MIT",
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json
index 47633b39c95..53f1d0e1e72 100644
--- a/packages/kilo-i18n/package.json
+++ b/packages/kilo-i18n/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-i18n",
- "version": "7.0.48",
+ "version": "7.0.49",
"type": "module",
"license": "MIT",
"description": "Kilo-specific i18n translations and overrides",
diff --git a/packages/kilo-i18n/src/ar.ts b/packages/kilo-i18n/src/ar.ts
index 4abab3098f2..f61e3330f82 100644
--- a/packages/kilo-i18n/src/ar.ts
+++ b/packages/kilo-i18n/src/ar.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "المهارات",
"marketplace.tab.mcpServers": "خوادم MCP",
"marketplace.tab.modes": "الأوضاع",
- "marketplace.search.skills": "البحث في المهارات...",
"marketplace.category.all": "الكل",
- "marketplace.empty.skills": "لم يتم العثور على مهارات",
"marketplace.placeholder": "سيتم تنفيذه لاحقاً",
"marketplace.card.installed": "مثبت",
"marketplace.card.install": "تثبيت",
"marketplace.card.remove": "إزالة",
"marketplace.card.removeScope": "إزالة ({{scope}})",
+ "marketplace.card.showMore": "عرض المزيد",
+ "marketplace.card.showLess": "عرض أقل",
"marketplace.install.title": "تثبيت {{name}}",
"marketplace.install.scope": "النطاق",
"marketplace.install.scope.project": "المشروع",
@@ -43,4 +43,25 @@ export const dict = {
"هل أنت متأكد أنك تريد إزالة هذا الـ {{type}}؟ سيؤدي هذا إلى إزالته من تكوين {{scope}} الخاص بك.",
"marketplace.remove.cancel": "إلغاء",
"marketplace.remove.confirm.button": "إزالة",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "بحث...",
+ "marketplace.filter.all": "جميع العناصر",
+ "marketplace.filter.notInstalled": "غير مثبت",
+ "marketplace.empty": "لم يتم العثور على عناصر",
+ "marketplace.badge.mcpServer": "خادم MCP",
+ "marketplace.badge.mode": "وضع",
+ "marketplace.card.by": "بواسطة {{author}}",
+ "marketplace.install.method": "طريقة التثبيت",
+ "marketplace.install.parameters": "المعلمات",
+ "marketplace.install.optional": "(اختياري)",
+ "marketplace.install.required": "{{name}} مطلوب",
+ "marketplace.scope.project": "مشروع",
+ "marketplace.scope.global": "عام",
+ "marketplace.remove.type.mcp": "خادم MCP",
+ "marketplace.remove.type.skill": "مهارة",
+ "marketplace.remove.type.mode": "وضع",
+ "marketplace.remove.failed": "فشلت إزالة {{name}}",
+ "marketplace.install": "تثبيت",
+ "marketplace.filter.installed": "مثبت",
+ "marketplace.error.dismiss": "تجاهل",
}
diff --git a/packages/kilo-i18n/src/br.ts b/packages/kilo-i18n/src/br.ts
index cfa23e214d6..e5cdf8251d8 100644
--- a/packages/kilo-i18n/src/br.ts
+++ b/packages/kilo-i18n/src/br.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "Servidores MCP",
"marketplace.tab.modes": "Modos",
- "marketplace.search.skills": "Buscar skills...",
"marketplace.category.all": "Todos",
- "marketplace.empty.skills": "Nenhuma skill encontrada",
"marketplace.placeholder": "A ser implementado",
"marketplace.card.installed": "Instalado",
"marketplace.card.install": "Instalar",
"marketplace.card.remove": "Remover",
"marketplace.card.removeScope": "Remover ({{scope}})",
+ "marketplace.card.showMore": "Mostrar mais",
+ "marketplace.card.showLess": "Mostrar menos",
"marketplace.install.title": "Instalar {{name}}",
"marketplace.install.scope": "Escopo",
"marketplace.install.scope.project": "Projeto",
@@ -43,4 +43,25 @@ export const dict = {
"Tem certeza que deseja remover este {{type}}? Isso o removerá da sua configuração {{scope}}.",
"marketplace.remove.cancel": "Cancelar",
"marketplace.remove.confirm.button": "Remover",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Pesquisar...",
+ "marketplace.filter.all": "Todos os Itens",
+ "marketplace.filter.notInstalled": "Não Instalado",
+ "marketplace.empty": "Nenhum item encontrado",
+ "marketplace.badge.mcpServer": "Servidor MCP",
+ "marketplace.badge.mode": "Modo",
+ "marketplace.card.by": "por {{author}}",
+ "marketplace.install.method": "Método de Instalação",
+ "marketplace.install.parameters": "Parâmetros",
+ "marketplace.install.optional": "(opcional)",
+ "marketplace.install.required": "{{name}} é obrigatório",
+ "marketplace.scope.project": "projeto",
+ "marketplace.scope.global": "global",
+ "marketplace.remove.type.mcp": "servidor MCP",
+ "marketplace.remove.type.skill": "habilidade",
+ "marketplace.remove.type.mode": "modo",
+ "marketplace.remove.failed": "Falha ao remover {{name}}",
+ "marketplace.install": "Instalar",
+ "marketplace.filter.installed": "Instalado",
+ "marketplace.error.dismiss": "Dispensar",
}
diff --git a/packages/kilo-i18n/src/bs.ts b/packages/kilo-i18n/src/bs.ts
index aecc7423546..ddd8c5cffad 100644
--- a/packages/kilo-i18n/src/bs.ts
+++ b/packages/kilo-i18n/src/bs.ts
@@ -24,14 +24,14 @@ export const dict = {
"marketplace.tab.skills": "Vještine",
"marketplace.tab.mcpServers": "MCP Serveri",
"marketplace.tab.modes": "Modovi",
- "marketplace.search.skills": "Pretraži vještine...",
"marketplace.category.all": "Sve",
- "marketplace.empty.skills": "Nema pronađenih vještina",
"marketplace.placeholder": "Biće implementirano",
"marketplace.card.installed": "Instalirano",
"marketplace.card.install": "Instaliraj",
"marketplace.card.remove": "Ukloni",
"marketplace.card.removeScope": "Ukloni ({{scope}})",
+ "marketplace.card.showMore": "Prikaži više",
+ "marketplace.card.showLess": "Prikaži manje",
"marketplace.install.title": "Instaliraj {{name}}",
"marketplace.install.scope": "Opseg",
"marketplace.install.scope.project": "Projekat",
@@ -48,4 +48,25 @@ export const dict = {
"Jeste li sigurni da želite ukloniti ovaj {{type}}? Ovo će ga ukloniti iz vaše {{scope}} konfiguracije.",
"marketplace.remove.cancel": "Otkaži",
"marketplace.remove.confirm.button": "Ukloni",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Pretraži...",
+ "marketplace.filter.all": "Sve stavke",
+ "marketplace.filter.notInstalled": "Nije instalirano",
+ "marketplace.empty": "Nema pronađenih stavki",
+ "marketplace.badge.mcpServer": "MCP Server",
+ "marketplace.badge.mode": "Režim",
+ "marketplace.card.by": "od {{author}}",
+ "marketplace.install.method": "Metoda instalacije",
+ "marketplace.install.parameters": "Parametri",
+ "marketplace.install.optional": "(opciono)",
+ "marketplace.install.required": "{{name}} je obavezno",
+ "marketplace.scope.project": "projekat",
+ "marketplace.scope.global": "globalno",
+ "marketplace.remove.type.mcp": "MCP server",
+ "marketplace.remove.type.skill": "vještina",
+ "marketplace.remove.type.mode": "režim",
+ "marketplace.remove.failed": "Uklanjanje {{name}} nije uspjelo",
+ "marketplace.install": "Instaliraj",
+ "marketplace.filter.installed": "Instalirano",
+ "marketplace.error.dismiss": "Odbaci",
}
diff --git a/packages/kilo-i18n/src/da.ts b/packages/kilo-i18n/src/da.ts
index fb5541bdaeb..5de93df58a1 100644
--- a/packages/kilo-i18n/src/da.ts
+++ b/packages/kilo-i18n/src/da.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "MCP-servere",
"marketplace.tab.modes": "Tilstande",
- "marketplace.search.skills": "Søg efter skills...",
"marketplace.category.all": "Alle",
- "marketplace.empty.skills": "Ingen skills fundet",
"marketplace.placeholder": "Skal implementeres",
"marketplace.card.installed": "Installeret",
"marketplace.card.install": "Installer",
"marketplace.card.remove": "Fjern",
"marketplace.card.removeScope": "Fjern ({{scope}})",
+ "marketplace.card.showMore": "Vis mere",
+ "marketplace.card.showLess": "Vis mindre",
"marketplace.install.title": "Installer {{name}}",
"marketplace.install.scope": "Omfang",
"marketplace.install.scope.project": "Projekt",
@@ -43,4 +43,25 @@ export const dict = {
"Er du sikker på, at du vil fjerne denne {{type}}? Dette vil fjerne den fra din {{scope}} konfiguration.",
"marketplace.remove.cancel": "Annuller",
"marketplace.remove.confirm.button": "Fjern",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Søg...",
+ "marketplace.filter.all": "Alle elementer",
+ "marketplace.filter.notInstalled": "Ikke installeret",
+ "marketplace.empty": "Ingen elementer fundet",
+ "marketplace.badge.mcpServer": "MCP-server",
+ "marketplace.badge.mode": "Tilstand",
+ "marketplace.card.by": "af {{author}}",
+ "marketplace.install.method": "Installationsmetode",
+ "marketplace.install.parameters": "Parametre",
+ "marketplace.install.optional": "(valgfrit)",
+ "marketplace.install.required": "{{name}} er påkrævet",
+ "marketplace.scope.project": "projekt",
+ "marketplace.scope.global": "global",
+ "marketplace.remove.type.mcp": "MCP-server",
+ "marketplace.remove.type.skill": "færdighed",
+ "marketplace.remove.type.mode": "tilstand",
+ "marketplace.remove.failed": "Kunne ikke fjerne {{name}}",
+ "marketplace.install": "Installer",
+ "marketplace.filter.installed": "Installeret",
+ "marketplace.error.dismiss": "Afvis",
}
diff --git a/packages/kilo-i18n/src/de.ts b/packages/kilo-i18n/src/de.ts
index bd34499ade6..7952050b42d 100644
--- a/packages/kilo-i18n/src/de.ts
+++ b/packages/kilo-i18n/src/de.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "MCP-Server",
"marketplace.tab.modes": "Modi",
- "marketplace.search.skills": "Skills suchen...",
"marketplace.category.all": "Alle",
- "marketplace.empty.skills": "Keine Skills gefunden",
"marketplace.placeholder": "Noch nicht implementiert",
"marketplace.card.installed": "Installiert",
"marketplace.card.install": "Installieren",
"marketplace.card.remove": "Entfernen",
"marketplace.card.removeScope": "Entfernen ({{scope}})",
+ "marketplace.card.showMore": "Mehr anzeigen",
+ "marketplace.card.showLess": "Weniger anzeigen",
"marketplace.install.title": "{{name}} installieren",
"marketplace.install.scope": "Bereich",
"marketplace.install.scope.project": "Projekt",
@@ -43,4 +43,25 @@ export const dict = {
"Sind Sie sicher, dass Sie diese {{type}} entfernen möchten? Dadurch wird sie aus Ihrer {{scope}}-Konfiguration entfernt.",
"marketplace.remove.cancel": "Abbrechen",
"marketplace.remove.confirm.button": "Entfernen",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Suchen...",
+ "marketplace.filter.all": "Alle Elemente",
+ "marketplace.filter.notInstalled": "Nicht installiert",
+ "marketplace.empty": "Keine Elemente gefunden",
+ "marketplace.badge.mcpServer": "MCP-Server",
+ "marketplace.badge.mode": "Modus",
+ "marketplace.card.by": "von {{author}}",
+ "marketplace.install.method": "Installationsmethode",
+ "marketplace.install.parameters": "Parameter",
+ "marketplace.install.optional": "(optional)",
+ "marketplace.install.required": "{{name}} ist erforderlich",
+ "marketplace.scope.project": "Projekt",
+ "marketplace.scope.global": "Global",
+ "marketplace.remove.type.mcp": "MCP-Server",
+ "marketplace.remove.type.skill": "Skill",
+ "marketplace.remove.type.mode": "Modus",
+ "marketplace.remove.failed": "Fehler beim Entfernen von {{name}}",
+ "marketplace.install": "Installieren",
+ "marketplace.filter.installed": "Installiert",
+ "marketplace.error.dismiss": "Verwerfen",
}
diff --git a/packages/kilo-i18n/src/en.ts b/packages/kilo-i18n/src/en.ts
index 5cccd33c5c2..a2d0c30011d 100644
--- a/packages/kilo-i18n/src/en.ts
+++ b/packages/kilo-i18n/src/en.ts
@@ -21,14 +21,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "MCP Servers",
"marketplace.tab.modes": "Modes",
- "marketplace.search.skills": "Search skills...",
"marketplace.category.all": "All",
- "marketplace.empty.skills": "No skills found",
"marketplace.placeholder": "To be implemented",
"marketplace.card.installed": "Installed",
"marketplace.card.install": "Install",
"marketplace.card.remove": "Remove",
"marketplace.card.removeScope": "Remove ({{scope}})",
+ "marketplace.card.showMore": "Show more",
+ "marketplace.card.showLess": "Show less",
"marketplace.install.title": "Install {{name}}",
"marketplace.install.scope": "Scope",
"marketplace.install.scope.project": "Project",
@@ -45,4 +45,25 @@ export const dict = {
"Are you sure you want to remove this {{type}}? This will remove it from your {{scope}} configuration.",
"marketplace.remove.cancel": "Cancel",
"marketplace.remove.confirm.button": "Remove",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Search...",
+ "marketplace.filter.all": "All Items",
+ "marketplace.filter.notInstalled": "Not Installed",
+ "marketplace.empty": "No items found",
+ "marketplace.badge.mcpServer": "MCP Server",
+ "marketplace.badge.mode": "Mode",
+ "marketplace.card.by": "by {{author}}",
+ "marketplace.install.method": "Installation Method",
+ "marketplace.install.parameters": "Parameters",
+ "marketplace.install.optional": "(optional)",
+ "marketplace.install.required": "{{name}} is required",
+ "marketplace.scope.project": "project",
+ "marketplace.scope.global": "global",
+ "marketplace.remove.type.mcp": "MCP server",
+ "marketplace.remove.type.skill": "skill",
+ "marketplace.remove.type.mode": "mode",
+ "marketplace.remove.failed": "Failed to remove {{name}}",
+ "marketplace.install": "Install",
+ "marketplace.filter.installed": "Installed",
+ "marketplace.error.dismiss": "Dismiss",
}
diff --git a/packages/kilo-i18n/src/es.ts b/packages/kilo-i18n/src/es.ts
index cdedb7864b8..bede5eb3727 100644
--- a/packages/kilo-i18n/src/es.ts
+++ b/packages/kilo-i18n/src/es.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "Servidores MCP",
"marketplace.tab.modes": "Modos",
- "marketplace.search.skills": "Buscar skills...",
"marketplace.category.all": "Todos",
- "marketplace.empty.skills": "No se encontraron skills",
"marketplace.placeholder": "Por implementar",
"marketplace.card.installed": "Instalado",
"marketplace.card.install": "Instalar",
"marketplace.card.remove": "Eliminar",
"marketplace.card.removeScope": "Eliminar ({{scope}})",
+ "marketplace.card.showMore": "Mostrar más",
+ "marketplace.card.showLess": "Mostrar menos",
"marketplace.install.title": "Instalar {{name}}",
"marketplace.install.scope": "Alcance",
"marketplace.install.scope.project": "Proyecto",
@@ -43,4 +43,25 @@ export const dict = {
"¿Estás seguro de que deseas eliminar este {{type}}? Esto lo eliminará de tu configuración {{scope}}.",
"marketplace.remove.cancel": "Cancelar",
"marketplace.remove.confirm.button": "Eliminar",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Buscar...",
+ "marketplace.filter.all": "Todos los elementos",
+ "marketplace.filter.notInstalled": "No instalado",
+ "marketplace.empty": "No se encontraron elementos",
+ "marketplace.badge.mcpServer": "Servidor MCP",
+ "marketplace.badge.mode": "Modo",
+ "marketplace.card.by": "por {{author}}",
+ "marketplace.install.method": "Método de instalación",
+ "marketplace.install.parameters": "Parámetros",
+ "marketplace.install.optional": "(opcional)",
+ "marketplace.install.required": "{{name}} es obligatorio",
+ "marketplace.scope.project": "proyecto",
+ "marketplace.scope.global": "global",
+ "marketplace.remove.type.mcp": "servidor MCP",
+ "marketplace.remove.type.skill": "habilidad",
+ "marketplace.remove.type.mode": "modo",
+ "marketplace.remove.failed": "Error al eliminar {{name}}",
+ "marketplace.install": "Instalar",
+ "marketplace.filter.installed": "Instalado",
+ "marketplace.error.dismiss": "Descartar",
}
diff --git a/packages/kilo-i18n/src/fr.ts b/packages/kilo-i18n/src/fr.ts
index e741db9adc7..786758db4fd 100644
--- a/packages/kilo-i18n/src/fr.ts
+++ b/packages/kilo-i18n/src/fr.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "Serveurs MCP",
"marketplace.tab.modes": "Modes",
- "marketplace.search.skills": "Rechercher des skills...",
"marketplace.category.all": "Tout",
- "marketplace.empty.skills": "Aucune skill trouvée",
"marketplace.placeholder": "À implémenter",
"marketplace.card.installed": "Installé",
"marketplace.card.install": "Installer",
"marketplace.card.remove": "Supprimer",
"marketplace.card.removeScope": "Supprimer ({{scope}})",
+ "marketplace.card.showMore": "Afficher plus",
+ "marketplace.card.showLess": "Afficher moins",
"marketplace.install.title": "Installer {{name}}",
"marketplace.install.scope": "Portée",
"marketplace.install.scope.project": "Projet",
@@ -43,4 +43,25 @@ export const dict = {
"Êtes-vous sûr de vouloir supprimer ce {{type}} ? Cela le supprimera de votre configuration {{scope}}.",
"marketplace.remove.cancel": "Annuler",
"marketplace.remove.confirm.button": "Supprimer",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Rechercher...",
+ "marketplace.filter.all": "Tous les éléments",
+ "marketplace.filter.notInstalled": "Non installé",
+ "marketplace.empty": "Aucun élément trouvé",
+ "marketplace.badge.mcpServer": "Serveur MCP",
+ "marketplace.badge.mode": "Mode",
+ "marketplace.card.by": "par {{author}}",
+ "marketplace.install.method": "Méthode d'installation",
+ "marketplace.install.parameters": "Paramètres",
+ "marketplace.install.optional": "(facultatif)",
+ "marketplace.install.required": "{{name}} est requis",
+ "marketplace.scope.project": "projet",
+ "marketplace.scope.global": "global",
+ "marketplace.remove.type.mcp": "serveur MCP",
+ "marketplace.remove.type.skill": "compétence",
+ "marketplace.remove.type.mode": "mode",
+ "marketplace.remove.failed": "Échec de la suppression de {{name}}",
+ "marketplace.install": "Installer",
+ "marketplace.filter.installed": "Installé",
+ "marketplace.error.dismiss": "Ignorer",
}
diff --git a/packages/kilo-i18n/src/ja.ts b/packages/kilo-i18n/src/ja.ts
index 460697114ab..2f2658aaf06 100644
--- a/packages/kilo-i18n/src/ja.ts
+++ b/packages/kilo-i18n/src/ja.ts
@@ -18,14 +18,14 @@ export const dict = {
"marketplace.tab.skills": "スキル",
"marketplace.tab.mcpServers": "MCPサーバー",
"marketplace.tab.modes": "モード",
- "marketplace.search.skills": "スキルを検索...",
"marketplace.category.all": "すべて",
- "marketplace.empty.skills": "スキルが見つかりません",
"marketplace.placeholder": "未実装",
"marketplace.card.installed": "インストール済み",
"marketplace.card.install": "インストール",
"marketplace.card.remove": "削除",
"marketplace.card.removeScope": "削除 ({{scope}})",
+ "marketplace.card.showMore": "もっと見る",
+ "marketplace.card.showLess": "折りたたむ",
"marketplace.install.title": "{{name}} のインストール",
"marketplace.install.scope": "スコープ",
"marketplace.install.scope.project": "プロジェクト",
@@ -41,4 +41,25 @@ export const dict = {
"marketplace.remove.confirm": "この{{type}}を削除してもよろしいですか? これにより、{{scope}}設定から削除されます。",
"marketplace.remove.cancel": "キャンセル",
"marketplace.remove.confirm.button": "削除",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "検索...",
+ "marketplace.filter.all": "すべてのアイテム",
+ "marketplace.filter.notInstalled": "未インストール",
+ "marketplace.empty": "アイテムが見つかりません",
+ "marketplace.badge.mcpServer": "MCPサーバー",
+ "marketplace.badge.mode": "モード",
+ "marketplace.card.by": "作成者: {{author}}",
+ "marketplace.install.method": "インストール方法",
+ "marketplace.install.parameters": "パラメーター",
+ "marketplace.install.optional": "(任意)",
+ "marketplace.install.required": "{{name}} は必須です",
+ "marketplace.scope.project": "プロジェクト",
+ "marketplace.scope.global": "グローバル",
+ "marketplace.remove.type.mcp": "MCPサーバー",
+ "marketplace.remove.type.skill": "スキル",
+ "marketplace.remove.type.mode": "モード",
+ "marketplace.remove.failed": "{{name}} の削除に失敗しました",
+ "marketplace.install": "インストール",
+ "marketplace.filter.installed": "インストール済み",
+ "marketplace.error.dismiss": "閉じる",
}
diff --git a/packages/kilo-i18n/src/ko.ts b/packages/kilo-i18n/src/ko.ts
index 1d2d11e4443..e3bc71042b6 100644
--- a/packages/kilo-i18n/src/ko.ts
+++ b/packages/kilo-i18n/src/ko.ts
@@ -18,14 +18,14 @@ export const dict = {
"marketplace.tab.skills": "스킬",
"marketplace.tab.mcpServers": "MCP 서버",
"marketplace.tab.modes": "모드",
- "marketplace.search.skills": "스킬 검색...",
"marketplace.category.all": "전체",
- "marketplace.empty.skills": "스킬을 찾을 수 없습니다",
"marketplace.placeholder": "구현 예정",
"marketplace.card.installed": "설치됨",
"marketplace.card.install": "설치",
"marketplace.card.remove": "제거",
"marketplace.card.removeScope": "제거 ({{scope}})",
+ "marketplace.card.showMore": "더 보기",
+ "marketplace.card.showLess": "접기",
"marketplace.install.title": "{{name}} 설치",
"marketplace.install.scope": "범위",
"marketplace.install.scope.project": "프로젝트",
@@ -41,4 +41,25 @@ export const dict = {
"marketplace.remove.confirm": "이 {{type}}을(를) 제거하시겠습니까? {{scope}} 구성에서 제거됩니다.",
"marketplace.remove.cancel": "취소",
"marketplace.remove.confirm.button": "제거",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "검색...",
+ "marketplace.filter.all": "모든 항목",
+ "marketplace.filter.notInstalled": "설치되지 않음",
+ "marketplace.empty": "항목을 찾을 수 없음",
+ "marketplace.badge.mcpServer": "MCP 서버",
+ "marketplace.badge.mode": "모드",
+ "marketplace.card.by": "제작: {{author}}",
+ "marketplace.install.method": "설치 방법",
+ "marketplace.install.parameters": "매개변수",
+ "marketplace.install.optional": "(선택 사항)",
+ "marketplace.install.required": "{{name}}이(가) 필요합니다",
+ "marketplace.scope.project": "프로젝트",
+ "marketplace.scope.global": "글로벌",
+ "marketplace.remove.type.mcp": "MCP 서버",
+ "marketplace.remove.type.skill": "스킬",
+ "marketplace.remove.type.mode": "모드",
+ "marketplace.remove.failed": "{{name}} 제거 실패",
+ "marketplace.install": "설치",
+ "marketplace.filter.installed": "설치됨",
+ "marketplace.error.dismiss": "닫기",
}
diff --git a/packages/kilo-i18n/src/no.ts b/packages/kilo-i18n/src/no.ts
index 70e5f3a3e0c..6e1687e1a67 100644
--- a/packages/kilo-i18n/src/no.ts
+++ b/packages/kilo-i18n/src/no.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "MCP-servere",
"marketplace.tab.modes": "Moduser",
- "marketplace.search.skills": "Søk etter skills...",
"marketplace.category.all": "Alle",
- "marketplace.empty.skills": "Ingen skills funnet",
"marketplace.placeholder": "Skal implementeres",
"marketplace.card.installed": "Installert",
"marketplace.card.install": "Installer",
"marketplace.card.remove": "Fjern",
"marketplace.card.removeScope": "Fjern ({{scope}})",
+ "marketplace.card.showMore": "Vis mer",
+ "marketplace.card.showLess": "Vis mindre",
"marketplace.install.title": "Installer {{name}}",
"marketplace.install.scope": "Omfang",
"marketplace.install.scope.project": "Prosjekt",
@@ -43,4 +43,25 @@ export const dict = {
"Er du sikker på at du vil fjerne denne {{type}}? Dette vil fjerne den fra din {{scope}}-konfigurasjon.",
"marketplace.remove.cancel": "Avbryt",
"marketplace.remove.confirm.button": "Fjern",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Søk...",
+ "marketplace.filter.all": "Alle elementer",
+ "marketplace.filter.notInstalled": "Ikke installert",
+ "marketplace.empty": "Ingen elementer funnet",
+ "marketplace.badge.mcpServer": "MCP-server",
+ "marketplace.badge.mode": "Modus",
+ "marketplace.card.by": "av {{author}}",
+ "marketplace.install.method": "Installasjonsmetode",
+ "marketplace.install.parameters": "Parametere",
+ "marketplace.install.optional": "(valgfritt)",
+ "marketplace.install.required": "{{name}} er påkrevd",
+ "marketplace.scope.project": "prosjekt",
+ "marketplace.scope.global": "global",
+ "marketplace.remove.type.mcp": "MCP-server",
+ "marketplace.remove.type.skill": "ferdighet",
+ "marketplace.remove.type.mode": "modus",
+ "marketplace.remove.failed": "Kunne ikke fjerne {{name}}",
+ "marketplace.install": "Installer",
+ "marketplace.filter.installed": "Installert",
+ "marketplace.error.dismiss": "Avvis",
}
diff --git a/packages/kilo-i18n/src/pl.ts b/packages/kilo-i18n/src/pl.ts
index b4b7f0685a1..0951308b723 100644
--- a/packages/kilo-i18n/src/pl.ts
+++ b/packages/kilo-i18n/src/pl.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Skills",
"marketplace.tab.mcpServers": "Serwery MCP",
"marketplace.tab.modes": "Tryby",
- "marketplace.search.skills": "Szukaj skills...",
"marketplace.category.all": "Wszystkie",
- "marketplace.empty.skills": "Nie znaleziono skills",
"marketplace.placeholder": "Do zaimplementowania",
"marketplace.card.installed": "Zainstalowano",
"marketplace.card.install": "Zainstaluj",
"marketplace.card.remove": "Usuń",
"marketplace.card.removeScope": "Usuń ({{scope}})",
+ "marketplace.card.showMore": "Pokaż więcej",
+ "marketplace.card.showLess": "Pokaż mniej",
"marketplace.install.title": "Instalacja: {{name}}",
"marketplace.install.scope": "Zakres",
"marketplace.install.scope.project": "Projekt",
@@ -43,4 +43,25 @@ export const dict = {
"Czy na pewno chcesz usunąć ten {{type}}? Spowoduje to usunięcie z konfiguracji: {{scope}}.",
"marketplace.remove.cancel": "Anuluj",
"marketplace.remove.confirm.button": "Usuń",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Szukaj...",
+ "marketplace.filter.all": "Wszystkie elementy",
+ "marketplace.filter.notInstalled": "Nie zainstalowano",
+ "marketplace.empty": "Nie znaleziono elementów",
+ "marketplace.badge.mcpServer": "Serwer MCP",
+ "marketplace.badge.mode": "Tryb",
+ "marketplace.card.by": "autor: {{author}}",
+ "marketplace.install.method": "Metoda instalacji",
+ "marketplace.install.parameters": "Parametry",
+ "marketplace.install.optional": "(opcjonalne)",
+ "marketplace.install.required": "{{name}} jest wymagane",
+ "marketplace.scope.project": "projekt",
+ "marketplace.scope.global": "globalny",
+ "marketplace.remove.type.mcp": "serwer MCP",
+ "marketplace.remove.type.skill": "umiejętność",
+ "marketplace.remove.type.mode": "tryb",
+ "marketplace.remove.failed": "Nie udało się usunąć {{name}}",
+ "marketplace.install": "Zainstaluj",
+ "marketplace.filter.installed": "Zainstalowano",
+ "marketplace.error.dismiss": "Odrzuć",
}
diff --git a/packages/kilo-i18n/src/ru.ts b/packages/kilo-i18n/src/ru.ts
index 581b6925967..11fc5db56e2 100644
--- a/packages/kilo-i18n/src/ru.ts
+++ b/packages/kilo-i18n/src/ru.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "Навыки",
"marketplace.tab.mcpServers": "MCP-серверы",
"marketplace.tab.modes": "Режимы",
- "marketplace.search.skills": "Поиск навыков...",
"marketplace.category.all": "Все",
- "marketplace.empty.skills": "Навыки не найдены",
"marketplace.placeholder": "Будет реализовано",
"marketplace.card.installed": "Установлено",
"marketplace.card.install": "Установить",
"marketplace.card.remove": "Удалить",
"marketplace.card.removeScope": "Удалить ({{scope}})",
+ "marketplace.card.showMore": "Показать больше",
+ "marketplace.card.showLess": "Показать меньше",
"marketplace.install.title": "Установка {{name}}",
"marketplace.install.scope": "Область",
"marketplace.install.scope.project": "Проект",
@@ -43,4 +43,25 @@ export const dict = {
"Вы уверены, что хотите удалить этот {{type}}? Это удалит его из вашей конфигурации ({{scope}}).",
"marketplace.remove.cancel": "Отмена",
"marketplace.remove.confirm.button": "Удалить",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Поиск...",
+ "marketplace.filter.all": "Все элементы",
+ "marketplace.filter.notInstalled": "Не установлено",
+ "marketplace.empty": "Элементы не найдены",
+ "marketplace.badge.mcpServer": "MCP-сервер",
+ "marketplace.badge.mode": "Режим",
+ "marketplace.card.by": "от {{author}}",
+ "marketplace.install.method": "Способ установки",
+ "marketplace.install.parameters": "Параметры",
+ "marketplace.install.optional": "(необязательно)",
+ "marketplace.install.required": "Требуется {{name}}",
+ "marketplace.scope.project": "проект",
+ "marketplace.scope.global": "глобально",
+ "marketplace.remove.type.mcp": "MCP-сервер",
+ "marketplace.remove.type.skill": "навык",
+ "marketplace.remove.type.mode": "режим",
+ "marketplace.remove.failed": "Не удалось удалить {{name}}",
+ "marketplace.install": "Установить",
+ "marketplace.filter.installed": "Установлено",
+ "marketplace.error.dismiss": "Закрыть",
}
diff --git a/packages/kilo-i18n/src/th.ts b/packages/kilo-i18n/src/th.ts
index 5fe5514aab0..65b1127e7df 100644
--- a/packages/kilo-i18n/src/th.ts
+++ b/packages/kilo-i18n/src/th.ts
@@ -19,14 +19,14 @@ export const dict = {
"marketplace.tab.skills": "ทักษะ",
"marketplace.tab.mcpServers": "เซิร์ฟเวอร์ MCP",
"marketplace.tab.modes": "โหมด",
- "marketplace.search.skills": "ค้นหาทักษะ...",
"marketplace.category.all": "ทั้งหมด",
- "marketplace.empty.skills": "ไม่พบทักษะ",
"marketplace.placeholder": "ยังไม่ได้ดำเนินการ",
"marketplace.card.installed": "ติดตั้งแล้ว",
"marketplace.card.install": "ติดตั้ง",
"marketplace.card.remove": "ลบ",
"marketplace.card.removeScope": "ลบ ({{scope}})",
+ "marketplace.card.showMore": "แสดงเพิ่มเติม",
+ "marketplace.card.showLess": "แสดงน้อยลง",
"marketplace.install.title": "ติดตั้ง {{name}}",
"marketplace.install.scope": "ขอบเขต",
"marketplace.install.scope.project": "โปรเจกต์",
@@ -43,4 +43,25 @@ export const dict = {
"คุณแน่ใจหรือไม่ว่าต้องการลบ {{type}} นี้? สิ่งนี้จะลบออกจากการกำหนดค่า {{scope}} ของคุณ",
"marketplace.remove.cancel": "ยกเลิก",
"marketplace.remove.confirm.button": "ลบ",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "ค้นหา...",
+ "marketplace.filter.all": "รายการทั้งหมด",
+ "marketplace.filter.notInstalled": "ยังไม่ได้ติดตั้ง",
+ "marketplace.empty": "ไม่พบรายการ",
+ "marketplace.badge.mcpServer": "เซิร์ฟเวอร์ MCP",
+ "marketplace.badge.mode": "โหมด",
+ "marketplace.card.by": "โดย {{author}}",
+ "marketplace.install.method": "วิธีการติดตั้ง",
+ "marketplace.install.parameters": "พารามิเตอร์",
+ "marketplace.install.optional": "(ไม่บังคับ)",
+ "marketplace.install.required": "จำเป็นต้องใช้ {{name}}",
+ "marketplace.scope.project": "โปรเจ็กต์",
+ "marketplace.scope.global": "โกลบอล",
+ "marketplace.remove.type.mcp": "เซิร์ฟเวอร์ MCP",
+ "marketplace.remove.type.skill": "ทักษะ",
+ "marketplace.remove.type.mode": "โหมด",
+ "marketplace.remove.failed": "ไม่สามารถลบ {{name}} ได้",
+ "marketplace.install": "ติดตั้ง",
+ "marketplace.filter.installed": "ติดตั้งแล้ว",
+ "marketplace.error.dismiss": "ปิด",
}
diff --git a/packages/kilo-i18n/src/tr.ts b/packages/kilo-i18n/src/tr.ts
index 1b4c19c6fda..5dff96caf50 100644
--- a/packages/kilo-i18n/src/tr.ts
+++ b/packages/kilo-i18n/src/tr.ts
@@ -14,4 +14,54 @@ export const dict = {
// Reasoning block label
"ui.reasoning.label": "Akıl Yürütme",
+
+ // Marketplace
+ "marketplace.tab.skills": "Yetenekler",
+ "marketplace.tab.mcpServers": "MCP Sunucuları",
+ "marketplace.tab.modes": "Modlar",
+ "marketplace.category.all": "Tümü",
+ "marketplace.placeholder": "Uygulanacak",
+ "marketplace.card.installed": "Yüklendi",
+ "marketplace.card.install": "Yükle",
+ "marketplace.card.remove": "Kaldır",
+ "marketplace.card.removeScope": "Kaldır ({{scope}})",
+ "marketplace.card.showMore": "Daha fazla göster",
+ "marketplace.card.showLess": "Daha az göster",
+ "marketplace.install.title": "{{name}} yükle",
+ "marketplace.install.scope": "Kapsam",
+ "marketplace.install.scope.project": "Proje",
+ "marketplace.install.scope.global": "Genel",
+ "marketplace.install.prerequisites": "Ön koşullar",
+ "marketplace.install.installing": "Yükleniyor...",
+ "marketplace.install.cancel": "İptal",
+ "marketplace.install.success": "Başarıyla yüklendi!",
+ "marketplace.install.failed": "Yükleme başarısız oldu",
+ "marketplace.install.done": "Bitti",
+ "marketplace.install.close": "Kapat",
+ "marketplace.remove.title": "{{name}} kaldırılsın mı?",
+ "marketplace.remove.confirm":
+ "Bu {{type}} öğesini kaldırmak istediğinizden emin misiniz? Bu, {{scope}} yapılandırmanızdan kaldırılacaktır.",
+ "marketplace.remove.cancel": "İptal",
+ "marketplace.remove.confirm.button": "Kaldır",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "Ara...",
+ "marketplace.filter.all": "Tüm Öğeler",
+ "marketplace.filter.notInstalled": "Yüklü Değil",
+ "marketplace.empty": "Öğe bulunamadı",
+ "marketplace.badge.mcpServer": "MCP Sunucusu",
+ "marketplace.badge.mode": "Mod",
+ "marketplace.card.by": "{{author}} tarafından",
+ "marketplace.install.method": "Yükleme Yöntemi",
+ "marketplace.install.parameters": "Parametreler",
+ "marketplace.install.optional": "(isteğe bağlı)",
+ "marketplace.install.required": "{{name}} gereklidir",
+ "marketplace.scope.project": "proje",
+ "marketplace.scope.global": "genel",
+ "marketplace.remove.type.mcp": "MCP sunucusu",
+ "marketplace.remove.type.skill": "yetenek",
+ "marketplace.remove.type.mode": "mod",
+ "marketplace.remove.failed": "{{name}} kaldırılamadı",
+ "marketplace.install": "Yükle",
+ "marketplace.filter.installed": "Yüklendi",
+ "marketplace.error.dismiss": "Kapat",
}
diff --git a/packages/kilo-i18n/src/zh.ts b/packages/kilo-i18n/src/zh.ts
index a5f9d8870d3..f6ab44e6cf8 100644
--- a/packages/kilo-i18n/src/zh.ts
+++ b/packages/kilo-i18n/src/zh.ts
@@ -17,14 +17,14 @@ export const dict = {
"marketplace.tab.skills": "技能",
"marketplace.tab.mcpServers": "MCP 服务器",
"marketplace.tab.modes": "模式",
- "marketplace.search.skills": "搜索技能...",
"marketplace.category.all": "全部",
- "marketplace.empty.skills": "未找到技能",
"marketplace.placeholder": "待实现",
"marketplace.card.installed": "已安装",
"marketplace.card.install": "安装",
"marketplace.card.remove": "移除",
"marketplace.card.removeScope": "移除 ({{scope}})",
+ "marketplace.card.showMore": "展开",
+ "marketplace.card.showLess": "收起",
"marketplace.install.title": "安装 {{name}}",
"marketplace.install.scope": "作用域",
"marketplace.install.scope.project": "项目",
@@ -40,4 +40,25 @@ export const dict = {
"marketplace.remove.confirm": "确定要移除此 {{type}} 吗?这会将其从你的 {{scope}} 配置中移除。",
"marketplace.remove.cancel": "取消",
"marketplace.remove.confirm.button": "移除",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "搜索...",
+ "marketplace.filter.all": "所有项目",
+ "marketplace.filter.notInstalled": "未安装",
+ "marketplace.empty": "未找到项目",
+ "marketplace.badge.mcpServer": "MCP 服务器",
+ "marketplace.badge.mode": "模式",
+ "marketplace.card.by": "作者:{{author}}",
+ "marketplace.install.method": "安装方法",
+ "marketplace.install.parameters": "参数",
+ "marketplace.install.optional": "(可选)",
+ "marketplace.install.required": "{{name}} 是必填项",
+ "marketplace.scope.project": "项目",
+ "marketplace.scope.global": "全局",
+ "marketplace.remove.type.mcp": "MCP 服务器",
+ "marketplace.remove.type.skill": "技能",
+ "marketplace.remove.type.mode": "模式",
+ "marketplace.remove.failed": "移除 {{name}} 失败",
+ "marketplace.install": "安装",
+ "marketplace.filter.installed": "已安装",
+ "marketplace.error.dismiss": "关闭",
}
diff --git a/packages/kilo-i18n/src/zht.ts b/packages/kilo-i18n/src/zht.ts
index 219ed9d7f95..1f123464e9a 100644
--- a/packages/kilo-i18n/src/zht.ts
+++ b/packages/kilo-i18n/src/zht.ts
@@ -17,14 +17,14 @@ export const dict = {
"marketplace.tab.skills": "技能",
"marketplace.tab.mcpServers": "MCP 伺服器",
"marketplace.tab.modes": "模式",
- "marketplace.search.skills": "搜尋技能...",
"marketplace.category.all": "全部",
- "marketplace.empty.skills": "找不到技能",
"marketplace.placeholder": "待實作",
"marketplace.card.installed": "已安裝",
"marketplace.card.install": "安裝",
"marketplace.card.remove": "移除",
"marketplace.card.removeScope": "移除 ({{scope}})",
+ "marketplace.card.showMore": "展開",
+ "marketplace.card.showLess": "收起",
"marketplace.install.title": "安裝 {{name}}",
"marketplace.install.scope": "作用域",
"marketplace.install.scope.project": "專案",
@@ -40,4 +40,25 @@ export const dict = {
"marketplace.remove.confirm": "確定要移除此 {{type}} 嗎?這會將其從你的 {{scope}} 設定中移除。",
"marketplace.remove.cancel": "取消",
"marketplace.remove.confirm.button": "移除",
+ "marketplace.tab.mcp": "MCP",
+ "marketplace.search": "搜尋...",
+ "marketplace.filter.all": "所有項目",
+ "marketplace.filter.notInstalled": "未安裝",
+ "marketplace.empty": "找不到項目",
+ "marketplace.badge.mcpServer": "MCP 伺服器",
+ "marketplace.badge.mode": "模式",
+ "marketplace.card.by": "作者:{{author}}",
+ "marketplace.install.method": "安裝方法",
+ "marketplace.install.parameters": "參數",
+ "marketplace.install.optional": "(選填)",
+ "marketplace.install.required": "{{name}} 是必填項",
+ "marketplace.scope.project": "專案",
+ "marketplace.scope.global": "全域",
+ "marketplace.remove.type.mcp": "MCP 伺服器",
+ "marketplace.remove.type.skill": "技能",
+ "marketplace.remove.type.mode": "模式",
+ "marketplace.remove.failed": "移除 {{name}} 失敗",
+ "marketplace.install": "安裝",
+ "marketplace.filter.installed": "已安裝",
+ "marketplace.error.dismiss": "關閉",
}
diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json
index 4cc1584fa7e..dc03bf9fe16 100644
--- a/packages/kilo-telemetry/package.json
+++ b/packages/kilo-telemetry/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-telemetry",
- "version": "7.0.48",
+ "version": "7.0.49",
"type": "module",
"license": "MIT",
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
diff --git a/packages/kilo-telemetry/tsconfig.json b/packages/kilo-telemetry/tsconfig.json
index dd08759fbbc..d7c900fa255 100644
--- a/packages/kilo-telemetry/tsconfig.json
+++ b/packages/kilo-telemetry/tsconfig.json
@@ -4,6 +4,7 @@
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
+ "types": ["bun"],
"skipLibCheck": true,
"noUncheckedIndexedAccess": false
},
diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json
index 947373d6a84..31cb39fb268 100644
--- a/packages/kilo-ui/package.json
+++ b/packages/kilo-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-ui",
- "version": "7.0.48",
+ "version": "7.0.49",
"type": "module",
"license": "MIT",
"exports": {
diff --git a/packages/kilo-ui/src/components/basic-tool.css b/packages/kilo-ui/src/components/basic-tool.css
index 84fa512d83c..50f086d83e8 100644
--- a/packages/kilo-ui/src/components/basic-tool.css
+++ b/packages/kilo-ui/src/components/basic-tool.css
@@ -73,12 +73,21 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
padding: 0 8px;
height: 36px;
background-color: var(--surface-inset-base, var(--vscode-sideBar-background));
+
+ &:hover {
+ background-color: var(--surface-inset-base-hover, var(--vscode-list-hoverBackground));
+ }
}
[data-slot="collapsible-trigger"][aria-expanded="true"] {
border-bottom: 1px solid var(--border-weak-base, var(--vscode-panel-border));
}
+ /* Arrow always visible (no fade-on-hover) */
+ [data-slot="collapsible-arrow"] {
+ opacity: 1;
+ }
+
/* Arrow: DOWN when collapsed, UP when expanded (matches question-dock toggle) */
[data-slot="collapsible-arrow-icon"] {
transform: translateZ(0) rotate(0deg);
diff --git a/packages/kilo-ui/src/components/collapsible.css b/packages/kilo-ui/src/components/collapsible.css
index 84190941936..b6ddd4a009d 100644
--- a/packages/kilo-ui/src/components/collapsible.css
+++ b/packages/kilo-ui/src/components/collapsible.css
@@ -5,3 +5,14 @@
cursor: pointer;
}
}
+
+/* VS Code theme: tool-collapsible arrows point DOWN (collapsed) and UP (expanded), never right */
+html[data-theme="kilo-vscode"] [data-component="collapsible"].tool-collapsible {
+ [data-slot="collapsible-arrow-icon"] {
+ transform: translateZ(0) rotate(0deg);
+ }
+
+ [data-slot="collapsible-trigger"][aria-expanded="true"] [data-slot="collapsible-arrow-icon"] {
+ transform: translateZ(0) rotate(180deg);
+ }
+}
diff --git a/packages/kilo-ui/src/components/message-part.css b/packages/kilo-ui/src/components/message-part.css
index bd2db869f91..189e11f4454 100644
--- a/packages/kilo-ui/src/components/message-part.css
+++ b/packages/kilo-ui/src/components/message-part.css
@@ -139,12 +139,16 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] {
}
[data-slot="collapsible-trigger"] {
- height: 28px;
+ height: 36px;
padding: 0 8px;
gap: 6px;
background: var(--surface-inset-base);
border-radius: 0;
cursor: pointer;
+
+ &:hover {
+ background-color: var(--surface-inset-base-hover, var(--vscode-list-hoverBackground));
+ }
}
[data-slot="reasoning-header"] {
diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx
index 52dd9e71408..e731aed1367 100644
--- a/packages/kilo-ui/src/components/message-part.tsx
+++ b/packages/kilo-ui/src/components/message-part.tsx
@@ -1206,7 +1206,7 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp
return (
-
+
diff --git a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-collapsed-chromium-linux.png b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-collapsed-chromium-linux.png
index fdf49245b20..36ab5c857cb 100644
--- a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-collapsed-chromium-linux.png
+++ b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-collapsed-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cf93ca79d15deab7dc52473f68305b521c62d10e968a9258e09609ad9c04fde6
-size 15837
+oid sha256:67cb95242834a2ac3633b70a68a8ff9a9f343938092f06926a44639ba5c01121
+size 15968
diff --git a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png
index 3da58463454..a72714105d9 100644
--- a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png
+++ b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:14bf991adcbc6fcbf53f4cff2275dcec255943b5545c624231aeb73529ca2d6e
-size 32478
+oid sha256:6d93bf6465c33895aa54bc2479315ea2fba38d06488f3d7cdf868b60caaefd9e
+size 32623
diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json
index 0ccf0bd27db..807c9b1e40b 100644
--- a/packages/kilo-vscode/package.json
+++ b/packages/kilo-vscode/package.json
@@ -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.0.48",
+ "version": "7.0.49",
"icon": "assets/icons/logo-outline-black.png",
"galleryBanner": {
"color": "#FFFFFF",
diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts
index 09e98a3cda2..d91c1769fef 100644
--- a/packages/kilo-vscode/src/KiloProvider.ts
+++ b/packages/kilo-vscode/src/KiloProvider.ts
@@ -1,6 +1,7 @@
import * as path from "path"
import * as vscode from "vscode"
import { z } from "zod"
+import { buildPreviewPath, getPreviewCommand, getPreviewDir, parseImage, trimEntries } from "./image-preview"
import { isAbsolutePath } from "./path-utils"
import type {
KiloClient,
@@ -365,6 +366,16 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "abort":
await this.handleAbort(message.sessionID)
break
+ case "revertSession":
+ this.handleRevertSession(message.sessionID, message.messageID).catch((e) =>
+ console.error("[Kilo New] handleRevertSession failed:", e),
+ )
+ break
+ case "unrevertSession":
+ this.handleUnrevertSession(message.sessionID).catch((e) =>
+ console.error("[Kilo New] handleUnrevertSession failed:", e),
+ )
+ break
case "permissionResponse":
await this.handlePermissionResponse(
message.permissionId,
@@ -428,6 +439,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "openSubAgentViewer":
vscode.commands.executeCommand("kilo-code.new.openSubAgentViewer", message.sessionID, message.title)
break
+ case "previewImage":
+ this.handlePreviewImage(message.dataUrl, message.filename)
+ break
case "openFile":
if (message.filePath) {
this.handleOpenFile(message.filePath, message.line, message.column)
@@ -657,7 +671,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
const workspace = this.getProjectDirectory(this.currentSession?.id)
const scope = message.mpInstallOptions?.target ?? "project"
const result = await this.getMarketplace().install(message.mpItem, message.mpInstallOptions, workspace)
- if (result.success) await this.disposeCliInstance(scope)
+ if (result.success) {
+ await this.disposeCliInstance(scope)
+ this.cachedAgentsMessage = null
+ await this.fetchAndSendAgents()
+ }
this.postMessage({
type: "marketplaceInstallResult",
success: result.success,
@@ -670,7 +688,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
const workspace = this.getProjectDirectory(this.currentSession?.id)
const scope = message.mpInstallOptions?.target ?? "project"
const result = await this.getMarketplace().remove(message.mpItem, scope, workspace)
- if (result.success) await this.disposeCliInstance(scope)
+ if (result.success) {
+ await this.disposeCliInstance(scope)
+ this.cachedAgentsMessage = null
+ await this.fetchAndSendAgents()
+ }
this.postMessage({
type: "marketplaceRemoveResult",
success: result.success,
@@ -1236,22 +1258,34 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
*/
private async handleRemoveMode(name: string): Promise
{
if (!this.client) return
+ let removed = false
+
+ // 1. Try CLI removal (handles .md files and legacy .kilocodemodes)
try {
const dir = this.getWorkspaceDirectory()
const result = await this.client.kilocode.removeAgent({ name, directory: dir })
- if (result.error) {
- console.error("[Kilo New] KiloProvider: removeAgent returned error:", result.error)
- this.cachedAgentsMessage = null
- await this.fetchAndSendAgents()
- return
- }
- } catch (error) {
- console.error("[Kilo New] KiloProvider: Failed to remove mode:", error)
- this.cachedAgentsMessage = null
- await this.fetchAndSendAgents()
- return
+ if (!result.error) removed = true
+ } catch {
+ // CLI removal failed — agent may be in kilo.json instead
}
- // Invalidate cache so next requestAgents fetches fresh data
+
+ // 2. Try removing from kilo.json (handles marketplace-installed modes)
+ if (!removed) {
+ const workspace = this.getProjectDirectory(this.currentSession?.id)
+ const mp = this.getMarketplace()
+ const stub = { id: name, type: "mode" as const, name, description: "", content: "" }
+ const project = await mp.remove(stub, "project", workspace)
+ const global = await mp.remove(stub, "global", workspace)
+ if (project.success || global.success) {
+ await this.disposeCliInstance("global")
+ removed = true
+ }
+ }
+
+ if (!removed) {
+ console.error("[Kilo New] KiloProvider: Failed to remove mode:", name)
+ }
+
this.cachedAgentsMessage = null
await this.fetchAndSendAgents()
}
@@ -1267,12 +1301,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
await this.client.global.dispose().catch((e: unknown) => {
console.warn("[Kilo New] global.dispose() after marketplace change failed:", e)
})
- } else {
- const dir = this.getWorkspaceDirectory()
- await this.client.instance.dispose({ directory: dir }).catch((e: unknown) => {
- console.warn("[Kilo New] instance.dispose() after marketplace change failed:", e)
- })
}
+ // Always dispose the per-project instance so it rebuilds state from
+ // the (possibly updated) global + project config on the next request.
+ const dir = this.getWorkspaceDirectory()
+ await this.client.instance.dispose({ directory: dir }).catch((e: unknown) => {
+ console.warn("[Kilo New] instance.dispose() after marketplace change failed:", e)
+ })
}
/**
@@ -1697,6 +1732,30 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}
+ private async handleRevertSession(sessionID: string, messageID: string): Promise {
+ if (!this.client) return
+ const dir = this.getWorkspaceDirectory(sessionID)
+ const { data, error } = await this.client.session.revert({ sessionID, messageID, directory: dir })
+ if (error) {
+ console.error("[Kilo New] KiloProvider: Failed to revert session:", error)
+ this.postMessage({ type: "error", message: "Failed to revert session", sessionID })
+ return
+ }
+ if (data) this.postMessage({ type: "sessionUpdated", session: sessionToWebview(data) })
+ }
+
+ private async handleUnrevertSession(sessionID: string): Promise {
+ if (!this.client) return
+ const dir = this.getWorkspaceDirectory(sessionID)
+ const { data, error } = await this.client.session.unrevert({ sessionID, directory: dir })
+ if (error) {
+ console.error("[Kilo New] KiloProvider: Failed to unrevert session:", error)
+ this.postMessage({ type: "error", message: "Failed to redo session", sessionID })
+ return
+ }
+ if (data) this.postMessage({ type: "sessionUpdated", session: sessionToWebview(data) })
+ }
+
/**
* Handle compact (context summarization) request from the webview.
*/
@@ -1809,6 +1868,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
sessionID: perm.sessionID,
toolName: perm.permission,
patterns: perm.patterns,
+ always: perm.always,
args: perm.metadata,
message: `Permission required: ${perm.permission}`,
tool: perm.tool,
@@ -1982,6 +2042,44 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}
+ private handlePreviewImage(dataUrl: string, filename: string): void {
+ const dir = this.extensionContext?.globalStorageUri
+ if (!dir) return
+
+ const img = parseImage(dataUrl, filename)
+ if (!img) return
+
+ const root = vscode.Uri.joinPath(dir, getPreviewDir())
+ const uri = vscode.Uri.joinPath(dir, buildPreviewPath(img.name, Date.now()))
+ const clean = () =>
+ vscode.workspace.fs.readDirectory(root).then(
+ (items) => {
+ const stale = trimEntries(items.map(([name]) => ({ path: name })))
+ return Promise.all(
+ stale.map((name) =>
+ Promise.resolve(vscode.workspace.fs.delete(vscode.Uri.joinPath(root, name), { recursive: true })).then(
+ undefined,
+ (err: unknown) => {
+ console.warn("[Kilo New] KiloProvider: Failed to delete stale preview:", err)
+ },
+ ),
+ ),
+ )
+ },
+ () => [],
+ )
+ const open = () =>
+ vscode.commands
+ .executeCommand(...getPreviewCommand(uri))
+ .then(undefined, () => vscode.commands.executeCommand("vscode.open", uri))
+
+ void vscode.workspace.fs
+ .createDirectory(root)
+ .then(() => vscode.workspace.fs.writeFile(uri, img.data))
+ .then(() => clean())
+ .then(open, (err) => console.error("[Kilo New] KiloProvider: Failed to preview image:", err))
+ }
+
/**
* Handle openFile request from the webview — open a file in the VS Code editor.
* Resolves relative paths against the current session's directory (which may be
diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
index 26d455c5b18..ee235e930fa 100644
--- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
+++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
@@ -66,6 +66,7 @@ export class AgentManagerProvider implements vscode.Disposable {
constructor(
private readonly extensionUri: vscode.Uri,
private readonly connectionService: KiloConnectionService,
+ private readonly context: vscode.ExtensionContext,
) {
this.outputChannel = vscode.window.createOutputChannel("Kilo Agent Manager")
this.terminalManager = new SessionTerminalManager(
@@ -147,7 +148,7 @@ export class AgentManagerProvider implements vscode.Disposable {
panel.webview.html = this.getHtml(panel.webview)
- this.provider = new KiloProvider(this.extensionUri, this.connectionService)
+ this.provider = new KiloProvider(this.extensionUri, this.connectionService, this.context)
this.provider.attachToWebview(panel.webview, {
onBeforeMessage: (msg) => this.onMessage(msg),
})
@@ -232,6 +233,9 @@ export class AgentManagerProvider implements vscode.Disposable {
this.openWorktreeDirectory(m.worktreeId)
return null
}
+ if (m.type === "previewImage") {
+ return msg
+ }
if (m.type === "agentManager.showExistingLocalTerminal") {
this.terminalManager.syncLocalOnSessionSwitch()
return null
diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts
index b6cf1cc138d..91977a40469 100644
--- a/packages/kilo-vscode/src/agent-manager/types.ts
+++ b/packages/kilo-vscode/src/agent-manager/types.ts
@@ -379,6 +379,12 @@ interface GenericOpenFileIn {
column?: number
}
+interface PreviewImageIn {
+ type: "previewImage"
+ dataUrl: string
+ filename: string
+}
+
interface LoadMessagesIn {
type: "loadMessages"
sessionID: string
@@ -434,6 +440,7 @@ export type AgentManagerInMessage =
| StopDiffWatchIn
| OpenFileIn
| GenericOpenFileIn
+ | PreviewImageIn
| LoadMessagesIn
| ClearSessionIn
| AbortIn
diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts
index 457d0901d7f..c5fa591e2be 100644
--- a/packages/kilo-vscode/src/extension.ts
+++ b/packages/kilo-vscode/src/extension.ts
@@ -47,7 +47,7 @@ export function activate(context: vscode.ExtensionContext) {
)
// Create Agent Manager provider for editor panel
- const agentManagerProvider = new AgentManagerProvider(context.extensionUri, connectionService)
+ const agentManagerProvider = new AgentManagerProvider(context.extensionUri, connectionService, context)
context.subscriptions.push(agentManagerProvider)
// Register serializer so Agent Manager restores when VS Code restarts
diff --git a/packages/kilo-vscode/src/image-preview.ts b/packages/kilo-vscode/src/image-preview.ts
new file mode 100644
index 00000000000..c9abc2d4331
--- /dev/null
+++ b/packages/kilo-vscode/src/image-preview.ts
@@ -0,0 +1,83 @@
+import * as path from "path"
+
+const IMAGE_PREVIEW_ID = "imagePreview.previewEditor"
+const PREVIEW_DIR = "image-preview"
+const PREVIEW_LIMIT = 20
+
+type Preview = {
+ data: Uint8Array
+ ext: string
+ name: string
+}
+
+export function parseImage(dataUrl: string, filename: string): Preview | null {
+ const sep = dataUrl.indexOf(",")
+ if (sep === -1) return null
+
+ const head = dataUrl.slice(0, sep)
+ const mime = head.match(/^data:(image\/[A-Za-z0-9.+-]+);base64$/)?.[1]
+ if (!mime) return null
+
+ const data = parseBase64(dataUrl.slice(sep + 1))
+ if (!data) return null
+
+ const ext = getExt(mime)
+ const name = buildName(filename, ext)
+ return { data, ext, name }
+}
+
+export function buildPreviewPath(name: string, now: number): string {
+ return path.posix.join(PREVIEW_DIR, `${now}-${name}`)
+}
+
+export function getPreviewCommand(uri: {
+ toString(): string
+}): [string, { resource: { toString(): string }; size: "contain" }] {
+ return [IMAGE_PREVIEW_ID, { resource: uri, size: "contain" }]
+}
+
+export function getPreviewDir(): string {
+ return PREVIEW_DIR
+}
+
+export function trimEntries(items: T[], limit = PREVIEW_LIMIT): string[] {
+ if (items.length <= limit) return []
+
+ return items
+ .slice()
+ .sort((a, b) => a.path.localeCompare(b.path))
+ .slice(0, items.length - limit)
+ .map((item) => item.path)
+}
+
+function parseBase64(value: string): Uint8Array | null {
+ try {
+ return Buffer.from(value, "base64")
+ } catch {
+ return null
+ }
+}
+
+function getExt(mime: string): string {
+ const raw = mime.split("/")[1] ?? "png"
+ if (raw === "jpeg") return "jpg"
+ if (raw === "svg+xml") return "svg"
+ return raw
+}
+
+function buildName(filename: string, ext: string): string {
+ const raw = path.basename(filename || "image")
+ const item = path.parse(raw)
+ const base = sanitize(item.name) || "image"
+ const tail = sanitize(item.ext.replace(/^\./, ""))
+ const clean = tail ? `${base}.${tail}` : base
+ if (path.extname(clean)) return clean
+ return `${clean}.${ext}`
+}
+
+function sanitize(value: string): string {
+ return value
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
+ .replace(/-+/g, "-")
+ .replace(/^-|-$/g, "")
+}
diff --git a/packages/kilo-vscode/src/kilo-provider-utils.ts b/packages/kilo-vscode/src/kilo-provider-utils.ts
index c6caa869aa8..118b793a52a 100644
--- a/packages/kilo-vscode/src/kilo-provider-utils.ts
+++ b/packages/kilo-vscode/src/kilo-provider-utils.ts
@@ -44,6 +44,11 @@ export function sessionToWebview(session: Session) {
title: session.title,
createdAt: new Date(session.time.created).toISOString(),
updatedAt: new Date(session.time.updated).toISOString(),
+ // Use null (not undefined) so the value survives postMessage JSON serialization.
+ // Without this, unrevert responses lose the revert key entirely and the
+ // SolidJS store merge never clears the existing revert state.
+ revert: session.revert ?? null,
+ summary: session.summary ?? null,
}
}
@@ -162,6 +167,7 @@ export type WebviewMessage =
sessionID: string
toolName: string
patterns: string[]
+ always: string[]
args: Record
message: string
tool?: { messageID: string; callID: string }
@@ -174,6 +180,7 @@ export type WebviewMessage =
| { type: "permissionError"; permissionID: string }
| { type: "sessionCreated"; session: ReturnType }
| { type: "sessionUpdated"; session: ReturnType }
+ | { type: "messageRemoved"; sessionID: string; messageID: string }
| null
export function mapSSEEventToWebviewMessage(event: Event, sessionID: string | undefined): WebviewMessage {
@@ -209,6 +216,14 @@ export function mapSSEEventToWebviewMessage(event: Event, sessionID: string | un
},
}
}
+ case "message.removed": {
+ const props = event.properties as { sessionID: string; messageID: string }
+ return {
+ type: "messageRemoved",
+ sessionID: props.sessionID,
+ messageID: props.messageID,
+ }
+ }
case "session.status": {
const info = event.properties.status
return {
@@ -226,6 +241,7 @@ export function mapSSEEventToWebviewMessage(event: Event, sessionID: string | un
sessionID: event.properties.sessionID,
toolName: event.properties.permission,
patterns: event.properties.patterns ?? [],
+ always: event.properties.always ?? [],
args: event.properties.metadata,
message: `Permission required: ${event.properties.permission}`,
tool: event.properties.tool,
diff --git a/packages/kilo-vscode/src/services/autocomplete/AutocompleteModel.ts b/packages/kilo-vscode/src/services/autocomplete/AutocompleteModel.ts
index 71adb447a9b..bfadd50204b 100644
--- a/packages/kilo-vscode/src/services/autocomplete/AutocompleteModel.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/AutocompleteModel.ts
@@ -20,13 +20,10 @@ export class AutocompleteModel {
private connectionService: KiloConnectionService | null = null
public profileName: string | null = null
public profileType: string | null = null
- public loaded = false
- public hasKilocodeProfileWithNoBalance = false
constructor(connectionService?: KiloConnectionService) {
if (connectionService) {
this.connectionService = connectionService
- this.loaded = true
}
}
@@ -37,21 +34,6 @@ export class AutocompleteModel {
this.connectionService = service
}
- /**
- * Load model configuration.
- * Returns true if the connection service is available.
- */
- public async reload(): Promise {
- this.loaded = true
-
- if (this.connectionService) {
- const state = this.connectionService.getConnectionState()
- return state === "connected"
- }
-
- return false
- }
-
public supportsFim(): boolean {
return true
}
diff --git a/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts b/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts
index 4aaad29e7c6..b69bbaa158a 100644
--- a/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts
@@ -18,7 +18,6 @@ export interface AutocompleteServiceSettings {
provider?: string
model?: string
snoozeUntil?: number
- hasKilocodeProfileWithNoBalance?: boolean
}
function readSettings(): AutocompleteServiceSettings {
@@ -59,6 +58,7 @@ export class AutocompleteServiceManager {
public readonly codeActionProvider: AutocompleteCodeActionProvider
public readonly inlineCompletionProvider: AutocompleteInlineCompletionProvider
private inlineCompletionProviderDisposable: vscode.Disposable | null = null
+ private unsubscribeState: (() => void) | null = null
constructor(context: vscode.ExtensionContext, connectionService: KiloConnectionService) {
if (AutocompleteServiceManager._instance) {
@@ -86,6 +86,12 @@ export class AutocompleteServiceManager {
new AutocompleteTelemetry(),
)
+ // Reload when CLI backend connection state changes so autocomplete
+ // picks up the connected state even if it wasn't ready at startup.
+ this.unsubscribeState = connectionService.onStateChange(() => {
+ void this.load()
+ })
+
void this.load()
}
@@ -97,7 +103,6 @@ export class AutocompleteServiceManager {
}
public async load() {
- await this.model.reload()
this.settings = readSettings()
await this.updateGlobalContext()
@@ -119,12 +124,12 @@ export class AutocompleteServiceManager {
return
}
- // Register classic provider
+ // Register classic provider (tracked via this.inlineCompletionProviderDisposable,
+ // not context.subscriptions, so re-registration on reconnect doesn't leak)
this.inlineCompletionProviderDisposable = vscode.languages.registerInlineCompletionItemProvider(
{ scheme: "file" },
this.inlineCompletionProvider,
)
- this.context.subscriptions.push(this.inlineCompletionProviderDisposable)
}
public async disable() {
@@ -237,11 +242,6 @@ export class AutocompleteServiceManager {
const document = editor.document
- // Ensure model is loaded
- if (!this.model.loaded) {
- await this.load()
- }
-
// Call the inline completion provider directly with manual trigger context
const position = editor.selection.active
const context: vscode.InlineCompletionContext = {
@@ -293,22 +293,16 @@ export class AutocompleteServiceManager {
})
}
- private getCurrentModelName(): string | undefined {
- if (!this.model.loaded) {
- return
- }
+ private getCurrentModelName(): string {
return this.model.getModelName()
}
- private getCurrentProviderName(): string | undefined {
- if (!this.model.loaded) {
- return
- }
+ private getCurrentProviderName(): string {
return this.model.getProviderDisplayName()
}
private hasNoUsableProvider(): boolean {
- return this.model.loaded && !this.model.hasValidCredentials() && !this.model.hasKilocodeProfileWithNoBalance
+ return !this.model.hasValidCredentials()
}
private updateCostTracking(cost: number, _inputTokens: number, _outputTokens: number): void {
@@ -328,7 +322,6 @@ export class AutocompleteServiceManager {
model: this.getCurrentModelName(),
provider: this.getCurrentProviderName(),
profileName: this.model.profileName,
- hasKilocodeProfileWithNoBalance: this.model.hasKilocodeProfileWithNoBalance,
hasNoUsableProvider: this.hasNoUsableProvider(),
totalSessionCost: this.sessionCost,
completionCount: this.completionCount,
@@ -360,6 +353,10 @@ export class AutocompleteServiceManager {
this.snoozeTimer = null
}
+ // Unsubscribe from connection state changes
+ this.unsubscribeState?.()
+ this.unsubscribeState = null
+
// Dispose inline completion provider registration
if (this.inlineCompletionProviderDisposable) {
this.inlineCompletionProviderDisposable.dispose()
diff --git a/packages/kilo-vscode/src/services/autocomplete/AutocompleteStatusBar.ts b/packages/kilo-vscode/src/services/autocomplete/AutocompleteStatusBar.ts
index 2719d8d96ea..867a2f67717 100644
--- a/packages/kilo-vscode/src/services/autocomplete/AutocompleteStatusBar.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/AutocompleteStatusBar.ts
@@ -86,22 +86,12 @@ export class AutocompleteStatusBar {
}
public render() {
- if (this.props.hasKilocodeProfileWithNoBalance) {
- return this.renderNoCreditsError()
- }
if (this.props.hasNoUsableProvider) {
return this.renderNoUsableProviderError()
}
return this.renderDefault()
}
- private renderNoCreditsError() {
- this.statusBar.text = t("kilocode:autocomplete.statusBar.warning")
- this.statusBar.tooltip = this.createMarkdownTooltip(
- t("kilocode:autocomplete.statusBar.tooltip.noCredits", { command: SETTINGS_COMMAND }),
- )
- }
-
private renderNoUsableProviderError() {
this.statusBar.text = t("kilocode:autocomplete.statusBar.warning")
this.statusBar.tooltip = this.createMarkdownTooltip(
diff --git a/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteModel.spec.ts b/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteModel.spec.ts
index 20f91a1c2c2..d94bcd0612e 100644
--- a/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteModel.spec.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteModel.spec.ts
@@ -1,732 +1,169 @@
-import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
+import { describe, it, expect, vi } from "vitest"
import { AutocompleteModel } from "../AutocompleteModel"
-import { ProviderSettingsManager } from "../../../core/config/ProviderSettingsManager"
-import { AUTOCOMPLETE_PROVIDER_MODELS } from "../utils/kilocode-utils"
-import * as apiIndex from "../../../api"
-import { TelemetryService } from "../../../../packages/telemetry/src/TelemetryService"
+import type { KiloConnectionService } from "../../cli-backend"
+
+function createMockConnectionService(state: "connecting" | "connected" | "disconnected" | "error" = "connected") {
+ return {
+ getConnectionState: vi.fn().mockReturnValue(state),
+ getClient: vi.fn().mockReturnValue({
+ kilo: {
+ fim: vi.fn(),
+ },
+ }),
+ onStateChange: vi.fn().mockReturnValue(() => {}),
+ } as unknown as KiloConnectionService
+}
describe("AutocompleteModel", () => {
- let mockProviderSettingsManager: ProviderSettingsManager
-
- beforeEach(() => {
- mockProviderSettingsManager = {
- listConfig: vi.fn(),
- getProfile: vi.fn(),
- } as any
+ describe("constructor", () => {
+ it("defaults profileName and profileType to null", () => {
+ const model = new AutocompleteModel()
+ expect(model.profileName).toBeNull()
+ expect(model.profileType).toBeNull()
+ })
})
- describe("reload", () => {
- it("sorts profiles by supportedProviders index order", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [
- { id: "3", name: "profile3", apiProvider: supportedProviders[2] },
- { id: "1", name: "profile1", apiProvider: supportedProviders[0] },
- { id: "2", name: "profile2", apiProvider: supportedProviders[1] },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "profile1",
- apiProvider: supportedProviders[0],
- mistralApiKey: "test-key",
- } as any)
-
+ describe("setConnectionService", () => {
+ it("sets the connection service after construction", () => {
const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- expect(mockProviderSettingsManager.getProfile).toHaveBeenCalledWith({ id: "1" })
- })
-
- it("filters out profiles without apiProvider", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [
- { id: "1", name: "profile1", apiProvider: undefined },
- { id: "2", name: "profile2", apiProvider: supportedProviders[0] },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "2",
- name: "profile2",
- apiProvider: supportedProviders[0],
- mistralApiKey: "test-key",
- } as any)
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- expect(mockProviderSettingsManager.getProfile).toHaveBeenCalledWith({ id: "2" })
- })
-
- it("filters out profiles with unsupported apiProvider", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [
- { id: "1", name: "profile1", apiProvider: "unsupported" },
- { id: "2", name: "profile2", apiProvider: supportedProviders[0] },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "2",
- name: "profile2",
- apiProvider: supportedProviders[0],
- mistralApiKey: "test-key",
- } as any)
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- expect(mockProviderSettingsManager.getProfile).toHaveBeenCalledWith({ id: "2" })
- })
-
- it("handles empty profile list", async () => {
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue([])
-
- const model = new AutocompleteModel()
- const result = await model.reload(mockProviderSettingsManager)
-
- expect(mockProviderSettingsManager.getProfile).not.toHaveBeenCalled()
expect(model.hasValidCredentials()).toBe(false)
- expect(result).toBe(false)
- })
- it("returns true when profile found", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [{ id: "1", name: "mistral-profile", apiProvider: "mistral" }] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "mistral-profile",
- apiProvider: "mistral",
- mistralApiKey: "test-key",
- } as any)
-
- const model = new AutocompleteModel()
- const result = await model.reload(mockProviderSettingsManager)
-
- expect(result).toBe(true)
- expect(model.loaded).toBe(true)
+ const connection = createMockConnectionService("connected")
+ model.setConnectionService(connection)
+ expect(model.hasValidCredentials()).toBe(true)
})
})
- describe("provider usability", () => {
- beforeEach(() => {
- // Mock fetch globally for these tests
- vi.stubGlobal("fetch", vi.fn())
-
- // Mock TelemetryService
- const mockTelemetryService = {
- captureEvent: vi.fn(),
- captureException: vi.fn(),
- isTelemetryEnabled: vi.fn().mockReturnValue(false),
- shutdown: vi.fn(),
- }
- vi.spyOn(TelemetryService, "hasInstance").mockReturnValue(true)
- vi.spyOn(TelemetryService, "instance", "get").mockReturnValue(mockTelemetryService as any)
+ describe("hasValidCredentials", () => {
+ it("returns true when connected", () => {
+ const connection = createMockConnectionService("connected")
+ const model = new AutocompleteModel(connection)
+ expect(model.hasValidCredentials()).toBe(true)
})
- afterEach(() => {
- // Restore fetch
- vi.unstubAllGlobals()
-
- // Restore TelemetryService mocks
- vi.restoreAllMocks()
+ it("returns false when disconnected", () => {
+ const connection = createMockConnectionService("disconnected")
+ const model = new AutocompleteModel(connection)
+ expect(model.hasValidCredentials()).toBe(false)
})
- it("should skip kilocode provider when balance is zero and use openrouter instead", async () => {
- const profiles = [
- { id: "1", name: "kilocode-profile", apiProvider: "kilocode" },
- { id: "2", name: "openrouter-profile", apiProvider: "openrouter" },
- ] as any
+ it("returns false when connecting", () => {
+ const connection = createMockConnectionService("connecting")
+ const model = new AutocompleteModel(connection)
+ expect(model.hasValidCredentials()).toBe(false)
+ })
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
-
- // Mock profiles with tokens
- vi.mocked(mockProviderSettingsManager.getProfile).mockImplementation(async (args: any) => {
- if (args.id === "1") {
- return {
- id: "1",
- name: "kilocode-profile",
- apiProvider: "kilocode",
- kilocodeToken: "test-token",
- } as any
- } else if (args.id === "2") {
- return {
- id: "2",
- name: "openrouter-profile",
- apiProvider: "openrouter",
- openRouterApiKey: "test-key",
- } as any
- }
- return null as any
- })
-
- // Mock fetch to return zero balance for kilocode
- ;(global.fetch as any).mockImplementation(async (url: string) => {
- if (url.includes("/api/profile/balance")) {
- return {
- ok: true,
- json: async () => ({ data: { balance: 0 } }),
- } as any
- }
- // For OpenRouter models endpoint
- if (url.includes("/models")) {
- return {
- ok: true,
- json: async () => ({ data: [] }),
- } as any
- }
- // For other URLs, return a basic response
- return {
- ok: true,
- json: async () => ({}),
- } as any
- })
+ it("returns false when in error state", () => {
+ const connection = createMockConnectionService("error")
+ const model = new AutocompleteModel(connection)
+ expect(model.hasValidCredentials()).toBe(false)
+ })
+ it("returns false without connection service", () => {
const model = new AutocompleteModel()
- const result = await model.reload(mockProviderSettingsManager)
-
- // Should have tried both providers but used openrouter (since kilocode balance is 0)
- expect(result).toBe(true)
- expect(model.loaded).toBe(true)
+ expect(model.hasValidCredentials()).toBe(false)
})
+ })
- it("should use kilocode provider when balance is greater than zero", async () => {
- const profiles = [
- { id: "1", name: "kilocode-profile", apiProvider: "kilocode" },
- { id: "2", name: "openrouter-profile", apiProvider: "openrouter" },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
-
- // Mock profiles with tokens
- vi.mocked(mockProviderSettingsManager.getProfile).mockImplementation(async (args: any) => {
- if (args.id === "1") {
- return {
- id: "1",
- name: "kilocode-profile",
- apiProvider: "kilocode",
- kilocodeToken: "test-token",
- } as any
- } else if (args.id === "2") {
- return {
- id: "2",
- name: "openrouter-profile",
- apiProvider: "openrouter",
- openRouterApiKey: "test-key",
- } as any
- }
- return null as any
- })
-
- // Mock fetch to return positive balance for kilocode
- ;(global.fetch as any).mockImplementation(async (url: string) => {
- if (url.includes("/api/profile/balance")) {
- return {
- ok: true,
- json: async () => ({ data: { balance: 10.5 } }),
- } as any
- }
- // For OpenRouter models endpoint
- if (url.includes("/models")) {
- return {
- ok: true,
- json: async () => ({ data: [] }),
- } as any
- }
- // For other URLs, return a basic response
- return {
- ok: true,
- json: async () => ({}),
- } as any
- })
-
+ describe("supportsFim", () => {
+ it("always returns true", () => {
const model = new AutocompleteModel()
- const result = await model.reload(mockProviderSettingsManager)
-
- // Should have used kilocode provider (first one with positive balance)
- expect(result).toBe(true)
- expect(model.loaded).toBe(true)
+ expect(model.supportsFim()).toBe(true)
})
+ })
- it("should handle kilocode provider with no token", async () => {
- const profiles = [
- { id: "1", name: "kilocode-profile", apiProvider: "kilocode" },
- { id: "2", name: "openrouter-profile", apiProvider: "openrouter" },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
-
- // Mock profiles - kilocode without token
- vi.mocked(mockProviderSettingsManager.getProfile).mockImplementation(async (args: any) => {
- if (args.id === "1") {
- return {
- id: "1",
- name: "kilocode-profile",
- apiProvider: "kilocode",
- kilocodeToken: "", // No token
- } as any
- } else if (args.id === "2") {
- return {
- id: "2",
- name: "openrouter-profile",
- apiProvider: "openrouter",
- openRouterApiKey: "test-key",
- } as any
- }
- return null as any
- })
-
- // Mock fetch to handle the no-token case
- ;(global.fetch as any).mockImplementation(async (url: string) => {
- if (url.includes("/api/profile/balance")) {
- // This should not be called since there's no token
- return {
- ok: false,
- status: 401,
- } as any
- }
- // For OpenRouter models endpoint
- if (url.includes("/models")) {
- return {
- ok: true,
- json: async () => ({ data: [] }),
- } as any
- }
- // For other URLs, return a basic response
- return {
- ok: true,
- json: async () => ({}),
- } as any
- })
-
+ describe("getModelName", () => {
+ it("returns the default model", () => {
const model = new AutocompleteModel()
- const result = await model.reload(mockProviderSettingsManager)
-
- // Should skip kilocode (no token) and use openrouter
- expect(result).toBe(true)
- expect(model.loaded).toBe(true)
- })
-
- it("should set hasKilocodeProfileWithNoBalance when kilocode profile exists but has no balance", async () => {
- const profiles = [{ id: "1", name: "kilocode-profile", apiProvider: "kilocode" }] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
-
- // Mock profile with token
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "kilocode-profile",
- apiProvider: "kilocode",
- kilocodeToken: "test-token",
- } as any)
-
- // Mock fetch to return zero balance for kilocode
- ;(global.fetch as any).mockImplementation(async (url: string) => {
- if (url.includes("/api/profile/balance")) {
- return {
- ok: true,
- json: async () => ({ balance: 0 }),
- } as any
- }
- return {
- ok: true,
- json: async () => ({}),
- } as any
- })
-
- const model = new AutocompleteModel()
- const result = await model.reload(mockProviderSettingsManager)
-
- // Should not find a usable provider
- expect(result).toBe(false)
- expect(model.loaded).toBe(true)
- // Should have set the flag indicating kilocode profile exists but has no balance
- expect(model.hasKilocodeProfileWithNoBalance).toBe(true)
- })
-
- it("should not set hasKilocodeProfileWithNoBalance when kilocode profile has balance", async () => {
- const profiles = [{ id: "1", name: "kilocode-profile", apiProvider: "kilocode" }] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
-
- // Mock profile with token
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "kilocode-profile",
- apiProvider: "kilocode",
- kilocodeToken: "test-token",
- } as any)
-
- // Mock fetch to return positive balance for kilocode and valid models response
- ;(global.fetch as any).mockImplementation(async (url: string) => {
- if (url.includes("/api/profile/balance")) {
- return {
- ok: true,
- json: async () => ({ balance: 10.5 }),
- } as any
- }
- // For OpenRouter/Kilocode models endpoint
- if (url.includes("/models")) {
- return {
- ok: true,
- json: async () => ({
- data: [
- {
- id: "mistralai/codestral-2508",
- name: "Codestral",
- context_length: 32000,
- pricing: { prompt: "0.0001", completion: "0.0003" },
- },
- ],
- }),
- } as any
- }
- return {
- ok: true,
- json: async () => ({}),
- } as any
- })
-
- const model = new AutocompleteModel()
- const result = await model.reload(mockProviderSettingsManager)
-
- // Should find a usable provider
- expect(result).toBe(true)
- expect(model.loaded).toBe(true)
- // Should not have set the flag
- expect(model.hasKilocodeProfileWithNoBalance).toBe(false)
- })
-
- it("should clear hasKilocodeProfileWithNoBalance on reload", async () => {
- const profiles = [{ id: "1", name: "kilocode-profile", apiProvider: "kilocode" }] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
-
- // Mock profile with token
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "kilocode-profile",
- apiProvider: "kilocode",
- kilocodeToken: "test-token",
- } as any)
-
- // First reload: zero balance
- ;(global.fetch as any).mockImplementation(async (url: string) => {
- if (url.includes("/api/profile/balance")) {
- return {
- ok: true,
- json: async () => ({ balance: 0 }),
- } as any
- }
- return {
- ok: true,
- json: async () => ({}),
- } as any
- })
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
- expect(model.hasKilocodeProfileWithNoBalance).toBe(true)
-
- // Second reload: positive balance with valid models response
- ;(global.fetch as any).mockImplementation(async (url: string) => {
- if (url.includes("/api/profile/balance")) {
- return {
- ok: true,
- json: async () => ({ balance: 10.5 }),
- } as any
- }
- // For OpenRouter/Kilocode models endpoint
- if (url.includes("/models")) {
- return {
- ok: true,
- json: async () => ({
- data: [
- {
- id: "mistralai/codestral-2508",
- name: "Codestral",
- context_length: 32000,
- pricing: { prompt: "0.0001", completion: "0.0003" },
- },
- ],
- }),
- } as any
- }
- return {
- ok: true,
- json: async () => ({}),
- } as any
- })
-
- await model.reload(mockProviderSettingsManager)
- // Flag should be cleared after reload with positive balance
- expect(model.hasKilocodeProfileWithNoBalance).toBe(false)
+ expect(model.getModelName()).toBe("mistralai/codestral-2508")
})
})
describe("getProviderDisplayName", () => {
- it("returns undefined when no provider is loaded", () => {
+ it("returns Kilo Gateway", () => {
const model = new AutocompleteModel()
- expect(model.getProviderDisplayName()).toBeUndefined()
- })
-
- it("returns provider name from API handler when provider is loaded", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [{ id: "1", name: "profile1", apiProvider: supportedProviders[0] }] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "profile1",
- apiProvider: supportedProviders[0],
- mistralApiKey: "test-key",
- } as any)
-
- // Mock buildApiHandler to return a handler with providerName
- const mockApiHandler = {
- providerName: "Mistral",
- getModel: vi.fn().mockReturnValue({ id: "mistral-model", info: {} }),
- createMessage: vi.fn(),
- countTokens: vi.fn(),
- }
- vi.spyOn(apiIndex, "buildApiHandler").mockReturnValue(mockApiHandler as any)
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- const providerName = model.getProviderDisplayName()
- expect(providerName).toBeTruthy()
- expect(typeof providerName).toBe("string")
- expect(providerName).toBe("Mistral")
-
- // Restore the spy
- vi.restoreAllMocks()
- })
-
- describe("profile information", () => {
- it("returns null for profile name when no profile is loaded", () => {
- const model = new AutocompleteModel()
- expect(model.profileName).toBeNull()
- })
-
- it("returns null for profile type when no profile is loaded", () => {
- const model = new AutocompleteModel()
- expect(model.profileType).toBeNull()
- })
-
- it("stores and returns profile name after loading", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [
- {
- id: "1",
- name: "My Autocomplete Profile",
- apiProvider: supportedProviders[0],
- profileType: "autocomplete",
- },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "My Autocomplete Profile",
- apiProvider: supportedProviders[0],
- profileType: "autocomplete",
- mistralApiKey: "test-key",
- } as any)
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- expect(model.profileName).toBe("My Autocomplete Profile")
- })
-
- it("stores and returns profile type after loading", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [
- { id: "1", name: "My Profile", apiProvider: supportedProviders[0], profileType: "autocomplete" },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "My Profile",
- apiProvider: supportedProviders[0],
- profileType: "autocomplete",
- mistralApiKey: "test-key",
- } as any)
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- expect(model.profileType).toBe("autocomplete")
- })
-
- it("clears profile information on cleanup", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const profiles = [
- { id: "1", name: "My Profile", apiProvider: supportedProviders[0], profileType: "autocomplete" },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "My Profile",
- apiProvider: supportedProviders[0],
- profileType: "autocomplete",
- mistralApiKey: "test-key",
- } as any)
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- expect(model.profileName).toBe("My Profile")
- expect(model.profileType).toBe("autocomplete")
-
- // Reload with empty profiles to trigger cleanup
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue([])
- await model.reload(mockProviderSettingsManager)
-
- expect(model.profileName).toBeNull()
- expect(model.profileType).toBeNull()
- })
+ expect(model.getProviderDisplayName()).toBe("Kilo Gateway")
})
})
- describe("reload model override behavior", () => {
- beforeEach(() => {
- // Mock buildApiHandler to return a handler with getModel
- const mockApiHandler = {
- getModel: vi.fn().mockReturnValue({ id: "test-model", info: {} }),
- createMessage: vi.fn(),
- countTokens: vi.fn(),
- }
- vi.spyOn(apiIndex, "buildApiHandler").mockReturnValue(mockApiHandler as any)
- })
-
- afterEach(() => {
- vi.restoreAllMocks()
- })
-
- it("should use custom model for explicit autocomplete profiles", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const provider = supportedProviders[0]
- const customModelId = "custom-autocomplete-model"
-
- const profiles = [
- {
- id: "1",
- name: "My Autocomplete Profile",
- apiProvider: provider,
- profileType: "autocomplete",
- },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "My Autocomplete Profile",
- apiProvider: provider,
- profileType: "autocomplete",
- mistralApiKey: "test-key",
- apiModelId: customModelId, // Custom model set by user
- } as any)
-
- // Mock buildApiHandler to return the custom model
- const mockApiHandler = {
- getModel: vi.fn().mockReturnValue({ id: customModelId, info: {} }),
- createMessage: vi.fn(),
- countTokens: vi.fn(),
- }
- vi.spyOn(apiIndex, "buildApiHandler").mockReturnValue(mockApiHandler as any)
-
+ describe("generateFimResponse", () => {
+ it("throws when connection service is not available", async () => {
const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- // The model should use the custom model from the profile
- const modelName = model.getModelName()
- expect(modelName).toBe(customModelId)
+ await expect(model.generateFimResponse("prefix", "suffix", vi.fn())).rejects.toThrow(
+ "Connection service is not available",
+ )
})
- it("should override model for non-autocomplete profiles", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const provider = supportedProviders[0]
- const defaultAutocompleteModel = AUTOCOMPLETE_PROVIDER_MODELS.get(provider)
-
- const profiles = [
- {
- id: "1",
- name: "My Chat Profile",
- apiProvider: provider,
- profileType: "chat", // Not an autocomplete profile
- },
- ] as any
-
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "My Chat Profile",
- apiProvider: provider,
- profileType: "chat",
- mistralApiKey: "test-key",
- apiModelId: "custom-chat-model", // This should be overridden
- } as any)
-
- // Mock buildApiHandler to return the overridden model
- const mockApiHandler = {
- getModel: vi.fn().mockReturnValue({ id: defaultAutocompleteModel, info: {} }),
- createMessage: vi.fn(),
- countTokens: vi.fn(),
- }
- vi.spyOn(apiIndex, "buildApiHandler").mockReturnValue(mockApiHandler as any)
-
- const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- // The model should be overridden with the default autocomplete model
- const modelName = model.getModelName()
- expect(modelName).toBe(defaultAutocompleteModel)
+ it("throws when not connected", async () => {
+ const connection = createMockConnectionService("disconnected")
+ const model = new AutocompleteModel(connection)
+ await expect(model.generateFimResponse("prefix", "suffix", vi.fn())).rejects.toThrow(
+ "CLI backend is not connected (state: disconnected)",
+ )
})
- it("should override model for profiles without profileType", async () => {
- const supportedProviders = [...AUTOCOMPLETE_PROVIDER_MODELS.keys()]
- const provider = supportedProviders[0]
- const defaultAutocompleteModel = AUTOCOMPLETE_PROVIDER_MODELS.get(provider)
-
- const profiles = [
+ it("streams chunks and returns metadata", async () => {
+ const chunks = [
+ { choices: [{ delta: { content: "hello" } }] },
{
- id: "1",
- name: "My Generic Profile",
- apiProvider: provider,
- // No profileType specified
+ choices: [{ delta: { content: " world" } }],
+ usage: { prompt_tokens: 10, completion_tokens: 5 },
+ cost: 0.001,
},
- ] as any
+ ]
- vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles)
- vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({
- id: "1",
- name: "My Generic Profile",
- apiProvider: provider,
- mistralApiKey: "test-key",
- apiModelId: "custom-model", // This should be overridden
- } as any)
+ const connection = createMockConnectionService("connected")
+ const client = (connection as any).getClient()
+ client.kilo.fim.mockResolvedValue({
+ stream: (async function* () {
+ for (const chunk of chunks) yield chunk
+ })(),
+ })
- // Mock buildApiHandler to return the overridden model
- const mockApiHandler = {
- getModel: vi.fn().mockReturnValue({ id: defaultAutocompleteModel, info: {} }),
- createMessage: vi.fn(),
- countTokens: vi.fn(),
- }
- vi.spyOn(apiIndex, "buildApiHandler").mockReturnValue(mockApiHandler as any)
+ const model = new AutocompleteModel(connection)
+ const received: string[] = []
+ const result = await model.generateFimResponse("prefix", "suffix", (text) => received.push(text))
+ expect(received).toEqual(["hello", " world"])
+ expect(result).toEqual({
+ cost: 0.001,
+ inputTokens: 10,
+ outputTokens: 5,
+ cacheWriteTokens: 0,
+ cacheReadTokens: 0,
+ })
+ })
+
+ it("passes model parameters to fim call", async () => {
+ const connection = createMockConnectionService("connected")
+ const client = (connection as any).getClient()
+ client.kilo.fim.mockResolvedValue({
+ stream: (async function* () {})(),
+ })
+
+ const model = new AutocompleteModel(connection)
+ const signal = new AbortController().signal
+ await model.generateFimResponse("pre", "suf", vi.fn(), signal)
+
+ expect(client.kilo.fim).toHaveBeenCalledWith(
+ {
+ prefix: "pre",
+ suffix: "suf",
+ model: "mistralai/codestral-2508",
+ maxTokens: 256,
+ temperature: 0.2,
+ },
+ { signal },
+ )
+ })
+ })
+
+ describe("generateResponse", () => {
+ it("throws because FIM is the primary strategy", async () => {
const model = new AutocompleteModel()
- await model.reload(mockProviderSettingsManager)
-
- // The model should be overridden with the default autocomplete model
- const modelName = model.getModelName()
- expect(modelName).toBe(defaultAutocompleteModel)
+ await expect(model.generateResponse("system", "user", vi.fn())).rejects.toThrow(
+ "Chat-based completions are not supported via CLI backend",
+ )
})
})
})
diff --git a/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts b/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts
index 456eaa5fbcd..5d4c68a34cf 100644
--- a/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts
@@ -63,13 +63,8 @@ vi.mock("vscode", () => {
vi.mock("../AutocompleteModel", () => {
class AutocompleteModel {
- public loaded = false
public profileName = "test-profile"
- public async reload(): Promise {
- this.loaded = true
- }
-
public getModelName(): string {
return "test-model"
}
@@ -169,7 +164,9 @@ async function createManager(): Promise {
postStateToWebview: vi.fn().mockResolvedValue(undefined),
}
- const manager = new AutocompleteServiceManager(context, cline as any)
+ const connection = { onStateChange: vi.fn().mockReturnValue(() => {}), ...cline }
+
+ const manager = new AutocompleteServiceManager(context, connection as any)
await manager.load()
diff --git a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts
index 113247c89e7..6dc2bb97c9b 100644
--- a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts
@@ -15,10 +15,6 @@ export class ChatTextAreaAutocomplete {
this.telemetry = new AutocompleteTelemetry("chat-textarea")
}
- async initialize(): Promise {
- return this.model.reload()
- }
-
async getCompletion(userText: string, visibleCodeContext?: VisibleCodeContext): Promise<{ suggestion: string }> {
const startTime = Date.now()
@@ -29,13 +25,6 @@ export class ChatTextAreaAutocomplete {
provider: this.model.getProviderDisplayName(),
}
- if (!this.model.loaded) {
- const loaded = await this.initialize()
- if (!loaded) {
- return { suggestion: "" }
- }
- }
-
// Check if model has valid credentials (but don't require FIM)
if (!this.model.hasValidCredentials()) {
return { suggestion: "" }
diff --git a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/__tests__/ChatTextAreaAutocomplete.spec.ts b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/__tests__/ChatTextAreaAutocomplete.spec.ts
index dc43ff1b076..6fb284743d5 100644
--- a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/__tests__/ChatTextAreaAutocomplete.spec.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/__tests__/ChatTextAreaAutocomplete.spec.ts
@@ -16,7 +16,6 @@ describe("ChatTextAreaAutocomplete", () => {
it("should work with non-FIM models using chat-based completion", async () => {
// Setup: Model without FIM support (like Mistral)
const mockModel = new AutocompleteModel()
- mockModel.loaded = true
vi.spyOn(mockModel, "hasValidCredentials").mockReturnValue(true)
vi.spyOn(mockModel, "supportsFim").mockReturnValue(false)
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/ar.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/ar.ts
index c85054d374d..c2df6d81c72 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/ar.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/ar.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (معطل)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**لا يوجد رصيد في حسابك**\n\nحساب Kilo Code الخاص بك لا يحتوي على رصيد. لاستخدام الإكمال التلقائي، يرجى إضافة رصيد إلى حسابك.\n\n[فتح الإعدادات]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**لم يتم تكوين نموذج الإكمال التلقائي**\n\nلتمكين الإكمال التلقائي، أضف ملفًا شخصيًا مع أحد هذه المزودين المدعومين: {{providers}}.\n\n[فتح الإعدادات]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "إجمالي تكلفة الجلسة:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/ca.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/ca.ts
index df4b70cff80..92a7802c492 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/ca.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/ca.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (desactivat)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**No tens crèdits al teu compte**\n\nEl teu compte de Kilo Code no té crèdits. Per utilitzar l'autocompletat, si us plau afegeix crèdits al teu compte.\n\n[Obrir Configuració]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**No s'ha configurat cap model d'autocompletat**\n\nPer habilitar l'autocompletat, afegeix un perfil amb un d'aquests proveïdors compatibles: {{providers}}.\n\n[Obrir Configuració]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Cost total de la sessió:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/cs.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/cs.ts
index 7b22314cd5f..a12a46c8a0c 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/cs.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/cs.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (zakázáno)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Na tvém účtu nejsou žádné kredity**\n\nTvůj účet Kilo Code nemá žádné kredity. Pro použití automatického doplňování prosím přidej kredity na svůj účet.\n\n[Otevřít Nastavení]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Není nakonfigurován žádný model automatického doplňování**\n\nPro povolení automatického doplňování přidej profil s jedním z těchto podporovaných poskytovatelů: {{providers}}.\n\n[Otevřít Nastavení]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Celkové náklady relace:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/de.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/de.ts
index 507187a4e50..2525a491c04 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/de.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/de.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (deaktiviert)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Kein Guthaben auf deinem Konto**\n\nDein Kilo Code Konto hat kein Guthaben. Um Autocomplete zu nutzen, füge bitte Guthaben zu deinem Konto hinzu.\n\n[Einstellungen öffnen]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Kein Autocomplete-Modell konfiguriert**\n\nUm Autocomplete zu aktivieren, füge ein Profil mit einem dieser unterstützten Anbieter hinzu: {{providers}}.\n\n[Einstellungen öffnen]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Sitzungsgesamtkosten:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/en.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/en.ts
index a0d5d2f02fe..bd536d23c07 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/en.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/en.ts
@@ -7,8 +7,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (disabled)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**No credits in your account**\n\nYour Kilo Code account has no credits. To use autocomplete, please add credits to your account.\n\n[Open Settings]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**No autocomplete model configured**\n\nTo enable autocomplete, add a profile with one of these supported providers: {{providers}}.\n\n[Open Settings]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Session total cost:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/es.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/es.ts
index adf5e07a993..2348ca32e9a 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/es.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/es.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (deshabilitado)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**No hay créditos en tu cuenta**\n\nTu cuenta de Kilo Code no tiene créditos. Para usar el autocompletado, por favor añade créditos a tu cuenta.\n\n[Abrir Configuración]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**No hay modelo de autocompletado configurado**\n\nPara habilitar el autocompletado, añade un perfil con uno de estos proveedores compatibles: {{providers}}.\n\n[Abrir Configuración]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Costo total de la sesión:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/fr.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/fr.ts
index 65f9d13a86f..56dd0f139aa 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/fr.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/fr.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (désactivé)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Pas de crédits sur ton compte**\n\nTon compte Kilo Code n'a pas de crédits. Pour utiliser l'autocomplétion, ajoute des crédits à ton compte.\n\n[Ouvrir les Paramètres]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Aucun modèle d'autocomplétion configuré**\n\nPour activer l'autocomplétion, ajoute un profil avec l'un de ces fournisseurs pris en charge : {{providers}}.\n\n[Ouvrir les Paramètres]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Coût total de la session :",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/hi.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/hi.ts
index 7cb24ae396c..b23e34b5bb4 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/hi.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/hi.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (अक्षम)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**आपके खाते में कोई क्रेडिट नहीं है**\n\nआपके Kilo Code खाते में कोई क्रेडिट नहीं है। ऑटोकम्प्लीट का उपयोग करने के लिए, कृपया अपने खाते में क्रेडिट जोड़ें।\n\n[सेटिंग्स खोलें]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**कोई ऑटोकम्प्लीट मॉडल कॉन्फ़िगर नहीं किया गया**\n\nऑटोकम्प्लीट सक्षम करने के लिए, इन समर्थित प्रदाताओं में से एक के साथ एक प्रोफ़ाइल जोड़ें: {{providers}}।\n\n[सेटिंग्स खोलें]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "सत्र की कुल लागत:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/id.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/id.ts
index 062ebf6f835..4f7384d404d 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/id.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/id.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (dinonaktifkan)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Tidak ada kredit di akun kamu**\n\nAkun Kilo Code kamu tidak memiliki kredit. Untuk menggunakan autocomplete, silakan tambahkan kredit ke akun kamu.\n\n[Buka Pengaturan]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Tidak ada model autocomplete yang dikonfigurasi**\n\nUntuk mengaktifkan autocomplete, tambahkan profil dengan salah satu penyedia yang didukung ini: {{providers}}.\n\n[Buka Pengaturan]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Total biaya sesi:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/it.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/it.ts
index bdc75654b60..5bd075d981b 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/it.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/it.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (disabilitato)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Nessun credito nel tuo account**\n\nIl tuo account Kilo Code non ha crediti. Per usare l'autocompletamento, aggiungi crediti al tuo account.\n\n[Apri Impostazioni]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Nessun modello di autocompletamento configurato**\n\nPer abilitare l'autocompletamento, aggiungi un profilo con uno di questi provider supportati: {{providers}}.\n\n[Apri Impostazioni]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Costo totale della sessione:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/ja.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/ja.ts
index b4d3a9f2772..ad59af271d7 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/ja.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/ja.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete(無効)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**アカウントにクレジットがありません**\n\nKilo Code アカウントにクレジットがありません。オートコンプリートを使用するには、アカウントにクレジットを追加してください。\n\n[設定を開く]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**オートコンプリートモデルが設定されていません**\n\nオートコンプリートを有効にするには、これらのサポートされているプロバイダーのいずれかでプロファイルを追加してください: {{providers}}。\n\n[設定を開く]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "セッション合計コスト:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/ko.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/ko.ts
index cc55702d80b..cc4eab6dfb8 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/ko.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/ko.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (비활성화됨)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**계정에 크레딧이 없습니다**\n\nKilo Code 계정에 크레딧이 없습니다. 자동완성을 사용하려면 계정에 크레딧을 추가해 주세요.\n\n[설정 열기]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**자동완성 모델이 구성되지 않았습니다**\n\n자동완성을 활성화하려면 다음 지원되는 제공업체 중 하나로 프로필을 추가하세요: {{providers}}.\n\n[설정 열기]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "세션 총 비용:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/nl.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/nl.ts
index 8ca573dab41..b30b2d2834d 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/nl.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/nl.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (uitgeschakeld)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Geen tegoed op je account**\n\nJe Kilo Code account heeft geen tegoed. Om autocomplete te gebruiken, voeg tegoed toe aan je account.\n\n[Instellingen openen]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Geen autocomplete model geconfigureerd**\n\nOm autocomplete in te schakelen, voeg een profiel toe met een van deze ondersteunde providers: {{providers}}.\n\n[Instellingen openen]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Totale sessiekosten:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/pl.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/pl.ts
index 7bb800c8d6c..64251cd2df7 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/pl.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/pl.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (wyłączone)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Brak środków na koncie**\n\nTwoje konto Kilo Code nie ma środków. Aby korzystać z autouzupełniania, dodaj środki do swojego konta.\n\n[Otwórz Ustawienia]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Nie skonfigurowano modelu autouzupełniania**\n\nAby włączyć autouzupełnianie, dodaj profil z jednym z tych obsługiwanych dostawców: {{providers}}.\n\n[Otwórz Ustawienia]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Całkowity koszt sesji:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/pt-BR.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/pt-BR.ts
index a4794f2cf73..8c879217de2 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/pt-BR.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/pt-BR.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (desabilitado)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Sem créditos na sua conta**\n\nSua conta Kilo Code não tem créditos. Para usar o autocompletar, adicione créditos à sua conta.\n\n[Abrir Configurações]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Nenhum modelo de autocompletar configurado**\n\nPara habilitar o autocompletar, adicione um perfil com um destes provedores suportados: {{providers}}.\n\n[Abrir Configurações]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Custo total da sessão:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/ru.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/ru.ts
index 4e1afb0d3f0..bb572c5f3e4 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/ru.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/ru.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (отключено)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**На вашем счёте нет кредитов**\n\nНа вашем счёте Kilo Code нет кредитов. Чтобы использовать автодополнение, пожалуйста, пополните счёт.\n\n[Открыть Настройки]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Модель автодополнения не настроена**\n\nЧтобы включить автодополнение, добавьте профиль с одним из поддерживаемых провайдеров: {{providers}}.\n\n[Открыть Настройки]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Общая стоимость сессии:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/sk.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/sk.ts
index 4e2959c3b98..8ff2f3c8e15 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/sk.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/sk.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (zakázané)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Na tvojom účte nie sú žiadne kredity**\n\nTvoj účet Kilo Code nemá žiadne kredity. Pre použitie automatického doplňovania prosím pridaj kredity na svoj účet.\n\n[Otvoriť Nastavenia]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Nie je nakonfigurovaný žiadny model automatického doplňovania**\n\nPre povolenie automatického doplňovania pridaj profil s jedným z týchto podporovaných poskytovateľov: {{providers}}.\n\n[Otvoriť Nastavenia]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Celkové náklady relácie:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/th.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/th.ts
index 3f3b18aa595..3ceeca319f3 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/th.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/th.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (ปิดใช้งาน)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**ไม่มีเครดิตในบัญชีของคุณ**\n\nบัญชี Kilo Code ของคุณไม่มีเครดิต หากต้องการใช้การเติมข้อความอัตโนมัติ กรุณาเพิ่มเครดิตในบัญชีของคุณ\n\n[เปิดการตั้งค่า]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**ไม่ได้กำหนดค่าโมเดลการเติมข้อความอัตโนมัติ**\n\nหากต้องการเปิดใช้งานการเติมข้อความอัตโนมัติ ให้เพิ่มโปรไฟล์กับผู้ให้บริการที่รองรับเหล่านี้: {{providers}}\n\n[เปิดการตั้งค่า]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "ค่าใช้จ่ายรวมของเซสชัน:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/tr.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/tr.ts
index d7c9586926b..417ba03ee1c 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/tr.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/tr.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (devre dışı)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Hesabınızda kredi yok**\n\nKilo Code hesabınızda kredi bulunmuyor. Otomatik tamamlamayı kullanmak için lütfen hesabınıza kredi ekleyin.\n\n[Ayarları Aç]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Otomatik tamamlama modeli yapılandırılmadı**\n\nOtomatik tamamlamayı etkinleştirmek için desteklenen sağlayıcılardan biriyle bir profil ekleyin: {{providers}}.\n\n[Ayarları Aç]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Oturum toplam maliyeti:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/uk.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/uk.ts
index 57d742b661b..3dc1b1b32a4 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/uk.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/uk.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (вимкнено)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**На вашому рахунку немає кредитів**\n\nНа вашому рахунку Kilo Code немає кредитів. Щоб використовувати автодоповнення, будь ласка, поповніть рахунок.\n\n[Відкрити Налаштування]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Модель автодоповнення не налаштована**\n\nЩоб увімкнути автодоповнення, додайте профіль з одним із підтримуваних провайдерів: {{providers}}.\n\n[Відкрити Налаштування]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Загальна вартість сесії:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/vi.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/vi.ts
index c1f9f90775a..f645e3e2fe0 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/vi.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/vi.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (đã tắt)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**Không có tín dụng trong tài khoản của bạn**\n\nTài khoản Kilo Code của bạn không có tín dụng. Để sử dụng tự động hoàn thành, vui lòng thêm tín dụng vào tài khoản của bạn.\n\n[Mở Cài đặt]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**Chưa cấu hình mô hình tự động hoàn thành**\n\nĐể bật tự động hoàn thành, hãy thêm hồ sơ với một trong các nhà cung cấp được hỗ trợ sau: {{providers}}.\n\n[Mở Cài đặt]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Tổng chi phí phiên:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/zh-CN.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/zh-CN.ts
index 4efa687064d..da8cf67adbf 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/zh-CN.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/zh-CN.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete(已禁用)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**账户余额不足**\n\n你的 Kilo Code 账户没有余额。要使用自动补全功能,请为账户充值。\n\n[打开设置]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**未配置自动补全模型**\n\n要启用自动补全,请添加一个使用以下支持的提供商的配置文件:{{providers}}。\n\n[打开设置]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "会话总费用:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/i18n/zh-TW.ts b/packages/kilo-vscode/src/services/autocomplete/i18n/zh-TW.ts
index a6bb2fd388b..7bce63c2476 100644
--- a/packages/kilo-vscode/src/services/autocomplete/i18n/zh-TW.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/i18n/zh-TW.ts
@@ -6,8 +6,6 @@ export const dict = {
"kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete",
"kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete(已停用)",
- "kilocode:autocomplete.statusBar.tooltip.noCredits":
- "**帳戶中沒有額度**\n\nKilo Code 帳戶沒有額度。若要使用 Autocomplete,請為帳戶儲值。\n\n[開啟設定]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.noUsableProvider":
"**未設定 Autocomplete 模型**\n\n若要啟用 Autocomplete,請新增一個使用以下支援供應商的設定檔:{{providers}}。\n\n[開啟設定]({{command}})",
"kilocode:autocomplete.statusBar.tooltip.sessionTotal": "工作階段總費用:",
diff --git a/packages/kilo-vscode/src/services/autocomplete/types.ts b/packages/kilo-vscode/src/services/autocomplete/types.ts
index d32531f55f7..cce2f84cdc7 100644
--- a/packages/kilo-vscode/src/services/autocomplete/types.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/types.ts
@@ -137,7 +137,6 @@ export interface AutocompleteStatusBarStateProps {
model?: string
provider?: string
profileName?: string | null
- hasKilocodeProfileWithNoBalance?: boolean
hasNoUsableProvider?: boolean
totalSessionCost: number
completionCount: number
diff --git a/packages/kilo-vscode/src/services/marketplace/installer.ts b/packages/kilo-vscode/src/services/marketplace/installer.ts
index 7cf55351ef5..b1d2bcb9b58 100644
--- a/packages/kilo-vscode/src/services/marketplace/installer.ts
+++ b/packages/kilo-vscode/src/services/marketplace/installer.ts
@@ -3,9 +3,13 @@ import * as path from "path"
import * as os from "os"
import { execFile } from "child_process"
import { promisify } from "util"
+import * as yaml from "yaml"
import type {
MarketplaceItem,
SkillMarketplaceItem,
+ McpMarketplaceItem,
+ ModeMarketplaceItem,
+ McpInstallationMethod,
InstallMarketplaceItemOptions,
InstallResult,
RemoveResult,
@@ -24,9 +28,87 @@ export class MarketplaceInstaller {
): Promise {
const scope = options.target ?? "project"
if (item.type === "skill") return this.installSkill(item, scope, workspace)
- return { success: false, slug: item.id, error: `${item.type} installation not yet implemented` }
+ if (item.type === "mcp") return this.installMcp(item, options, scope, workspace)
+ return this.installMode(item, scope, workspace)
}
+ // ── MCP ─────────────────────────────────────────────────────────────
+
+ async installMcp(
+ item: McpMarketplaceItem,
+ options: InstallMarketplaceItemOptions,
+ scope: "project" | "global",
+ workspace?: string,
+ ): Promise {
+ if (scope === "project" && !workspace) {
+ return { success: false, slug: item.id, error: "No workspace directory for project-scope install" }
+ }
+
+ const config = await this.readConfig(scope, workspace)
+ if (!config.mcp) config.mcp = {}
+
+ if (config.mcp[item.id]) {
+ return { success: false, slug: item.id, error: "MCP server already installed. Remove it first." }
+ }
+
+ const content = this.resolveMcpContent(item, options)
+ if (!content) {
+ return { success: false, slug: item.id, error: "No installation content for MCP server" }
+ }
+
+ try {
+ config.mcp[item.id] = this.buildMcpEntry(content, options.parameters)
+ } catch (err) {
+ return { success: false, slug: item.id, error: `Invalid MCP config: ${err}` }
+ }
+
+ await this.writeConfig(scope, workspace, config)
+ return { success: true, slug: item.id }
+ }
+
+ private resolveMcpContent(item: McpMarketplaceItem, options: InstallMarketplaceItemOptions): string | undefined {
+ if (typeof item.content === "string") return item.content
+ if (!Array.isArray(item.content) || item.content.length === 0) return undefined
+ const name = options.parameters?.__method as string | undefined
+ if (name) {
+ const found = item.content.find((m: McpInstallationMethod) => m.name === name)
+ if (found) return found.content
+ }
+ return item.content[0].content
+ }
+
+ private buildMcpEntry(content: string, params?: Record): Record {
+ const filtered = Object.fromEntries(Object.entries(params ?? {}).filter(([k]) => k !== "__method"))
+ const replaced = Object.keys(filtered).length > 0 ? substituteParams(content, filtered) : content
+ return JSON.parse(replaced)
+ }
+
+ // ── Mode ────────────────────────────────────────────────────────────
+
+ async installMode(
+ item: ModeMarketplaceItem,
+ scope: "project" | "global",
+ workspace?: string,
+ ): Promise {
+ if (scope === "project" && !workspace) {
+ return { success: false, slug: item.id, error: "No workspace directory for project-scope install" }
+ }
+
+ const config = await this.readConfig(scope, workspace)
+ if (!config.agent) config.agent = {}
+
+ if (config.agent[item.id]) {
+ return { success: false, slug: item.id, error: "Mode already installed. Remove it first." }
+ }
+
+ config.agent[item.id] = convertModeToAgent(item.content)
+
+ await this.writeConfig(scope, workspace, config)
+ return { success: true, slug: item.id }
+ }
+
+ // ── Skill ───────────────────────────────────────────────────────────
+
async installSkill(
item: SkillMarketplaceItem,
scope: "project" | "global",
@@ -106,9 +188,34 @@ export class MarketplaceInstaller {
}
}
+ // ── Remove ──────────────────────────────────────────────────────────
+
async remove(item: MarketplaceItem, scope: "project" | "global", workspace?: string): Promise {
if (item.type === "skill") return this.removeSkill(item, scope, workspace)
- return { success: false, slug: item.id, error: `${item.type} removal not yet implemented` }
+ if (item.type === "mcp") return this.removeMcp(item, scope, workspace)
+ return this.removeMode(item, scope, workspace)
+ }
+
+ async removeMcp(item: McpMarketplaceItem, scope: "project" | "global", workspace?: string): Promise {
+ const config = await this.readConfig(scope, workspace)
+ if (!config.mcp?.[item.id]) {
+ return { success: true, slug: item.id }
+ }
+ delete config.mcp[item.id]
+ if (Object.keys(config.mcp).length === 0) delete config.mcp
+ await this.writeConfig(scope, workspace, config)
+ return { success: true, slug: item.id }
+ }
+
+ async removeMode(item: ModeMarketplaceItem, scope: "project" | "global", workspace?: string): Promise {
+ const config = await this.readConfig(scope, workspace)
+ if (!config.agent?.[item.id]) {
+ return { success: true, slug: item.id }
+ }
+ delete config.agent[item.id]
+ if (Object.keys(config.agent).length === 0) delete config.agent
+ await this.writeConfig(scope, workspace, config)
+ return { success: true, slug: item.id }
}
async removeSkill(
@@ -136,6 +243,32 @@ export class MarketplaceInstaller {
return { success: false, slug: item.id, error: String(err) }
}
}
+
+ // ── Config helpers ──────────────────────────────────────────────────
+
+ private async readConfig(
+ scope: "project" | "global",
+ workspace?: string,
+ ): Promise>> {
+ const filepath = this.paths.configPath(scope, workspace)
+ try {
+ const content = await fs.readFile(filepath, "utf-8")
+ return JSON.parse(content)
+ } catch (err) {
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return {}
+ throw err
+ }
+ }
+
+ private async writeConfig(
+ scope: "project" | "global",
+ workspace: string | undefined,
+ config: Record,
+ ): Promise {
+ const filepath = this.paths.configPath(scope, workspace)
+ await fs.mkdir(path.dirname(filepath), { recursive: true })
+ await fs.writeFile(filepath, JSON.stringify(config, null, 2) + "\n", "utf-8")
+ }
}
// ── Helpers ─────────────────────────────────────────────────────────
@@ -145,6 +278,66 @@ function isSafeId(id: string): boolean {
return /^[\w\-@.]+$/.test(id)
}
+// Group name → opencode permission key mapping (mirrors ModesMigrator)
+const GROUP_PERMISSIONS: Record = {
+ read: "read",
+ edit: "edit",
+ browser: "bash",
+ command: "bash",
+ mcp: "mcp",
+}
+const ALL_PERMISSIONS = ["read", "edit", "bash", "mcp"]
+
+function convertModeToAgent(content: string): Record {
+ const mode = yaml.parse(content) as Record
+ const groups = (mode.groups ?? []) as Array]>
+
+ const permission: Record = {}
+ const allowed = new Set()
+ for (const group of groups) {
+ if (typeof group === "string") {
+ const key = GROUP_PERMISSIONS[group] ?? group
+ allowed.add(key)
+ permission[key] = "allow"
+ } else if (Array.isArray(group)) {
+ const [name, cfg] = group
+ const key = GROUP_PERMISSIONS[name] ?? name
+ allowed.add(key)
+ permission[key] = cfg?.fileRegex ? { [String(cfg.fileRegex)]: "allow", "*": "deny" } : "allow"
+ }
+ }
+ for (const perm of ALL_PERMISSIONS) {
+ if (!allowed.has(perm)) permission[perm] = "deny"
+ }
+
+ const prompt = [mode.roleDefinition, mode.customInstructions].filter(Boolean).join("\n\n")
+ return {
+ mode: "primary",
+ description: mode.description ?? mode.whenToUse ?? mode.name,
+ prompt,
+ permission,
+ }
+}
+
+function escapeJsonValue(raw: string): string {
+ return raw
+ .replace(/\\/g, "\\\\")
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/\t/g, "\\t")
+}
+
+function substituteParams(template: string, params: Record): string {
+ let result = template
+ for (const [key, value] of Object.entries(params)) {
+ const escaped = escapeJsonValue(String(value ?? ""))
+ result = result.replaceAll(`{{${key}}}`, escaped)
+ result = result.replaceAll(`\${${key}}`, escaped)
+ }
+ return result
+}
+
async function findEscapedPaths(dir: string): Promise {
const resolved = path.resolve(dir)
const escaped: string[] = []
diff --git a/packages/kilo-vscode/src/services/telemetry/types.ts b/packages/kilo-vscode/src/services/telemetry/types.ts
index 5c7e106fd52..74c676a2204 100644
--- a/packages/kilo-vscode/src/services/telemetry/types.ts
+++ b/packages/kilo-vscode/src/services/telemetry/types.ts
@@ -32,8 +32,10 @@ export enum TelemetryEventName {
PROMPT_ENHANCED = "Prompt Enhanced",
// Marketplace
+ MARKETPLACE_INSTALL_BUTTON_CLICKED = "Marketplace Install Button Clicked",
MARKETPLACE_ITEM_INSTALLED = "Marketplace Item Installed",
MARKETPLACE_ITEM_REMOVED = "Marketplace Item Removed",
+ MARKETPLACE_TAB_VIEWED = "Marketplace Tab Viewed",
// Account & Auth
ACCOUNT_CONNECT_CLICKED = "Account Connect Clicked",
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts
index cdda6f3081c..71978fdc544 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts
@@ -160,22 +160,6 @@ test.describe("Permission Dock Dropdown — write", () => {
const root = page.locator("#storybook-root")
await expect(root).toHaveScreenshot(["permission-dock-dropdown", "write-expanded-pending.png"])
})
-
- test("rules expanded — mixed decisions", async ({ page }) => {
- await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
- await disableAnimations(page)
- await page.waitForSelector("#storybook-root *", { state: "attached" })
- await openDropdown(page)
-
- // Approve first rule, deny second (dispatchEvent bypasses tooltip overlays)
- const approveButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="approve"]')
- const denyButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="deny"]')
- await approveButtons.first().dispatchEvent("click")
- await denyButtons.nth(1).dispatchEvent("click")
-
- const root = page.locator("#storybook-root")
- await expect(root).toHaveScreenshot(["permission-dock-dropdown", "write-rules-mixed.png"])
- })
})
// ---------------------------------------------------------------------------
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-expanded-pending-chromium-linux.png
index f760f80b321..21b378872a5 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-expanded-pending-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-expanded-pending-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6ba76e2d5b993e999914fd0538650728adf78f7ad70978d8ee9982525f6ab5e8
-size 17626
+oid sha256:3ef8ec5886054bf0e7ae288c66ce5175204e74728c3e37f3669c551be98755b7
+size 12476
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-approved-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-approved-chromium-linux.png
index 25eae19771e..93171b5fe6b 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-approved-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-approved-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:29872fa36a6a6ba9e20765f92380b86f9a91beb9702b3cdb95914ea46a2cf40c
-size 17833
+oid sha256:54ff3bd3916245aa1dc65384db4a0b5908b4d78cc3700157112f1be63ce054f9
+size 12663
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-denied-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-denied-chromium-linux.png
index 7840f5c9b49..5631e23340a 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-denied-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-denied-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ce915e449cdec411c00df613ffc1017f95dba69737e0474fc8c483ea9f284026
-size 17872
+oid sha256:9edcfe9f76314110e9c1795ee0008ca35187ce28a19c86cfcf1f8bf499fe151c
+size 12714
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rules-mixed-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rules-mixed-chromium-linux.png
index d0b71517797..c53756f6978 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rules-mixed-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rules-mixed-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b39675360da34fc6cb9c1ff4e51afeff79417ca4baa88816967d2b0a4ece858f
-size 18043
+oid sha256:4e314513c9b4e964ea3b9a7cdbfc561e57eab2fa867aadfd370d1c0ad68c11b8
+size 12903
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/edit-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/edit-expanded-pending-chromium-linux.png
index 5deb79c5feb..adad64c8820 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/edit-expanded-pending-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/edit-expanded-pending-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c3b7d96f123e880b1395d160d17a48f385ef59344326c44d7cd13b342d2ca49e
-size 24193
+oid sha256:a3b9925b5424dd81e0fd60dc3cc4307730abdc010cccd73e7243292880dd6b09
+size 16833
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/external-dir-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/external-dir-expanded-pending-chromium-linux.png
index fc41dfc1e64..d0e34686389 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/external-dir-expanded-pending-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/external-dir-expanded-pending-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:739b8eb94e5cde39e4127a6ed3c499392174a380812e2e3497965643cac3dee9
-size 22265
+oid sha256:ef1d55223be06fd9d16566bc80a525c84c933a337ee4ccf2c41809e4984571b6
+size 17276
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-expanded-pending-chromium-linux.png
index 8e6f8a9a320..2eef307facd 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-expanded-pending-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-expanded-pending-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:62ef30b05bf2ec47a7ddd02a03f5f1cded5ced9cf9332dd3bd50d6809bcce96d
-size 18447
+oid sha256:5fb585654180101c29bd4e7efbc7129bf29e8681bc764cdae0d8233fe302a29e
+size 12711
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-rule-approved-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-rule-approved-chromium-linux.png
index da8a959b81f..efe40035d7e 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-rule-approved-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-rule-approved-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1f9864861136b578c4e46ece7dae5a139ada7e7be3d219462ef3a549b94239ec
-size 18648
+oid sha256:c0702dda15977143eeac1557040a583946f2fed4b1610c97a772cff4ae3f2482
+size 12842
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-expanded-pending-chromium-linux.png
index 90aa5cfaa2e..d5681a0b999 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-expanded-pending-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-expanded-pending-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:514803fa46f43ff58d8d403e630afc5c01a8841ac420d406755515bceb38beae
-size 23132
+oid sha256:9784fcf6612167075eb796887c9ac6ad8ab1596ef03dd0ace2b159f82dcb3675
+size 17978
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-mixed-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-mixed-chromium-linux.png
index 840e6dcdbc4..be1e061301c 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-mixed-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-mixed-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:578f6daa54a25fba38b049d623e2b12069b0cbe1c5e094031d8a0b0af0a56f52
-size 23966
+oid sha256:606cc8d90900a26682852c9eb14d24305f31672ecd40265954b7b85cca7d3c48
+size 18850
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/subagent-expanded-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/subagent-expanded-chromium-linux.png
index 949e7d47de5..4baec13ecc9 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/subagent-expanded-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/subagent-expanded-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:91ba11c0a81ced5dfe824a4c9e8fdcbaebaaddc660ef2b245df56903e180e4b7
-size 18949
+oid sha256:ab71bff42d0bd60c84f75cc04d91fdae8516667913152a0ae4e792268368c9d1
+size 13844
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/websearch-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/websearch-expanded-pending-chromium-linux.png
index e1016e91f6c..321c6b3dc37 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/websearch-expanded-pending-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/websearch-expanded-pending-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6f8bc26191620881314eff9ac921abba4e201c5af7ec8d222e405fdee9ddae9a
-size 17909
+oid sha256:8d37fd5942ec471c7f534b7b65ac568cdde7cd1b7f3542c50a72500ac3823cae
+size 12827
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-expanded-pending-chromium-linux.png
index feff311b196..77520553e46 100644
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-expanded-pending-chromium-linux.png
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-expanded-pending-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ce112e9762168271fd8b7b4316b740008269e9d248a668887adce3dde13478f0
-size 18362
+oid sha256:04377b0ddbaa0e2f4f126adff431db902fbff65ce653f2bc59434d150a833578
+size 15638
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-rules-mixed-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-rules-mixed-chromium-linux.png
deleted file mode 100644
index 83a6fd71b75..00000000000
--- a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-rules-mixed-chromium-linux.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d499d73467c37052d1277deefdd419e370a8239d7e117806d8442b2b9cc851a7
-size 18886
diff --git a/packages/kilo-vscode/tests/setup/vscode-mock.ts b/packages/kilo-vscode/tests/setup/vscode-mock.ts
index adf1dfafda8..8fdba932d30 100644
--- a/packages/kilo-vscode/tests/setup/vscode-mock.ts
+++ b/packages/kilo-vscode/tests/setup/vscode-mock.ts
@@ -51,6 +51,14 @@ const mockVscode = {
update: async () => {},
}),
asRelativePath: (pathOrUri: string) => pathOrUri,
+ fs: {
+ createDirectory: async () => {},
+ writeFile: async () => {},
+ readFile: async () => new Uint8Array(),
+ readDirectory: async () => [],
+ delete: async () => {},
+ stat: async () => ({ type: 1, ctime: 0, mtime: 0, size: 0 }),
+ },
},
window: {
activeTextEditor: undefined,
diff --git a/packages/kilo-vscode/tests/unit/extension-arch.test.ts b/packages/kilo-vscode/tests/unit/extension-arch.test.ts
new file mode 100644
index 00000000000..8a935d450d2
--- /dev/null
+++ b/packages/kilo-vscode/tests/unit/extension-arch.test.ts
@@ -0,0 +1,86 @@
+/**
+ * Architecture test: package.json ↔ source command sync
+ *
+ * Every command declared in package.json contributes.commands must have a
+ * matching registerCommand() call somewhere in src/. A declaration without
+ * an implementation causes a silent "command not found" error at runtime
+ * that is hard to diagnose — VS Code shows no warning at activation time.
+ */
+
+import { describe, it, expect } from "bun:test"
+import fs from "node:fs"
+import path from "node:path"
+
+const ROOT = path.resolve(import.meta.dir, "../..")
+const PKG_JSON_FILE = path.join(ROOT, "package.json")
+const SRC_DIR = path.join(ROOT, "src")
+
+function readSrcFiles(dir: string): string {
+ const parts: string[] = []
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, entry.name)
+ if (entry.isDirectory()) {
+ parts.push(readSrcFiles(full))
+ } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".spec.ts")) {
+ parts.push(fs.readFileSync(full, "utf-8"))
+ }
+ }
+ return parts.join("\n")
+}
+
+describe("Extension — package.json command sync", () => {
+ const pkg = JSON.parse(fs.readFileSync(PKG_JSON_FILE, "utf-8"))
+ const declared: string[] = pkg.contributes?.commands?.map((c: { command: string }) => c.command) ?? []
+ const source = readSrcFiles(SRC_DIR)
+
+ // Extract command IDs that appear in registerCommand() calls specifically.
+ // This avoids false positives from executeCommand() or other string references.
+ const registered = new Set([...source.matchAll(/registerCommand\s*\(\s*["']([^"']+)["']/g)].map((m) => m[1]))
+
+ /**
+ * Every command declared in package.json must be registered via registerCommand()
+ * somewhere in src/. A bare string match would accept executeCommand() references,
+ * which don't actually register a handler.
+ *
+ * Commands registered via template literals (e.g. jumpTo${i}) are detected by
+ * checking the dynamic registerCommand pattern in source instead.
+ */
+ it("every contributes.commands entry has a registerCommand() call", () => {
+ // Commands generated via template literals can't be extracted by regex,
+ // so verify the dynamic registration pattern exists in source instead.
+ const dynamic: Record = {
+ "kilo-code.new.agentManager.jumpTo": "registerCommand(`kilo-code.new.agentManager.jumpTo${",
+ }
+
+ const missing: string[] = []
+ for (const cmd of declared) {
+ const entry = Object.entries(dynamic).find(([prefix]) => cmd.startsWith(prefix))
+ if (entry) {
+ const [, pattern] = entry
+ if (!source.includes(pattern)) missing.push(`${cmd} (dynamic pattern not found)`)
+ continue
+ }
+ if (!registered.has(cmd)) missing.push(cmd)
+ }
+
+ expect(
+ missing,
+ `Commands declared in package.json but not registered via registerCommand().\n` +
+ `Add registerCommand("...", ...) or remove the declaration:\n` +
+ missing.map((m) => ` - ${m}`).join("\n"),
+ ).toEqual([])
+ })
+
+ /**
+ * All declared commands must use the kilo-code.new. prefix.
+ * The legacy kilo-code.* namespace (without .new.) belongs to the old
+ * extension and must not be reintroduced.
+ */
+ it("all declared commands use the kilo-code.new. prefix", () => {
+ const bad = declared.filter((cmd) => !cmd.startsWith("kilo-code.new."))
+ expect(
+ bad,
+ `Commands without "kilo-code.new." prefix — use the namespaced form:\n` + bad.map((b) => ` - ${b}`).join("\n"),
+ ).toEqual([])
+ })
+})
diff --git a/packages/kilo-vscode/tests/unit/i18n-keys.test.ts b/packages/kilo-vscode/tests/unit/i18n-keys.test.ts
new file mode 100644
index 00000000000..3410f5a2ad3
--- /dev/null
+++ b/packages/kilo-vscode/tests/unit/i18n-keys.test.ts
@@ -0,0 +1,407 @@
+/**
+ * Translation key validation
+ *
+ * 1. Ensures every string-literal translation key passed to a t() function
+ * actually exists in the corresponding English dictionary.
+ * 2. Ensures every English key has a translation in all other locale files.
+ *
+ * Three independent key pools are checked:
+ * - Webview (sidebar + agent manager): merged from app, ui, kilo-i18n, agent-manager dicts
+ * - CLI backend (extension-side server-manager): cli-backend/i18n dict
+ * - Autocomplete (extension-side): autocomplete/i18n dict
+ *
+ * Dynamic keys (template literals, variables) are intentionally skipped —
+ * only string literals are validated.
+ */
+
+import { describe, it, expect } from "bun:test"
+import { Glob } from "bun"
+import path from "node:path"
+
+// ── Webview dictionaries ────────────────────────────────────────────────────
+
+// Layer 1: app-local (sidebar)
+import { dict as appEn } from "../../webview-ui/src/i18n/en"
+import { dict as appZh } from "../../webview-ui/src/i18n/zh"
+import { dict as appZht } from "../../webview-ui/src/i18n/zht"
+import { dict as appKo } from "../../webview-ui/src/i18n/ko"
+import { dict as appDe } from "../../webview-ui/src/i18n/de"
+import { dict as appEs } from "../../webview-ui/src/i18n/es"
+import { dict as appFr } from "../../webview-ui/src/i18n/fr"
+import { dict as appDa } from "../../webview-ui/src/i18n/da"
+import { dict as appJa } from "../../webview-ui/src/i18n/ja"
+import { dict as appPl } from "../../webview-ui/src/i18n/pl"
+import { dict as appRu } from "../../webview-ui/src/i18n/ru"
+import { dict as appAr } from "../../webview-ui/src/i18n/ar"
+import { dict as appNo } from "../../webview-ui/src/i18n/no"
+import { dict as appBr } from "../../webview-ui/src/i18n/br"
+import { dict as appTh } from "../../webview-ui/src/i18n/th"
+import { dict as appBs } from "../../webview-ui/src/i18n/bs"
+
+// Layer 2: upstream UI (@opencode-ai/ui re-exported via @kilocode/kilo-ui)
+import { dict as uiEn } from "../../../ui/src/i18n/en"
+import { dict as uiZh } from "../../../ui/src/i18n/zh"
+import { dict as uiZht } from "../../../ui/src/i18n/zht"
+import { dict as uiKo } from "../../../ui/src/i18n/ko"
+import { dict as uiDe } from "../../../ui/src/i18n/de"
+import { dict as uiEs } from "../../../ui/src/i18n/es"
+import { dict as uiFr } from "../../../ui/src/i18n/fr"
+import { dict as uiDa } from "../../../ui/src/i18n/da"
+import { dict as uiJa } from "../../../ui/src/i18n/ja"
+import { dict as uiPl } from "../../../ui/src/i18n/pl"
+import { dict as uiRu } from "../../../ui/src/i18n/ru"
+import { dict as uiAr } from "../../../ui/src/i18n/ar"
+import { dict as uiNo } from "../../../ui/src/i18n/no"
+import { dict as uiBr } from "../../../ui/src/i18n/br"
+import { dict as uiTh } from "../../../ui/src/i18n/th"
+import { dict as uiBs } from "../../../ui/src/i18n/bs"
+
+// Layer 3: kilo-i18n overrides
+import { dict as kiloEn } from "../../../kilo-i18n/src/en"
+import { dict as kiloZh } from "../../../kilo-i18n/src/zh"
+import { dict as kiloZht } from "../../../kilo-i18n/src/zht"
+import { dict as kiloKo } from "../../../kilo-i18n/src/ko"
+import { dict as kiloDe } from "../../../kilo-i18n/src/de"
+import { dict as kiloEs } from "../../../kilo-i18n/src/es"
+import { dict as kiloFr } from "../../../kilo-i18n/src/fr"
+import { dict as kiloDa } from "../../../kilo-i18n/src/da"
+import { dict as kiloJa } from "../../../kilo-i18n/src/ja"
+import { dict as kiloPl } from "../../../kilo-i18n/src/pl"
+import { dict as kiloRu } from "../../../kilo-i18n/src/ru"
+import { dict as kiloAr } from "../../../kilo-i18n/src/ar"
+import { dict as kiloNo } from "../../../kilo-i18n/src/no"
+import { dict as kiloBr } from "../../../kilo-i18n/src/br"
+import { dict as kiloTh } from "../../../kilo-i18n/src/th"
+import { dict as kiloBs } from "../../../kilo-i18n/src/bs"
+
+// Layer 4: agent manager (locale alignment already tested in agent-manager-i18n-split.test.ts)
+import { dict as amEn } from "../../webview-ui/agent-manager/i18n/en"
+
+// ── Extension-side dictionaries ─────────────────────────────────────────────
+
+import { dict as cliEn } from "../../src/services/cli-backend/i18n/en"
+import { dict as cliZh } from "../../src/services/cli-backend/i18n/zh"
+import { dict as cliZht } from "../../src/services/cli-backend/i18n/zht"
+import { dict as cliDe } from "../../src/services/cli-backend/i18n/de"
+import { dict as cliEs } from "../../src/services/cli-backend/i18n/es"
+import { dict as cliFr } from "../../src/services/cli-backend/i18n/fr"
+import { dict as cliDa } from "../../src/services/cli-backend/i18n/da"
+import { dict as cliJa } from "../../src/services/cli-backend/i18n/ja"
+import { dict as cliKo } from "../../src/services/cli-backend/i18n/ko"
+import { dict as cliPl } from "../../src/services/cli-backend/i18n/pl"
+import { dict as cliRu } from "../../src/services/cli-backend/i18n/ru"
+import { dict as cliAr } from "../../src/services/cli-backend/i18n/ar"
+import { dict as cliNo } from "../../src/services/cli-backend/i18n/no"
+import { dict as cliBr } from "../../src/services/cli-backend/i18n/br"
+import { dict as cliTh } from "../../src/services/cli-backend/i18n/th"
+import { dict as cliBs } from "../../src/services/cli-backend/i18n/bs"
+
+import { dict as acEn } from "../../src/services/autocomplete/i18n/en"
+
+// ── Locale maps ─────────────────────────────────────────────────────────────
+
+const ROOT = path.resolve(import.meta.dir, "../..")
+
+const appLocales: Record> = {
+ en: appEn,
+ zh: appZh,
+ zht: appZht,
+ ko: appKo,
+ de: appDe,
+ es: appEs,
+ fr: appFr,
+ da: appDa,
+ ja: appJa,
+ pl: appPl,
+ ru: appRu,
+ ar: appAr,
+ no: appNo,
+ br: appBr,
+ th: appTh,
+ bs: appBs,
+}
+
+const kiloLocales: Record> = {
+ en: kiloEn,
+ zh: kiloZh,
+ zht: kiloZht,
+ ko: kiloKo,
+ de: kiloDe,
+ es: kiloEs,
+ fr: kiloFr,
+ da: kiloDa,
+ ja: kiloJa,
+ pl: kiloPl,
+ ru: kiloRu,
+ ar: kiloAr,
+ no: kiloNo,
+ br: kiloBr,
+ th: kiloTh,
+ bs: kiloBs,
+}
+
+const uiLocales: Record> = {
+ en: uiEn,
+ zh: uiZh,
+ zht: uiZht,
+ ko: uiKo,
+ de: uiDe,
+ es: uiEs,
+ fr: uiFr,
+ da: uiDa,
+ ja: uiJa,
+ pl: uiPl,
+ ru: uiRu,
+ ar: uiAr,
+ no: uiNo,
+ br: uiBr,
+ th: uiTh,
+ bs: uiBs,
+}
+
+const cliLocales: Record> = {
+ en: cliEn,
+ zh: cliZh,
+ zht: cliZht,
+ de: cliDe,
+ es: cliEs,
+ fr: cliFr,
+ da: cliDa,
+ ja: cliJa,
+ ko: cliKo,
+ pl: cliPl,
+ ru: cliRu,
+ ar: cliAr,
+ no: cliNo,
+ br: cliBr,
+ th: cliTh,
+ bs: cliBs,
+}
+
+// Merge webview dictionaries in the same priority order as language.tsx
+const webviewKeys = new Set(Object.keys({ ...appEn, ...uiEn, ...kiloEn, ...amEn }))
+const cliKeys = new Set(Object.keys(cliEn))
+const acKeys = new Set(Object.keys(acEn))
+
+// ── File scanning ───────────────────────────────────────────────────────────
+
+interface Missing {
+ file: string
+ line: number
+ key: string
+}
+
+/**
+ * Regex to match t("key") / t('key') calls, including multiline.
+ *
+ * The `s` (dotAll) flag lets `\s*` span newlines so that calls like:
+ * t(
+ * "key"
+ * )
+ * are captured. Runs against the entire file content, not line-by-line.
+ */
+const T_CALL = /(?:^|[^a-zA-Z_$])t\(\s*(?:["'])([^"']+)["']/gs
+
+function offsetToLine(content: string, offset: number): number {
+ let line = 1
+ for (let i = 0; i < offset && i < content.length; i++) {
+ if (content[i] === "\n") line++
+ }
+ return line
+}
+
+function extractKeys(content: string): Array<{ line: number; key: string }> {
+ const results: Array<{ line: number; key: string }> = []
+ let match: RegExpExecArray | null
+ T_CALL.lastIndex = 0
+ while ((match = T_CALL.exec(content)) !== null) {
+ const key = match[1]
+ if (key) results.push({ line: offsetToLine(content, match.index), key })
+ }
+ return results
+}
+
+async function collectFiles(glob: Glob, dir: string): Promise {
+ const files: string[] = []
+ for await (const entry of glob.scan({ cwd: dir, absolute: true })) {
+ files.push(entry)
+ }
+ return files
+}
+
+// ── Webview files ───────────────────────────────────────────────────────────
+
+async function findWebviewMissing(): Promise {
+ const glob = new Glob("**/*.{ts,tsx}")
+ const dirs = [path.join(ROOT, "webview-ui/src"), path.join(ROOT, "webview-ui/agent-manager")]
+
+ const files: string[] = []
+ for (const dir of dirs) {
+ files.push(...(await collectFiles(glob, dir)))
+ }
+
+ // Exclude i18n dictionary files themselves and storybook files
+ const filtered = files.filter((f) => !f.includes("/i18n/") && !f.includes(".stories.") && !f.includes("/stories/"))
+
+ const missing: Missing[] = []
+ for (const file of filtered) {
+ const content = await Bun.file(file).text()
+ for (const { line, key } of extractKeys(content)) {
+ if (!webviewKeys.has(key)) {
+ missing.push({ file: path.relative(ROOT, file), line, key })
+ }
+ }
+ }
+ return missing
+}
+
+// ── CLI backend files ───────────────────────────────────────────────────────
+
+async function findCliBackendMissing(): Promise {
+ const glob = new Glob("**/*.ts")
+ const dir = path.join(ROOT, "src/services/cli-backend")
+
+ const files = (await collectFiles(glob, dir)).filter((f) => !f.includes("/i18n/"))
+
+ const missing: Missing[] = []
+ for (const file of files) {
+ const content = await Bun.file(file).text()
+ for (const { line, key } of extractKeys(content)) {
+ if (!cliKeys.has(key)) {
+ missing.push({ file: path.relative(ROOT, file), line, key })
+ }
+ }
+ }
+ return missing
+}
+
+// ── Autocomplete files ──────────────────────────────────────────────────────
+
+async function findAutocompleteMissing(): Promise {
+ const glob = new Glob("**/*.ts")
+ const dir = path.join(ROOT, "src/services/autocomplete")
+
+ const files = (await collectFiles(glob, dir)).filter((f) => !f.includes("/i18n/") && !f.includes("/shims/"))
+
+ const missing: Missing[] = []
+ for (const file of files) {
+ const content = await Bun.file(file).text()
+ for (const { line, key } of extractKeys(content)) {
+ if (!acKeys.has(key)) {
+ missing.push({ file: path.relative(ROOT, file), line, key })
+ }
+ }
+ }
+ return missing
+}
+
+// ── Locale completeness helpers ─────────────────────────────────────────────
+
+function findMissingLocaleKeys(
+ en: Record,
+ locales: Record>,
+): Array<{ locale: string; key: string }> {
+ const base = Object.keys(en)
+ const results: Array<{ locale: string; key: string }> = []
+ for (const [locale, dict] of Object.entries(locales)) {
+ if (locale === "en") continue
+ const keys = new Set(Object.keys(dict))
+ for (const key of base) {
+ if (!keys.has(key)) {
+ results.push({ locale, key })
+ }
+ }
+ }
+ return results
+}
+
+function formatLocaleReport(items: Array<{ locale: string; key: string }>): string {
+ return items.map((m) => ` [${m.locale}] "${m.key}"`).join("\n")
+}
+
+// ── Tests ───────────────────────────────────────────────────────────────────
+
+function formatReport(missing: Missing[]): string {
+ return missing.map((m) => ` ${m.file}:${m.line} — "${m.key}"`).join("\n")
+}
+
+describe("i18n key validation — no missing translation keys", () => {
+ it("webview: all t() string literal keys exist in merged dictionaries", async () => {
+ const missing = await findWebviewMissing()
+ if (missing.length > 0) {
+ expect(
+ missing,
+ `Found ${missing.length} translation key(s) not present in any dictionary:\n${formatReport(missing)}`,
+ ).toEqual([])
+ }
+ expect(missing).toEqual([])
+ })
+
+ it("cli-backend: all t() string literal keys exist in cli-backend dictionary", async () => {
+ const missing = await findCliBackendMissing()
+ if (missing.length > 0) {
+ expect(
+ missing,
+ `Found ${missing.length} translation key(s) not present in cli-backend dictionary:\n${formatReport(missing)}`,
+ ).toEqual([])
+ }
+ expect(missing).toEqual([])
+ })
+
+ it("autocomplete: all t() string literal keys exist in autocomplete dictionary", async () => {
+ const missing = await findAutocompleteMissing()
+ if (missing.length > 0) {
+ expect(
+ missing,
+ `Found ${missing.length} translation key(s) not present in autocomplete dictionary:\n${formatReport(missing)}`,
+ ).toEqual([])
+ }
+ expect(missing).toEqual([])
+ })
+})
+
+describe("i18n locale completeness — every English key exists in all locales", () => {
+ it("shared UI: every English key has a translation in all locales", () => {
+ const missing = findMissingLocaleKeys(uiEn, uiLocales)
+ if (missing.length > 0) {
+ expect(
+ missing,
+ `Found ${missing.length} missing shared UI translation(s):\n${formatLocaleReport(missing)}`,
+ ).toEqual([])
+ }
+ expect(missing).toEqual([])
+ })
+
+ it("sidebar app: every English key has a translation in all locales", () => {
+ const missing = findMissingLocaleKeys(appEn, appLocales)
+ if (missing.length > 0) {
+ expect(
+ missing,
+ `Found ${missing.length} missing sidebar translation(s):\n${formatLocaleReport(missing)}`,
+ ).toEqual([])
+ }
+ expect(missing).toEqual([])
+ })
+
+ it("kilo-i18n: every English key has a translation in all locales", () => {
+ const missing = findMissingLocaleKeys(kiloEn, kiloLocales)
+ if (missing.length > 0) {
+ expect(
+ missing,
+ `Found ${missing.length} missing kilo-i18n translation(s):\n${formatLocaleReport(missing)}`,
+ ).toEqual([])
+ }
+ expect(missing).toEqual([])
+ })
+
+ it("cli-backend: every English key has a translation in all locales", () => {
+ const missing = findMissingLocaleKeys(cliEn, cliLocales)
+ if (missing.length > 0) {
+ expect(
+ missing,
+ `Found ${missing.length} missing cli-backend translation(s):\n${formatLocaleReport(missing)}`,
+ ).toEqual([])
+ }
+ expect(missing).toEqual([])
+ })
+})
diff --git a/packages/kilo-vscode/tests/unit/image-preview.test.ts b/packages/kilo-vscode/tests/unit/image-preview.test.ts
new file mode 100644
index 00000000000..6e52993b032
--- /dev/null
+++ b/packages/kilo-vscode/tests/unit/image-preview.test.ts
@@ -0,0 +1,61 @@
+import { describe, expect, it } from "bun:test"
+import { buildPreviewPath, getPreviewCommand, getPreviewDir, parseImage, trimEntries } from "../../src/image-preview"
+
+describe("parseImage", () => {
+ it("parses png data urls and preserves a clean extension", () => {
+ const img = parseImage("data:image/png;base64,aGVsbG8=", "screen")
+
+ expect(img).not.toBeNull()
+ expect(img?.name).toBe("screen.png")
+ expect(img?.ext).toBe("png")
+ expect(Buffer.from(img?.data ?? []).toString("utf8")).toBe("hello")
+ })
+
+ it("sanitizes the basename and keeps the existing extension", () => {
+ const img = parseImage("data:image/jpeg;base64,aGVsbG8=", "../../bad name!!.jpeg")
+
+ expect(img).not.toBeNull()
+ expect(img?.name).toBe("bad-name.jpeg")
+ expect(img?.ext).toBe("jpg")
+ })
+
+ it("returns null for non-image data urls", () => {
+ expect(parseImage("data:text/plain;base64,aGVsbG8=", "note.txt")).toBeNull()
+ })
+
+ it("returns null when the header is not base64", () => {
+ expect(parseImage("data:image/png,hello", "screen.png")).toBeNull()
+ })
+})
+
+describe("buildPreviewPath", () => {
+ it("writes previews into a dedicated storage folder", () => {
+ expect(buildPreviewPath("screen.png", 42)).toBe("image-preview/42-screen.png")
+ })
+})
+
+describe("getPreviewDir", () => {
+ it("returns the preview storage folder", () => {
+ expect(getPreviewDir()).toBe("image-preview")
+ })
+})
+
+describe("trimEntries", () => {
+ it("drops the oldest preview paths once the limit is exceeded", () => {
+ const items = Array.from({ length: 22 }, (_, i) => ({ path: `${String(i).padStart(2, "0")}-screen.png` }))
+
+ expect(trimEntries(items)).toEqual(["00-screen.png", "01-screen.png"])
+ })
+
+ it("keeps all preview paths when below the limit", () => {
+ expect(trimEntries([{ path: "01-screen.png" }])).toEqual([])
+ })
+})
+
+describe("getPreviewCommand", () => {
+ it("targets the built-in preview editor with explicit sizing", () => {
+ const uri = { toString: () => "file:///tmp/screen.png" }
+
+ expect(getPreviewCommand(uri)).toEqual(["imagePreview.previewEditor", { resource: uri, size: "contain" }])
+ })
+})
diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts
index a9d8fc7e373..4e80af488da 100644
--- a/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts
+++ b/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts
@@ -284,6 +284,7 @@ describe("mapSSEEventToWebviewMessage", () => {
expect(msg.permission.args).toEqual({ path: "/foo" })
expect(msg.permission.message).toBe("Permission required: read_file")
expect(msg.permission.patterns).toEqual(["**/*.ts"])
+ expect(msg.permission.always).toEqual([])
}
})
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png
index 20dc4b679f4..78b20d2cc55 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:de6be977bd3805fdbbbb1fdaf3498201c1097adea26ebe460c56b681d1828e39
-size 9457
+oid sha256:c5ada07e94ff10490732ba4b27d8e204102816aa00e472324a31ee9f8c7e8484
+size 9470
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-many-options-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-many-options-chromium-linux.png
index 96968cf183f..55d93f78ee7 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-many-options-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-many-options-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3164d510232a0457cf935a3032f736825486f133e89994e544c678ccc7e6849a
-size 30652
+oid sha256:0bbec9564c9fa46fce155ada8e176641e540c912461102b7b424d317b3283c38
+size 31033
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png
index d0c6d2dd6fb..675a2de7c0f 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:dc40d7bbc5f3eaee3b48b4f90046a10e54db3288e71214eec190e4b0fa61cc16
-size 22777
+oid sha256:48ebccc3ab71403efa74803f140b17d97a000989b64dd42c65d8f9774d059cb8
+size 23139
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png
index aba396e2ab2..cd8af2ea67a 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3da4a2d29601e45eb4b5fe007a62a203c2b5151b2d74437d22630d2ddc8582a6
-size 28723
+oid sha256:371621a81d9c42d1dc327492fb2e3d2516857dfa189adf3c924ac6951d33e910
+size 29073
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-all-done-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-all-done-chromium-linux.png
index d90db8bb538..ed2dad3b0af 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-all-done-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-all-done-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bc5f6d5b31970c33aaf062756bbdd73637db17f1bc74c1558e9a81c33c5d602d
-size 4582
+oid sha256:742b7939e4b5a803413aad42418fe60f8d138bf6153087baae8cc22fba4202ae
+size 4755
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-chromium-linux.png
index b677959c414..963e13adb0a 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/task-header-with-todos-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cd828877cfcbb7d3f3af475c2ca5f92b2fd627ae41f1a03725ab91a948b40833
-size 4704
+oid sha256:5ab73f8c0585e9f889c51073428aa0e7082d7aecdc3d5b0c459720c1b860c436
+size 4890
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png
index 6959a564fa5..9b826fda0b6 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cf635f68a3a3ea84caa026f3be0481f21a2a7424acd8d2fdc138f8018f843af8
+oid sha256:0e29bb3e5a3d408f6a8edabbf12a59a53c3e39e7fd3efa7cadc0e3c7c380e5cc
size 18908
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png
index 9a067ead3ed..680cfc7b893 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b3656d23c630a2d7f412d2fc3eee231f45cbe47e6d1e4e7ca44ae4cca3c3160b
-size 15323
+oid sha256:c6226c4c30415c1e8d1103003464f3a7c86df362bb7b49d2d37400acfd4c1372
+size 12528
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png
index 9d67d03d5db..60c88a1a1c0 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c95ebadbd707aab03b0b14858cd9e25c4e7b06e8976abf76e377b9eaa5bd2144
-size 16900
+oid sha256:fb035f3cea015f0a5e693ac3bef31848c5c596642948ddd3f524699cd3637b23
+size 14245
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/mcp-tool-cards-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/mcp-tool-cards-chromium-linux.png
index cc354b28d32..a60c467b659 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/mcp-tool-cards-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/mcp-tool-cards-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:22c9bc250a347451c3a194f3ec701bfaf9db67f3104d12bde92adffc08bcd30b
-size 6263
+oid sha256:61c9ddc7067281976b9183e0696952d1ed2f3453c67f54b3c8766ddf1f9b3ba5
+size 6402
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png
index bd787278cb7..a45de7ff4c4 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8f7fd08433524c113a2d7b3fed8bef5c023d9a451621f658b836c7967ca954aa
-size 8310
+oid sha256:52f85f9f63eccd6217f37616c525df311e4d0a558547ae127366a7388e859dee
+size 8395
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-bash-many-rules-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-bash-many-rules-chromium-linux.png
index 0fc73f39049..f333bde8d1c 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-bash-many-rules-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-bash-many-rules-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1c43a0a2f6a351d53da542aba6b68113e8231c1ee9cf7276bf6a16263b65d811
-size 18775
+oid sha256:1cf3791372e3e69fd759f2596e72211c982e8694990103c178f1aed0c59229f1
+size 16139
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-edit-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-edit-chromium-linux.png
index 7c9631bb33b..21522caf85e 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-edit-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-edit-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d23b2a1621e33e92e65c7ead23b42f05decd443b370ef7122b840e6f5cf043e1
-size 21177
+oid sha256:1f3dc016ba452b8eaf69c5f4532d0edc4915c43c345a62029fcbaad46f8ac902
+size 16140
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-external-dir-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-external-dir-chromium-linux.png
index 4002998e5ab..e3762cf5dd8 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-external-dir-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-external-dir-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:63b8651967a042ef6e79f721fc4bf99eae875296dbd991566d3c3b7e90f04669
-size 18542
+oid sha256:469f10e66c7505ff9034663696fdefc400f21803612fb5df8a58f4f991155ac3
+size 15945
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-subagent-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-subagent-chromium-linux.png
index cf8c0e73185..d07a188e24a 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-subagent-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-subagent-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:94ef29b3eca3b0e9e407daf9c17388dcf5735c134a74bc13500559bcf9707c9b
-size 16338
+oid sha256:36b72898b310b3c4e57fbc506e5ee663d74ed64ed2967d8a9d1201da50904bf0
+size 13583
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-todo-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-todo-chromium-linux.png
index ec79a3cc440..d5e3bd0b083 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-todo-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-todo-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:168eacdc6523223587bad5f72f3828e03211733b4dc1fa39f152409089e03ff3
-size 16468
+oid sha256:6a1ebf498979d1e4371169657582cfb4fe0cf50f2dce9a666c8b3f66355057e1
+size 13824
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-websearch-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-websearch-chromium-linux.png
index 4667f970ab5..e9221b20914 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-websearch-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-websearch-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9c2402fb61b05c355cedea5a26c39f4a5b4d4a1e04c0fb8ac1ac50edbdc746b3
-size 16426
+oid sha256:cce4f8672aba4f77ed12ecd59fa0b4846ee6673d59e91de748e5f3333f124292
+size 13766
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png
index 542c4451ee0..fe72453f42b 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:38712f08584902b1ccb267fed0271c6c1a9b5f549b47bd743ca344604df2095a
-size 17433
+oid sha256:2fc8f143da673f83abe1def14f87aa325ecfb8127de9c5f66c97096e43dc1f28
+size 14876
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-above-chatbox-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-above-chatbox-chromium-linux.png
index a62b761700f..39eccc33fde 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-above-chatbox-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-above-chatbox-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0dcef8172fb810da542e5d4f364a60fa8cb0d0d3a212ad939e0117ca91b5b57c
-size 30461
+oid sha256:b3f472c100d4432064af12da764bd44baa3295ad4b20c6158cc3fa467667797a
+size 30794
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-completed-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-completed-chromium-linux.png
index 7ee8b1c81ae..5d43a5e77bf 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-completed-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-completed-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7e3d24b86091c0041a1e7426209a1d1d826b656f04b9fed1d662e3d5b67aa95f
-size 6185
+oid sha256:74d53efec74b56fcd689b1b2de4facb0570306995a6f5451d38e8303b65bbcc6
+size 6187
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png
index 2c82c3e045e..d034bd3697b 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:84e3f1c43bd36828f6548f3a3a85827693b370f6f3fc319408829eb742146bb5
-size 19287
+oid sha256:38d2cef0a125b5b6bba81af716c1accd522a60e0c915c8f9a7be502382bfcb2b
+size 14661
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png
index 492118610c0..b1f51e7cdea 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d2c75a7bb06035f07f364c7fb7a1eb0b813663658d018e4eea881c48cc0e276c
-size 8698
+oid sha256:ce16f0acdd7d82bb2b1bfd993e015cf89f1d20d50d2c045a6ad08fd4a6382c50
+size 8931
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-mcp-card-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-mcp-card-chromium-linux.png
new file mode 100644
index 00000000000..a4d77020e8c
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-mcp-card-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5d027fc81aa110b8a3fa94816c18ddd7d104ed1fc103abd234c39e805637afce
+size 12263
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-mode-card-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-mode-card-chromium-linux.png
new file mode 100644
index 00000000000..a20ba79fd7a
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-mode-card-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a69ac58dc72885950a702661b75bc1989a28e8b07b3136037255891b7e5a3b06
+size 12839
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png
index 2822662560f..86b45cb700e 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/installed-skill-card-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7bc9274e748f94e0ae979f688a87c52b9a3d579ac015a2a49f327f8c96d002db
-size 11116
+oid sha256:0d5400d9db070953fcbf4cc3f8a4968b80c5bccb1a67ee63a463aa0539de1f41
+size 11190
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-empty-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-empty-chromium-linux.png
new file mode 100644
index 00000000000..e6db4c0c7e4
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-empty-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1edca81fb2c946f87df5436684418e63b7a9c761079e44f024971a81316b8263
+size 6549
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-with-installed-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-with-installed-chromium-linux.png
new file mode 100644
index 00000000000..59da02c6806
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-with-installed-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ce4ba314abb23bb43694d512ade273d99760f250b061e235899d86e170bb5f9a
+size 52892
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-with-items-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-with-items-chromium-linux.png
new file mode 100644
index 00000000000..62f2ae9b606
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/mcp-tab-with-items-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:631d41ecf6216249943722a797f75c3aefb2f84ebdb7ee80529662a26fa35af5
+size 50206
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-empty-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-empty-chromium-linux.png
new file mode 100644
index 00000000000..f190c5fac6e
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-empty-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c925148c349c6ab1f14190a2a379aa24869de35e5b77455fa15d8868eb08343
+size 5943
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-with-installed-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-with-installed-chromium-linux.png
new file mode 100644
index 00000000000..b43f90cbf3b
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-with-installed-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:30346f68598419d1fe80a3f33d3326cc844a30356ebedb022a841be4096514c6
+size 53909
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-with-items-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-with-items-chromium-linux.png
new file mode 100644
index 00000000000..b86b588ce51
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/modes-tab-with-items-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e654374e5c12a4a184a0d27709cd773684e31e08a3d7b6305358849cf30c75be
+size 50986
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/single-mcp-card-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/single-mcp-card-chromium-linux.png
new file mode 100644
index 00000000000..ba9374ed8bd
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/single-mcp-card-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9399ad494d6bc3302cc01f6f815763662c43d1eafe0b2ecff4b8eacb5dc5e8ef
+size 10770
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/single-mode-card-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/single-mode-card-chromium-linux.png
new file mode 100644
index 00000000000..ec8a7a5e249
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/single-mode-card-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b7e17426d82ff00ac8194963a3c3be946994018e2f6c9553dfaee237cb043fd9
+size 11324
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png
index 204b46b36ef..b7d88719025 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-installed-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:be90b297cbdef33fa1c3c67be7a42e4c00a5f3d68147a5fbf54a04ab7b261d04
-size 52330
+oid sha256:782c1fae4bed19825b56570e573d09cc80aa2180ca5c6a4c495867f207d95beb
+size 54728
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png
index 1bda64ef203..899e1a2c4ad 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/marketplace/skills-tab-with-items-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d27dc2c85a377f7daab0b7de444a7d883a4503ab45c92c0c0a9155de89e2a221
-size 49775
+oid sha256:3d309302b177f97f198a2b889c96d08cf4578d219043d48a9d0251460be411c4
+size 52039
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx
index 274e38c9076..1fb48cb6c6b 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx
+++ b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx
@@ -247,7 +247,14 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
{(img) => (
-

+

+ vscode.postMessage({ type: "previewImage", dataUrl: img.dataUrl, filename: img.filename })
+ }
+ />
-
+
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx
index df3d4c3d79d..944927b538c 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx
@@ -20,6 +20,7 @@ import { formatRelativeDate } from "../../utils/date"
import { CloudImportDialog } from "./CloudImportDialog"
import { FeedbackDialog } from "./FeedbackDialog"
import { VscodeSessionTurn } from "./VscodeSessionTurn"
+import { RevertBanner } from "./RevertBanner"
import { WorkingIndicator } from "../shared/WorkingIndicator"
import { activeUserMessageID as getActiveUserMessageID } from "../../context/session-queue"
@@ -64,8 +65,14 @@ export const MessageList: Component = (props) => {
}
})
- const userMessages = () => session.userMessages()
- const isEmpty = () => userMessages().length === 0 && !session.loading()
+ const allUserMessages = () => session.userMessages()
+ const boundary = () => session.revert()?.messageID
+ const userMessages = createMemo(() => {
+ const b = boundary()
+ if (!b) return allUserMessages()
+ return allUserMessages().filter((m) => m.id < b)
+ })
+ const isEmpty = () => userMessages().length === 0 && !session.loading() && !boundary()
const recent = createMemo(() =>
[...session.sessions()]
@@ -153,6 +160,9 @@ export const MessageList: Component = (props) => {
)
}}
+
+
+
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx
index fd041287bbb..d884ee8c87c 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx
@@ -4,7 +4,9 @@
* Uses kilo-ui's DockPrompt component for proper surface styling.
*
* Per-rule toggles allow users to approve/deny individual permission rules for future requests.
- * The command buttons (Deny / Allow Always / Allow Once) control the current command.
+ * For bash, the hierarchical rules from metadata.rules are shown.
+ * For other tools, the always array is shown so users can configure per-tool permissions.
+ * The command buttons (Deny / Allow Once) control the current command.
* When all rules are toggled ✓, the command auto-runs.
*/
@@ -24,14 +26,14 @@ let rulesExpandedPreference = false
export const PermissionDock: Component<{
request: PermissionRequest
responding: boolean
- onDecide: (response: "once" | "always" | "reject", approvedAlways: string[], deniedAlways: string[]) => void
+ onDecide: (response: "once" | "reject", approvedAlways: string[], deniedAlways: string[]) => void
}> = (props) => {
const session = useSession()
const language = useLanguage()
const fromChild = () => props.request.sessionID !== session.currentSessionID()
- // Bash sends fine-grained rules via metadata.rules; other tools have no dropdown.
- const rules = () => props.request.args?.rules ?? []
+ // Bash sends fine-grained rules via metadata.rules; other tools use the always array.
+ const rules = () => props.request.args?.rules ?? props.request.always ?? []
// Rules like "git *" or "git log *" — strip the trailing wildcard for display.
// A bare "*" (global wildcard) becomes empty so only the tool name shows.
const label = (rule: string) => (rule === "*" ? "" : rule.replace(/ \*$/, ""))
@@ -43,7 +45,6 @@ export const PermissionDock: Component<{
const [decisions, setDecisions] = createSignal>({})
const [expanded, setExpanded] = createSignal(rulesExpandedPreference)
- const hasDenied = () => Object.values(decisions()).some((d) => d === "denied")
const hasRules = () => rules().length > 0
const toggleExpanded = () => {
@@ -179,7 +180,6 @@ export const PermissionDock: Component<{
{toolDescription()}
-
{language.t("ui.permission.sessionHint")}
-