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) => (
- {img.filename} + {img.filename} + 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")}

- + 1}> + + +
+ + +
+ + {(file) => ( +
+ {file.file} + +
+ )} +
+
+
+
{language.t("revert.banner.hint")}
+ + + ) +} diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx index 47b3703d5c4..83a1dd91337 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx @@ -29,6 +29,7 @@ import type { } from "@kilocode/sdk/v2" import { ErrorDisplay } from "./ErrorDisplay" import { useServer } from "../../context/server" +import { useSession } from "../../context/session" function getDirectory(path: string): string { const sep = path.includes("/") ? "/" : "\\" @@ -53,6 +54,7 @@ export const VscodeSessionTurn: Component = (props) => { const i18n = useI18n() const diffComponent = useDiffComponent() const server = useServer() + const session = useSession() const emptyMessages: SDKMessage[] = [] const emptyParts: SDKPart[] = [] @@ -155,6 +157,11 @@ export const VscodeSessionTurn: Component = (props) => { parts={parts() as unknown as Parameters[0]["parts"]} interrupted={interrupted()} queued={props.queued} + onRevert={ + assistantMessages().length > 0 && session.status() === "idle" && !session.revert() + ? () => session.revertSession(props.messageID) + : undefined + } /> diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/InstallModal.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/InstallModal.tsx index e070513b5de..15dfb87402c 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/InstallModal.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/InstallModal.tsx @@ -1,71 +1,119 @@ -import { Component, createSignal, onMount, onCleanup, Show, For } from "solid-js" +import { createSignal, createEffect, onCleanup, Show, For } from "solid-js" +import { Dialog } from "@kilocode/kilo-ui/dialog" import { Button } from "@kilocode/kilo-ui/button" import { RadioGroup } from "@kilocode/kilo-ui/radio-group" +import { Select } from "@kilocode/kilo-ui/select" +import { TextField } from "@kilocode/kilo-ui/text-field" +import { Spinner } from "@kilocode/kilo-ui/spinner" import { useVSCode } from "../../context/vscode" import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" -import type { MarketplaceItem } from "../../types/marketplace" +import type { MarketplaceItem, McpMarketplaceItem, McpInstallationMethod, McpParameter } from "../../types/marketplace" interface ScopeOption { value: "project" | "global" label: string - disabled?: boolean } interface Props { item: MarketplaceItem onClose: () => void - onInstallResult?: (success: boolean) => void + onInstallResult: ( + success: boolean, + scope: "project" | "global", + extra?: { hasParameters?: boolean; installationMethodName?: string }, + ) => void } -const InstallModal: Component = (props) => { +export const InstallModal = (props: Props) => { const vscode = useVSCode() const server = useServer() const { t } = useLanguage() const workspace = () => server.workspaceDirectory() - const scopeOptions = (): ScopeOption[] => [ - { value: "project", label: t("marketplace.install.scope.project"), disabled: !workspace() }, - { value: "global", label: t("marketplace.install.scope.global") }, - ] - const [scope, setScope] = createSignal(workspace() ? scopeOptions()[0] : scopeOptions()[1]) + const options = (): ScopeOption[] => + workspace() + ? [ + { value: "project", label: t("marketplace.scope.project") }, + { value: "global", label: t("marketplace.scope.global") }, + ] + : [{ value: "global", label: t("marketplace.scope.global") }] + const initial = workspace() ? options()[0] : options()[0] + const [scope, setScope] = createSignal(initial) const [installing, setInstalling] = createSignal(false) const [result, setResult] = createSignal<{ success: boolean; error?: string } | null>(null) + const [params, setParams] = createSignal>({}) - const prerequisites = () => props.item.prerequisites ?? [] + // MCP installation methods + const methods = (): McpInstallationMethod[] => { + if (props.item.type !== "mcp") return [] + const mcp = props.item as McpMarketplaceItem + if (!Array.isArray(mcp.content)) return [] + return mcp.content + } - onMount(() => { - const handler = (event: MessageEvent) => { - const msg = event.data - if (msg?.type === "marketplaceInstallResult" && msg.slug === props.item.id) { + const [method, setMethod] = createSignal(methods()[0]) + + const prerequisites = (): string[] => { + if (method()?.prerequisites?.length) return method()!.prerequisites! + return props.item.prerequisites ?? [] + } + + const parameters = (): McpParameter[] => { + if (method()?.parameters?.length) return method()!.parameters! + if (props.item.type === "mcp") return (props.item as McpMarketplaceItem).parameters ?? [] + return [] + } + + const valid = () => { + for (const param of parameters()) { + if (!param.optional && !params()[param.key]?.trim()) return false + } + return true + } + + const setParam = (key: string, value: string) => { + setParams((prev) => ({ ...prev, [key]: value })) + } + + createEffect(() => { + const unsub = vscode.onMessage((msg) => { + if (msg.type === "marketplaceInstallResult" && msg.slug === props.item.id) { setInstalling(false) setResult({ success: msg.success, error: msg.error }) - props.onInstallResult?.(msg.success) + props.onInstallResult(msg.success, scope().value, { + hasParameters: Object.keys(params()).length > 0, + installationMethodName: method()?.name, + }) } - } - window.addEventListener("message", handler) - onCleanup(() => window.removeEventListener("message", handler)) + }) + onCleanup(unsub) }) const handleInstall = () => { setInstalling(true) + const paramValues: Record = { ...params() } + if (method()) { + paramValues.__method = method()!.name + } vscode.postMessage({ type: "installMarketplaceItem", mpItem: props.item, - mpInstallOptions: { target: scope().value }, + mpInstallOptions: { + target: scope().value, + parameters: Object.keys(paramValues).length > 0 ? paramValues : undefined, + }, }) } return ( -
!installing() && props.onClose()}> -
e.stopPropagation()}> -

{t("marketplace.install.title", { name: props.item.name })}

- - -
- + + +
+
+ {t("marketplace.install.scope")} x.value} label={(x: ScopeOption) => x.label} @@ -73,46 +121,86 @@ const InstallModal: Component = (props) => { />
+ 1}> +
+ {t("marketplace.install.method")} + o.value} + label={(o: StatusOption) => o.label} + onSelect={(v: StatusOption | undefined) => v && setStatus(v)} + /> +
+ 0}> +
+ + {(tag) => ( + + )} + +
+
+ + +
+ } + > + 0} fallback={

{props.emptyMessage}

}> +
+ + {(item) => { + const skill = item.type === "skill" ? (item as SkillMarketplaceItem) : undefined + const mcp = item.type === "mcp" ? (item as McpMarketplaceItem) : undefined + return ( + {(tag) => {tag}}} + /> + ) + }} + +
+
+
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx index e821a6e0beb..295b081bd78 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx @@ -1,168 +1,204 @@ -import { Component, createSignal, createMemo, onMount, onCleanup, Show } from "solid-js" +import { createSignal, createMemo, createEffect, onCleanup, onMount, Show } from "solid-js" import { Tabs } from "@kilocode/kilo-ui/tabs" +import { Card } from "@kilocode/kilo-ui/card" +import { Button } from "@kilocode/kilo-ui/button" import { useVSCode } from "../../context/vscode" import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" -import type { MarketplaceItem, SkillMarketplaceItem, MarketplaceInstalledMetadata } from "../../types/marketplace" -import SkillsMarketplace from "./SkillsMarketplace" -import InstallModal from "./InstallModal" -import RemoveDialog from "./RemoveDialog" +import { useDialog } from "@kilocode/kilo-ui/context/dialog" +import type { + MarketplaceItem, + McpMarketplaceItem, + ModeMarketplaceItem, + SkillMarketplaceItem, + MarketplaceInstalledMetadata, +} from "../../types/marketplace" +import { TelemetryEventName } from "../../../../src/services/telemetry/types" +import { MarketplaceListView } from "./MarketplaceListView" +import { InstallModal } from "./InstallModal" +import { RemoveDialog } from "./RemoveDialog" import "./marketplace.css" const EMPTY_METADATA: MarketplaceInstalledMetadata = { project: {}, global: {} } -const MarketplaceView: Component = () => { +export const MarketplaceView = () => { const vscode = useVSCode() const server = useServer() const { t } = useLanguage() + const dialog = useDialog() const [items, setItems] = createSignal([]) const [metadata, setMetadata] = createSignal(EMPTY_METADATA) const [fetching, setFetching] = createSignal(true) const [errors, setErrors] = createSignal([]) - const [tab, setTab] = createSignal("skill") - - // Install/Remove state - const [installItem, setInstallItem] = createSignal(null) - const [removeItem, setRemoveItem] = createSignal<{ item: MarketplaceItem; scope: "project" | "global" } | null>(null) - const [pendingRemove, setPendingRemove] = createSignal(null) - const [removeError, setRemoveError] = createSignal(null) + const [tab, setTab] = createSignal("mcp") + const [pending, setPending] = createSignal<{ item: MarketplaceItem; scope: "project" | "global" } | null>(null) const skills = createMemo(() => items().filter((i): i is SkillMarketplaceItem => i.type === "skill")) + const mcps = createMemo(() => items().filter((i): i is McpMarketplaceItem => i.type === "mcp")) + const modes = createMemo(() => items().filter((i): i is ModeMarketplaceItem => i.type === "mode")) const fetchData = () => { setFetching(true) vscode.postMessage({ type: "fetchMarketplaceData" }) } - onMount(() => { - fetchData() - - const handler = (event: MessageEvent) => { - const msg = event.data - if (msg?.type === "marketplaceData") { + // Listen for messages + createEffect(() => { + const unsub = vscode.onMessage((msg) => { + if (msg.type === "marketplaceData") { setItems(msg.marketplaceItems ?? []) setMetadata(msg.marketplaceInstalledMetadata ?? EMPTY_METADATA) setErrors(msg.errors ?? []) setFetching(false) } - if (msg?.type === "marketplaceRemoveResult") { - if (msg.slug === pendingRemove()) { - setPendingRemove(null) - if (!msg.success) { - setRemoveError(msg.error ?? "Failed to remove item") - } else { - fetchData() + if (msg.type === "marketplaceRemoveResult") { + const removed = pending() + setPending(null) + if (msg.success) { + if (removed) { + telemetry(TelemetryEventName.MARKETPLACE_ITEM_REMOVED, { + itemId: removed.item.id, + itemType: removed.item.type, + itemName: removed.item.name, + target: removed.scope, + }) } + fetchData() + } else { + setErrors((prev) => [...prev, msg.error ?? t("marketplace.remove.failed", { name: msg.slug })]) } } - // Re-fetch when agents or skills change (e.g. after install via CLI) - if (msg?.type === "agentsLoaded" || msg?.type === "skillsLoaded") { - fetchData() - } - // Re-fetch when workspace directory changes - if (msg?.type === "workspaceDirectoryChanged") { - fetchData() - } - } - window.addEventListener("message", handler) - onCleanup(() => window.removeEventListener("message", handler)) + }) + onCleanup(unsub) + }) + + // Re-fetch when workspace changes + createEffect(() => { + server.workspaceDirectory() + fetchData() + }) + + const telemetry = (event: string, properties?: Record) => { + vscode.postMessage({ type: "telemetry", event, properties: properties ?? {} }) + } + + onMount(() => { + telemetry(TelemetryEventName.MARKETPLACE_TAB_VIEWED) }) const handleInstall = (item: MarketplaceItem) => { - setInstallItem(item) + telemetry(TelemetryEventName.MARKETPLACE_INSTALL_BUTTON_CLICKED, { + itemId: item.id, + itemType: item.type, + itemName: item.name, + }) + dialog.show(() => ( + dialog.close()} + onInstallResult={(success, scope, extra) => { + if (success) { + telemetry(TelemetryEventName.MARKETPLACE_ITEM_INSTALLED, { + itemId: item.id, + itemType: item.type, + itemName: item.name, + target: scope, + ...(extra?.hasParameters && { hasParameters: true }), + ...(extra?.installationMethodName && { installationMethodName: extra.installationMethodName }), + }) + dialog.close() + fetchData() + } + }} + /> + )) } const handleRemove = (item: MarketplaceItem, scope: "project" | "global") => { - setRemoveItem({ item, scope }) + dialog.show(() => ( + dialog.close()} + onConfirm={() => { + setPending({ item, scope }) + vscode.postMessage({ + type: "removeInstalledMarketplaceItem", + mpItem: item, + mpInstallOptions: { target: scope }, + }) + dialog.close() + }} + /> + )) } - const confirmRemove = () => { - const r = removeItem() - if (!r) return - setPendingRemove(r.item.id) - setRemoveError(null) - vscode.postMessage({ - type: "removeInstalledMarketplaceItem", - mpItem: r.item, - mpInstallOptions: { target: r.scope }, - }) - setRemoveItem(null) + const dismissError = (idx: number) => { + setErrors((prev) => prev.filter((_, i) => i !== idx)) } return (
- + 0}> + {errors().map((err, idx) => ( + + {err} + + + ))} + + + - {t("marketplace.tab.skills")} - {t("marketplace.tab.mcpServers")} + {t("marketplace.tab.mcp")} {t("marketplace.tab.modes")} + {t("marketplace.tab.skills")} - - - +
+ + + - -
-

{t("marketplace.placeholder")}

-
-
+ + + - -
-

{t("marketplace.placeholder")}

-
-
+ + + +
- - 0}> -
- {errors().map((e) => ( -

{e}

- ))} -
-
- - -
-

{removeError()}

-
-
- - - {(item) => ( - setInstallItem(null)} - onInstallResult={(success) => { - if (success) { - setInstallItem(null) - fetchData() - } - }} - /> - )} - - - - {(r) => ( - setRemoveItem(null)} - onConfirm={confirmRemove} - /> - )} -
) } - -export default MarketplaceView diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx index 4e544fac0cd..fb763cab520 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx @@ -1,4 +1,4 @@ -import { Component } from "solid-js" +import { Dialog } from "@kilocode/kilo-ui/dialog" import { Button } from "@kilocode/kilo-ui/button" import type { MarketplaceItem } from "../../types/marketplace" import { useLanguage } from "../../context/language" @@ -10,25 +10,26 @@ interface Props { onConfirm: () => void } -const RemoveDialog: Component = (props) => { +export const RemoveDialog = (props: Props) => { const { t } = useLanguage() + const typeName = () => { + if (props.item.type === "mcp") return t("marketplace.remove.type.mcp") + if (props.item.type === "mode") return t("marketplace.remove.type.mode") + return t("marketplace.remove.type.skill") + } + return ( -
-
e.stopPropagation()}> -

{t("marketplace.remove.title", { name: props.item.name })}

-

{t("marketplace.remove.confirm", { type: props.item.type, scope: props.scope })}

-
- - -
+ +

{t("marketplace.remove.confirm", { type: typeName(), scope: t(`marketplace.scope.${props.scope}`) })}

+
+ +
-
+ ) } - -export default RemoveDialog diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/SkillsMarketplace.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/SkillsMarketplace.tsx deleted file mode 100644 index e55c4d3419c..00000000000 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/SkillsMarketplace.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Component, createSignal, createMemo, For, Show } from "solid-js" -import { TextField } from "@kilocode/kilo-ui/text-field" -import { RadioGroup } from "@kilocode/kilo-ui/radio-group" -import { Tag } from "@kilocode/kilo-ui/tag" -import { Spinner } from "@kilocode/kilo-ui/spinner" -import type { SkillMarketplaceItem, MarketplaceInstalledMetadata, MarketplaceItem } from "../../types/marketplace" -import { useLanguage } from "../../context/language" -import ItemCard from "./ItemCard" - -interface Props { - items: SkillMarketplaceItem[] - metadata: MarketplaceInstalledMetadata - fetching: boolean - onInstall: (item: MarketplaceItem) => void - onRemove: (item: MarketplaceItem, scope: "project" | "global") => void -} - -const ALL = "__all__" - -const SkillsMarketplace: Component = (props) => { - const { t } = useLanguage() - const [search, setSearch] = createSignal("") - const [category, setCategory] = createSignal(ALL) - - const categories = createMemo(() => { - const set = new Set() - for (const item of props.items) { - if (item.displayCategory) set.add(item.displayCategory) - } - return [ALL, ...Array.from(set).sort()] - }) - - const filtered = createMemo(() => { - const q = search().toLowerCase() - const cat = category() - return props.items.filter((item) => { - if (cat !== ALL && item.displayCategory !== cat) return false - if (!q) return true - return ( - item.id.toLowerCase().includes(q) || - item.name.toLowerCase().includes(q) || - item.displayName.toLowerCase().includes(q) || - item.description.toLowerCase().includes(q) || - item.displayCategory.toLowerCase().includes(q) - ) - }) - }) - - return ( -
-
- -
-
- c} - label={(c: string) => (c === ALL ? t("marketplace.category.all") : c)} - onSelect={(v: string | undefined) => v && setCategory(v)} - /> -
- }> - 0} fallback={

{t("marketplace.empty.skills")}

}> -
- - {(item) => ( - {item.displayCategory}} - /> - )} - -
-
-
-
- ) -} - -export default SkillsMarketplace diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts index 979b85151d1..e322a99a16f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/index.ts @@ -1 +1 @@ -export { default as MarketplaceView } from "./MarketplaceView" +export { MarketplaceView } from "./MarketplaceView" diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css index 49c95541ce4..77a7c0d306f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css @@ -6,6 +6,19 @@ height: 100%; padding: 12px; overflow: auto; + gap: 8px; +} + +.marketplace-tabs-root { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; +} + +.marketplace-content { + flex: 1; + overflow: auto; } /* List layout */ @@ -23,22 +36,59 @@ align-items: center; } +.marketplace-search-field { + flex: 1; +} + .marketplace-category-filters { overflow-x: auto; } +/* Tag filters */ + +.marketplace-active-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.marketplace-tag-filter { + all: unset; + cursor: pointer; + opacity: 0.5; + transition: opacity 0.15s ease; +} + +.marketplace-tag-filter:hover { + opacity: 0.8; +} + +.marketplace-tag-filter.active { + opacity: 1; +} + +/* Loading & empty */ + +.marketplace-loading { + display: flex; + justify-content: center; + align-items: center; + min-height: 200px; +} + +.marketplace-empty { + text-align: center; + color: var(--text-weak); + padding: 24px; +} + /* Grid */ .marketplace-grid { display: grid; - grid-template-columns: 1fr; + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 12px; -} - -@media (min-width: 500px) { - .marketplace-grid { - grid-template-columns: repeat(2, 1fr); - } + max-width: 1200px; } /* Card */ @@ -48,9 +98,6 @@ flex-direction: column; gap: 8px; padding: 12px; - border: 1px solid var(--border-base); - border-radius: 6px; - background: var(--background-base); } .marketplace-card-header { @@ -71,11 +118,11 @@ color: var(--text-base); } -.marketplace-card-link { - text-decoration: none; +.marketplace-card-name.clickable { + cursor: pointer; } -.marketplace-card-link:hover { +.marketplace-card-name.clickable:hover { text-decoration: underline; } @@ -94,6 +141,22 @@ margin: 0; } +.marketplace-card-description.expanded { + display: block; + -webkit-line-clamp: unset; +} + +.marketplace-card-expand { + all: unset; + cursor: pointer; + font-size: 12px; + color: var(--text-interactive-base); +} + +.marketplace-card-expand:hover { + text-decoration: underline; +} + .marketplace-card-footer { display: flex; align-items: center; @@ -115,117 +178,109 @@ flex-shrink: 0; } -.marketplace-installed-tag { +/* Installed badge */ + +.marketplace-badge-installed { color: var(--text-on-success-base) !important; border-color: var(--border-success-base) !important; } +/* Remove button */ + .marketplace-remove-btn { color: var(--text-on-critical-base) !important; border-color: var(--border-critical-base) !important; } -/* Placeholder */ +/* Remove dialog actions */ -.marketplace-placeholder { - display: flex; - justify-content: center; - align-items: center; - min-height: 200px; - color: var(--text-weak); - font-size: 16px; -} - -/* Empty state */ - -.marketplace-empty { - text-align: center; - color: var(--text-weak); - padding: 24px; -} - -/* Modal / Dialog */ - -.marketplace-modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -} - -.marketplace-modal { - background: var(--background-base); - border: 1px solid var(--border-base); - border-radius: 8px; - padding: 20px; - max-width: 480px; - width: 90%; - max-height: 80vh; - overflow-y: auto; -} - -.marketplace-modal h3 { - margin: 0 0 16px; - font-size: 16px; - color: var(--text-base); -} - -.marketplace-modal p { - margin: 0 0 12px; - color: var(--text-weak); - font-size: 13px; -} - -.marketplace-modal-section { - margin-bottom: 16px; -} - -.marketplace-modal-section > label { - display: block; - font-weight: 600; - font-size: 13px; - margin-bottom: 6px; - color: var(--text-base); -} - -.marketplace-modal-actions { +.marketplace-remove-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; } -.marketplace-modal-result { - text-align: center; - padding: 12px 0; +/* Danger button (remove confirm) */ + +.danger-btn { + color: var(--text-on-critical-base) !important; + border-color: var(--border-critical-base) !important; } -.marketplace-prerequisites { +/* Install modal */ + +.install-modal-body { + display: flex; + flex-direction: column; + gap: 16px; +} + +.install-modal-section { + display: flex; + flex-direction: column; + gap: 6px; +} + +.install-modal-label { + font-weight: 600; + font-size: 13px; + color: var(--text-base); +} + +.install-modal-prerequisites { margin: 0; padding-left: 20px; font-size: 13px; color: var(--text-weak); } -/* Error/Success messages */ - -.marketplace-errors { - margin-top: 12px; +.install-modal-param { + margin-bottom: 8px; } -.marketplace-error { - color: var(--text-on-critical-base); - font-size: 13px; +.install-modal-footer { + display: flex; + justify-content: flex-end; + gap: 8px; } -.marketplace-success { +.install-modal-result { + text-align: center; + padding: 12px 0; +} + +.install-modal-success { color: var(--text-on-success-base); font-size: 14px; font-weight: 600; + margin: 0 0 16px; +} + +.install-modal-error-msg { + color: var(--text-on-critical-base); + font-size: 13px; + margin: 0 0 16px; +} + +/* Error banner */ + +.marketplace-error-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 8px 12px; + font-size: 13px; +} + +/* Link style */ + +.link { + color: var(--text-interactive-base); + cursor: pointer; +} + +.link:hover { + text-decoration: underline; } diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts index 264b722a54c..9504e734f37 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts @@ -1,11 +1,19 @@ import type { MarketplaceInstalledMetadata } from "../../types/marketplace" +export function isInstalled( + id: string, + type: string, + metadata: MarketplaceInstalledMetadata, +): "project" | "global" | false { + return installedScopes(id, type, metadata)[0] ?? false +} + export function installedScopes( id: string, type: string, metadata: MarketplaceInstalledMetadata, -): Array<"project" | "global"> { - const scopes: Array<"project" | "global"> = [] +): ("project" | "global")[] { + const scopes: ("project" | "global")[] = [] if (metadata.project[id]?.type === type) scopes.push("project") if (metadata.global[id]?.type === type) scopes.push("global") return scopes diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx index ada39f81c6e..8d555b0cff9 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx @@ -239,7 +239,12 @@ const AgentBehaviourTab: Component = () => { current={defaultAgentOptions().find((o) => o.value === (config().default_agent ?? ""))} value={(o) => o.value} label={(o) => o.label} - onSelect={(o) => o && updateConfig({ default_agent: o.value || undefined })} + onSelect={(o) => { + if (!o) return + const next = o.value || undefined + if (next === (config().default_agent ?? undefined)) return + updateConfig({ default_agent: next }) + }} variant="secondary" size="small" triggerVariant="settings" diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx index 9ba75d67b3c..812209e7c91 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx @@ -46,7 +46,12 @@ const DisplayTab: Component = () => { current={LAYOUT_OPTIONS.find((o) => o.value === (config().layout ?? "auto"))} value={(o) => o.value} label={(o) => language.t(o.labelKey)} - onSelect={(o) => o && updateConfig({ layout: o.value as "auto" | "stretch" })} + onSelect={(o) => { + if (!o) return + const next = o.value as "auto" | "stretch" + if (next === (config().layout ?? "auto")) return + updateConfig({ layout: next }) + }} variant="secondary" size="small" triggerVariant="settings" diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx index 94d0ec10b18..4f060d2e0df 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx @@ -43,7 +43,12 @@ const ExperimentalTab: Component = () => { current={SHARE_OPTIONS.find((o) => o.value === (config().share ?? "manual"))} value={(o) => o.value} label={(o) => language.t(o.labelKey)} - onSelect={(o) => o && updateConfig({ share: o.value as "manual" | "auto" | "disabled" })} + onSelect={(o) => { + if (!o) return + const next = o.value as "manual" | "auto" | "disabled" + if (next === (config().share ?? "manual")) return + updateConfig({ share: next }) + }} variant="secondary" size="small" triggerVariant="settings" diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 091272f4b50..700d31cc41b 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -135,7 +135,14 @@ interface SessionContextValue { currentVariant: () => string | undefined selectVariant: (value: string) => void + // Revert/undo state for the current session + revert: Accessor + revertedCount: Accessor + summary: Accessor + // Actions + revertSession: (messageID: string) => void + unrevertSession: () => void sendMessage: (text: string, providerID?: string, modelID?: string, files?: FileAttachment[]) => void abort: () => void compact: () => void @@ -493,6 +500,10 @@ export const SessionProvider: ParentComponent = (props) => { handleSessionDeleted(message.sessionID) break + case "messageRemoved": + handleMessageRemoved(message.sessionID, message.messageID) + break + case "error": // Only clear loading if the error is for the current session // (or has no sessionID for backwards compatibility) @@ -903,6 +914,18 @@ export const SessionProvider: ParentComponent = (props) => { }) } + // Matches desktop app's event-reducer.ts: message.removed handler. + // Splices the message from the store and deletes its parts. + function handleMessageRemoved(sessionID: string, messageID: string) { + setStore("messages", sessionID, (msgs = []) => msgs.filter((m) => m.id !== messageID)) + setStore( + "parts", + produce((parts) => { + delete parts[messageID] + }), + ) + } + function handleCloudSessionDataLoaded(cloudSessionId: string, title: string, messages: Message[]) { if (cloudPreviewId() !== cloudSessionId) return const key = `cloud:${cloudSessionId}` @@ -1262,6 +1285,47 @@ export const SessionProvider: ParentComponent = (props) => { const userMessages = createMemo(() => messages().filter((m) => m.role === "user")) + const revert = createMemo(() => { + const id = currentSessionID() + // revert can be null (cleared by unrevert) or undefined (never set) — treat both as "no revert" + return id ? (store.sessions[id]?.revert ?? undefined) : undefined + }) + + const revertedCount = createMemo(() => { + const boundary = revert()?.messageID + if (!boundary) return 0 + return userMessages().filter((m) => m.id >= boundary).length + }) + + const summary = createMemo(() => { + const id = currentSessionID() + return id ? (store.sessions[id]?.summary ?? undefined) : undefined + }) + + function revertSession(messageID: string) { + const id = currentSessionID() + if (!id) return + // Restore the reverted user message's prompt text into the input. + // Dispatch as a window message so PromptInput picks it up via onMessage. + const parts = store.parts[messageID] + if (parts) { + const text = parts + .filter((p) => p.type === "text" && !(p as { synthetic?: boolean }).synthetic) + .map((p) => (p as { text: string }).text ?? "") + .join("") + if (text) window.postMessage({ type: "setChatBoxMessage", text }, "*") + } + vscode.postMessage({ type: "revertSession", sessionID: id, messageID }) + } + + function unrevertSession() { + const id = currentSessionID() + if (!id) return + // Clear the prompt input on full redo (matching TUI/desktop behavior) + window.postMessage({ type: "setChatBoxMessage", text: "" }, "*") + vscode.postMessage({ type: "unrevertSession", sessionID: id }) + } + function syncSession(sessionID: string) { vscode.postMessage({ type: "syncSession", sessionID }) } @@ -1360,6 +1424,11 @@ export const SessionProvider: ParentComponent = (props) => { variantList, currentVariant, selectVariant, + revert, + revertedCount, + summary, + revertSession, + unrevertSession, sendMessage, abort, compact, diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index c4fa3f25acb..b064704bcac 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "فتح الإعدادات", "command.session.previous": "الجلسة السابقة", "command.session.next": "الجلسة التالية", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "الجلسة غير المقروءة السابقة", + "command.session.next.unseen": "الجلسة غير المقروءة التالية", "command.session.archive": "أرشفة الجلسة", "command.palette": "لوحة الأوامر", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "تراجع عن الرسالة الأخيرة", "command.session.redo": "إعادة", "command.session.redo.description": "إعادة الرسالة التي تم التراجع عنها", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "ضغط الجلسة", "command.session.compact.description": "تلخيص الجلسة لتقليل حجم السياق", "command.session.fork": "تشعب من الرسالة", @@ -229,6 +237,7 @@ export const dict = { "prompt.action.attachFile": "إرفاق ملف", "prompt.attachment.remove": "إزالة المرفق", "prompt.action.send": "إرسال", + "prompt.action.send.blocked": "أجب عن السؤال المعلق أو تجاهله أولاً", "prompt.action.stop": "توقف", "prompt.action.enhance": "تحسين النص", "prompt.action.resetModel": "إعادة تعيين النموذج إلى الافتراضي", @@ -680,6 +689,9 @@ export const dict = { "settings.providers.tag.config": "التكوين", "settings.providers.tag.custom": "مخصص", "settings.providers.tag.other": "أخرى", + "settings.providers.modeModels": "نموذج لكل وضع", + "settings.providers.modeModels.description": + "تجاوز النموذج الافتراضي لأوضاع محددة. إذا لم يتم التعيين، يتم استخدام النموذج الافتراضي العام.", "settings.models.title": "النماذج", "settings.models.description": "ستكون إعدادات النموذج قابلة للتكوين هنا.", "settings.agents.title": "الوكلاء", @@ -827,12 +839,14 @@ export const dict = { "Kilo Code هو مساعد ترميز بالذكاء الاصطناعي. اطلب منه بناء ميزات أو إصلاح أخطاء أو شرح قاعدة الشيفرة.", "session.messages.scrollToBottom": "التمرير إلى الأسفل", "session.status.writingResponse": "...جارٍ كتابة الرد", + "session.status.retry": "جارٍ إعادة المحاولة…", "session.status.retrying": "...إعادة المحاولة (المحاولة {{ attempt }})… {{ message }}", "session.status.working": "...جارٍ العمل", "dialog.model.noProviders": "لا يوجد موفرون", "prompt.placeholder.connecting": "جارٍ الاتصال بالخادم...", + "prompt.placeholder.error": "فشل الاتصال. تحقق من لوحة الإخراج أو أعد تشغيل الإضافة.", "prompt.placeholder.default": "اكتب رسالة... (Enter للإرسال، Shift+Enter لسطر جديد)", "context.usage.sessionCost": "تكلفة الجلسة", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index e8a16504af0..fcb2bd1b2d1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "Abrir configurações", "command.session.previous": "Sessão anterior", "command.session.next": "Próxima sessão", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Sessão não lida anterior", + "command.session.next.unseen": "Próxima sessão não lida", "command.session.archive": "Arquivar sessão", "command.palette": "Paleta de comandos", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "Desfazer a última mensagem", "command.session.redo": "Refazer", "command.session.redo.description": "Refazer a última mensagem desfeita", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Compactar sessão", "command.session.compact.description": "Resumir a sessão para reduzir o tamanho do contexto", "command.session.fork": "Bifurcar da mensagem", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "Anexar arquivo", "prompt.attachment.remove": "Remover anexo", "prompt.action.send": "Enviar", + "prompt.action.send.blocked": "Responda ou feche a pergunta pendente primeiro", "prompt.action.stop": "Parar", "prompt.action.enhance": "Melhorar prompt", "prompt.action.resetModel": "Redefinir modelo para o padrão", @@ -687,6 +696,9 @@ export const dict = { "settings.providers.tag.config": "Configuração", "settings.providers.tag.custom": "Personalizado", "settings.providers.tag.other": "Outro", + "settings.providers.modeModels": "Modelo por Modo", + "settings.providers.modeModels.description": + "Substitua o modelo padrão para modos específicos. Se não definido, o modelo padrão global é usado.", "settings.models.title": "Modelos", "settings.models.description": "Configurações de modelos estarão disponíveis aqui.", "settings.agents.title": "Agentes", @@ -835,12 +847,14 @@ export const dict = { "Kilo Code é um assistente de programação com IA. Peça para criar funcionalidades, corrigir bugs ou explicar sua base de código.", "session.messages.scrollToBottom": "Rolar para o final", "session.status.writingResponse": "Escrevendo resposta…", + "session.status.retry": "Tentando novamente…", "session.status.retrying": "Tentando novamente (tentativa {{ attempt }})… {{ message }}", "session.status.working": "Trabalhando…", "dialog.model.noProviders": "Nenhum provedor", "prompt.placeholder.connecting": "Conectando ao servidor...", + "prompt.placeholder.error": "Conexão falhou. Verifique o painel de saída ou reinicie a extensão.", "prompt.placeholder.default": "Digite uma mensagem... (Enter para enviar, Shift+Enter para nova linha)", "context.usage.sessionCost": "Custo da sessão", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 5097fd6a307..b2ff6c5f3b8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "Poništi posljednju poruku", "command.session.redo": "Vrati", "command.session.redo.description": "Vrati posljednju poništenu poruku", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Sažmi sesiju", "command.session.compact.description": "Sažmi sesiju kako bi se smanjio kontekst", "command.session.fork": "Fork iz poruke", @@ -231,6 +239,7 @@ export const dict = { "prompt.action.attachFile": "Priloži datoteku", "prompt.attachment.remove": "Ukloni prilog", "prompt.action.send": "Pošalji", + "prompt.action.send.blocked": "Prvo odgovorite ili odbacite pitanje na čekanju", "prompt.action.stop": "Zaustavi", "prompt.action.enhance": "Poboljšaj prompt", "prompt.action.resetModel": "Resetuj model na zadani", @@ -691,6 +700,9 @@ export const dict = { "settings.providers.tag.config": "Konfiguracija", "settings.providers.tag.custom": "Prilagođeno", "settings.providers.tag.other": "Ostalo", + "settings.providers.modeModels": "Model po režimu", + "settings.providers.modeModels.description": + "Zamijenite podrazumijevani model za određene režime. Ako nije postavljeno, koristi se globalni podrazumijevani model.", "settings.models.title": "Modeli", "settings.models.description": "Postavke modela će se ovdje moći podešavati.", "settings.agents.title": "Agenti", @@ -838,12 +850,14 @@ export const dict = { "Kilo Code je AI asistent za programiranje. Zatražite da gradi funkcionalnosti, ispravlja greške ili objasni vašu bazu koda.", "session.messages.scrollToBottom": "Pomakni se na dno", "session.status.writingResponse": "Pisanje odgovora…", + "session.status.retry": "Ponovni pokušaj…", "session.status.retrying": "Ponovni pokušaj (pokušaj {{ attempt }})… {{ message }}", "session.status.working": "Radim…", "dialog.model.noProviders": "Nema pružatelja", "prompt.placeholder.connecting": "Povezivanje na server...", + "prompt.placeholder.error": "Povezivanje nije uspjelo. Provjerite panel za izlaz ili ponovo pokrenite ekstenziju.", "prompt.placeholder.default": "Unesite poruku... (Enter za slanje, Shift+Enter za novi red)", "context.usage.sessionCost": "Cijena sesije", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 1e979d37779..7ebdbb2fac0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "Åbn indstillinger", "command.session.previous": "Forrige session", "command.session.next": "Næste session", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Forrige ulæste session", + "command.session.next.unseen": "Næste ulæste session", "command.session.archive": "Arkivér session", "command.palette": "Kommandopalette", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "Fortryd den sidste besked", "command.session.redo": "Omgør", "command.session.redo.description": "Omgør den sidste fortrudte besked", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Komprimér session", "command.session.compact.description": "Opsummer sessionen for at reducere kontekststørrelsen", "command.session.fork": "Forgren fra besked", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "Vedhæft fil", "prompt.attachment.remove": "Fjern vedhæftning", "prompt.action.send": "Send", + "prompt.action.send.blocked": "Besvar eller afvis det afventende spørgsmål først", "prompt.action.stop": "Stop", "prompt.action.enhance": "Forbedr prompt", "prompt.action.resetModel": "Nulstil model til standard", @@ -686,6 +695,9 @@ export const dict = { "settings.providers.tag.config": "Konfiguration", "settings.providers.tag.custom": "Brugerdefineret", "settings.providers.tag.other": "Andet", + "settings.providers.modeModels": "Model pr. tilstand", + "settings.providers.modeModels.description": + "Tilsidesæt standardmodellen for bestemte tilstande. Hvis ikke angivet, bruges den globale standardmodel.", "settings.models.title": "Modeller", "settings.models.description": "Modelindstillinger vil kunne konfigureres her.", "settings.agents.title": "Agenter", @@ -833,12 +845,14 @@ export const dict = { "Kilo Code er en AI-kodningsassistent. Bed den om at bygge funktioner, rette fejl eller forklare din kodebase.", "session.messages.scrollToBottom": "Rul til bunden", "session.status.writingResponse": "Skriver svar…", + "session.status.retry": "Prøver igen…", "session.status.retrying": "Prøver igen (forsøg {{ attempt }})… {{ message }}", "session.status.working": "Arbejder…", "dialog.model.noProviders": "Ingen udbydere", "prompt.placeholder.connecting": "Opretter forbindelse til server...", + "prompt.placeholder.error": "Forbindelse mislykkedes. Tjek outputpanelet eller genstart udvidelsen.", "prompt.placeholder.default": "Skriv en besked... (Enter for at sende, Shift+Enter for ny linje)", "context.usage.sessionCost": "Sessionsomkostning", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 99f0f585e0a..29469221cd8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -32,8 +32,8 @@ export const dict = { "command.settings.open": "Einstellungen öffnen", "command.session.previous": "Vorherige Sitzung", "command.session.next": "Nächste Sitzung", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Vorherige ungelesene Sitzung", + "command.session.next.unseen": "Nächste ungelesene Sitzung", "command.session.archive": "Sitzung archivieren", "command.palette": "Befehlspalette", @@ -82,6 +82,14 @@ export const dict = { "command.session.undo.description": "Letzte Nachricht rückgängig machen", "command.session.redo": "Wiederherstellen", "command.session.redo.description": "Letzte rückgängig gemachte Nachricht wiederherstellen", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Sitzung komprimieren", "command.session.compact.description": "Sitzung zusammenfassen, um die Kontextgröße zu reduzieren", "command.session.fork": "Von Nachricht abzweigen", @@ -234,6 +242,7 @@ export const dict = { "prompt.action.attachFile": "Datei anhängen", "prompt.attachment.remove": "Anhang entfernen", "prompt.action.send": "Senden", + "prompt.action.send.blocked": "Beantworten oder verwerfen Sie zuerst die ausstehende Frage", "prompt.action.stop": "Stopp", "prompt.action.enhance": "Prompt verbessern", "prompt.action.resetModel": "Modell auf Standard zurücksetzen", @@ -696,6 +705,9 @@ export const dict = { "settings.providers.tag.config": "Konfiguration", "settings.providers.tag.custom": "Benutzerdefiniert", "settings.providers.tag.other": "Andere", + "settings.providers.modeModels": "Modell pro Modus", + "settings.providers.modeModels.description": + "Überschreiben Sie das Standardmodell für bestimmte Modi. Wenn nicht festgelegt, wird das globale Standardmodell verwendet.", "settings.models.title": "Modelle", "settings.models.description": "Modelleinstellungen können hier konfiguriert werden.", "settings.agents.title": "Agenten", @@ -843,12 +855,15 @@ export const dict = { "Kilo Code ist ein KI-Programmierassistent. Bitten Sie ihn, Funktionen zu erstellen, Fehler zu beheben oder Ihre Codebasis zu erklären.", "session.messages.scrollToBottom": "Nach unten scrollen", "session.status.writingResponse": "Antwort wird geschrieben…", + "session.status.retry": "Erneuter Versuch…", "session.status.retrying": "Erneuter Versuch ({{ attempt }})… {{ message }}", "session.status.working": "Wird bearbeitet…", "dialog.model.noProviders": "Keine Anbieter", "prompt.placeholder.connecting": "Verbindung zum Server wird hergestellt...", + "prompt.placeholder.error": + "Verbindung fehlgeschlagen. Überprüfen Sie das Ausgabepanel oder starten Sie die Erweiterung neu.", "prompt.placeholder.default": "Nachricht eingeben... (Enter zum Senden, Shift+Enter für neue Zeile)", "context.usage.sessionCost": "Sitzungskosten", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 23eef2801c6..ae2b5a4340f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "Undo the last message", "command.session.redo": "Redo", "command.session.redo.description": "Redo the last undone message", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Compact session", "command.session.compact.description": "Summarize the session to reduce context size", "command.session.fork": "Fork from message", @@ -231,6 +239,7 @@ export const dict = { "prompt.action.attachFile": "Attach file", "prompt.attachment.remove": "Remove attachment", "prompt.action.send": "Send", + "prompt.action.send.blocked": "Answer or dismiss the pending question first", "prompt.action.stop": "Stop", "prompt.action.enhance": "Enhance prompt", "prompt.action.resetModel": "Reset model to default", @@ -838,6 +847,7 @@ export const dict = { "Kilo Code is an AI coding assistant. Ask it to build features, fix bugs, or explain your codebase.", "session.messages.scrollToBottom": "Scroll to bottom", "session.status.writingResponse": "Writing response...", + "session.status.retry": "Retrying…", "session.status.retrying": "Retrying (attempt {{ attempt }})… {{ message }}", "session.status.working": "Working...", @@ -845,7 +855,7 @@ export const dict = { "prompt.placeholder.connecting": "Connecting to server...", "prompt.placeholder.default": "Type a message... (Enter to send, Shift+Enter for new line)", - "prompt.placeholder.error": "", + "prompt.placeholder.error": "Connection failed. Check the output panel or restart the extension.", "context.usage.sessionCost": "Session cost", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 47d663824f3..4a67c75c9d6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "Abrir ajustes", "command.session.previous": "Sesión anterior", "command.session.next": "Siguiente sesión", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Sesión no leída anterior", + "command.session.next.unseen": "Siguiente sesión no leída", "command.session.archive": "Archivar sesión", "command.palette": "Paleta de comandos", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "Deshacer el último mensaje", "command.session.redo": "Rehacer", "command.session.redo.description": "Rehacer el último mensaje deshecho", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Compactar sesión", "command.session.compact.description": "Resumir la sesión para reducir el tamaño del contexto", "command.session.fork": "Bifurcar desde mensaje", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "Adjuntar archivo", "prompt.attachment.remove": "Eliminar adjunto", "prompt.action.send": "Enviar", + "prompt.action.send.blocked": "Responda o descarte la pregunta pendiente primero", "prompt.action.stop": "Detener", "prompt.action.enhance": "Mejorar prompt", "prompt.action.resetModel": "Restablecer modelo al predeterminado", @@ -692,6 +701,9 @@ export const dict = { "settings.providers.tag.config": "Configuración", "settings.providers.tag.custom": "Personalizado", "settings.providers.tag.other": "Otro", + "settings.providers.modeModels": "Modelo por modo", + "settings.providers.modeModels.description": + "Anula el modelo predeterminado para modos específicos. Si no se establece, se usa el modelo predeterminado global.", "settings.models.title": "Modelos", "settings.models.description": "La configuración de modelos estará disponible aquí.", "settings.agents.title": "Agentes", @@ -840,12 +852,14 @@ export const dict = { "Kilo Code es un asistente de programación con IA. Pídele que cree funcionalidades, corrija errores o explique tu código.", "session.messages.scrollToBottom": "Desplazar al final", "session.status.writingResponse": "Escribiendo respuesta…", + "session.status.retry": "Reintentando…", "session.status.retrying": "Reintentando (intento {{ attempt }})… {{ message }}", "session.status.working": "Trabajando…", "dialog.model.noProviders": "Sin proveedores", "prompt.placeholder.connecting": "Conectando al servidor...", + "prompt.placeholder.error": "Conexión fallida. Revisa el panel de salida o reinicia la extensión.", "prompt.placeholder.default": "Escribe un mensaje... (Enter para enviar, Shift+Enter para nueva línea)", "context.usage.sessionCost": "Coste de la sesión", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index da0135db55d..b343cb3876a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "Ouvrir les paramètres", "command.session.previous": "Session précédente", "command.session.next": "Session suivante", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Session non lue précédente", + "command.session.next.unseen": "Session non lue suivante", "command.session.archive": "Archiver la session", "command.palette": "Palette de commandes", @@ -79,6 +79,14 @@ export const dict = { "command.session.undo.description": "Annuler le dernier message", "command.session.redo": "Rétablir", "command.session.redo.description": "Rétablir le dernier message annulé", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Compacter la session", "command.session.compact.description": "Résumer la session pour réduire la taille du contexte", "command.session.fork": "Bifurquer à partir du message", @@ -231,6 +239,7 @@ export const dict = { "prompt.action.attachFile": "Joindre un fichier", "prompt.attachment.remove": "Supprimer la pièce jointe", "prompt.action.send": "Envoyer", + "prompt.action.send.blocked": "Répondez ou rejetez d'abord la question en attente", "prompt.action.stop": "Arrêter", "prompt.action.enhance": "Améliorer le prompt", "prompt.action.resetModel": "Réinitialiser le modèle par défaut", @@ -696,6 +705,9 @@ export const dict = { "settings.providers.tag.config": "Configuration", "settings.providers.tag.custom": "Personnalisé", "settings.providers.tag.other": "Autre", + "settings.providers.modeModels": "Modèle par mode", + "settings.providers.modeModels.description": + "Remplacez le modèle par défaut pour des modes spécifiques. Si non défini, le modèle par défaut global est utilisé.", "settings.models.title": "Modèles", "settings.models.description": "Les paramètres des modèles seront configurables ici.", "settings.agents.title": "Agents", @@ -845,12 +857,14 @@ export const dict = { "Kilo Code est un assistant de programmation IA. Demandez-lui de créer des fonctionnalités, corriger des bugs ou expliquer votre code.", "session.messages.scrollToBottom": "Défiler vers le bas", "session.status.writingResponse": "Rédaction de la réponse…", + "session.status.retry": "Nouvelle tentative…", "session.status.retrying": "Nouvelle tentative (essai {{ attempt }})… {{ message }}", "session.status.working": "En cours…", "dialog.model.noProviders": "Aucun fournisseur", "prompt.placeholder.connecting": "Connexion au serveur...", + "prompt.placeholder.error": "Échec de la connexion. Vérifiez le panneau de sortie ou redémarrez l'extension.", "prompt.placeholder.default": "Tapez un message... (Entrée pour envoyer, Maj+Entrée pour un saut de ligne)", "context.usage.sessionCost": "Coût de la session", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 7c7e68c0ced..7fdb68e8de9 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "設定を開く", "command.session.previous": "前のセッション", "command.session.next": "次のセッション", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "前の未読セッション", + "command.session.next.unseen": "次の未読セッション", "command.session.archive": "セッションをアーカイブ", "command.palette": "コマンドパレット", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "最後のメッセージを元に戻す", "command.session.redo": "やり直す", "command.session.redo.description": "元に戻したメッセージをやり直す", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "セッションを圧縮", "command.session.compact.description": "セッションを要約してコンテキストサイズを削減", "command.session.fork": "メッセージからフォーク", @@ -229,6 +237,7 @@ export const dict = { "prompt.action.attachFile": "ファイルを添付", "prompt.attachment.remove": "添付ファイルを削除", "prompt.action.send": "送信", + "prompt.action.send.blocked": "最初に保留中の質問に答えるか、閉じてください", "prompt.action.stop": "停止", "prompt.action.enhance": "プロンプトを改善", "prompt.action.resetModel": "モデルをデフォルトにリセット", @@ -685,6 +694,9 @@ export const dict = { "settings.providers.tag.config": "設定", "settings.providers.tag.custom": "カスタム", "settings.providers.tag.other": "その他", + "settings.providers.modeModels": "モードごとのモデル", + "settings.providers.modeModels.description": + "特定のモードのデフォルトモデルを上書きします。設定されていない場合、グローバルデフォルトモデルが使用されます。", "settings.models.title": "モデル", "settings.models.description": "モデル設定はここで構成できます。", "settings.agents.title": "エージェント", @@ -832,12 +844,14 @@ export const dict = { "Kilo CodeはAIコーディングアシスタントです。機能の構築、バグの修正、コードベースの説明を依頼できます。", "session.messages.scrollToBottom": "下にスクロール", "session.status.writingResponse": "応答を作成中…", + "session.status.retry": "再試行中…", "session.status.retrying": "再試行中({{ attempt }}回目)… {{ message }}", "session.status.working": "作業中…", "dialog.model.noProviders": "プロバイダーなし", "prompt.placeholder.connecting": "サーバーに接続中...", + "prompt.placeholder.error": "接続に失敗しました。出力パネルを確認するか、拡張機能を再起動してください。", "prompt.placeholder.default": "メッセージを入力... (Enterで送信、Shift+Enterで改行)", "context.usage.sessionCost": "セッションコスト", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index db088d27173..1389d5121ae 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -32,8 +32,8 @@ export const dict = { "command.settings.open": "설정 열기", "command.session.previous": "이전 세션", "command.session.next": "다음 세션", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "이전 읽지 않은 세션", + "command.session.next.unseen": "다음 읽지 않은 세션", "command.session.archive": "세션 보관", "command.palette": "명령 팔레트", @@ -82,6 +82,14 @@ export const dict = { "command.session.undo.description": "마지막 메시지 실행 취소", "command.session.redo": "다시 실행", "command.session.redo.description": "마지막 실행 취소된 메시지 다시 실행", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "세션 압축", "command.session.compact.description": "컨텍스트 크기를 줄이기 위해 세션 요약", "command.session.fork": "메시지에서 분기", @@ -233,6 +241,7 @@ export const dict = { "prompt.action.attachFile": "파일 첨부", "prompt.attachment.remove": "첨부 파일 제거", "prompt.action.send": "전송", + "prompt.action.send.blocked": "먼저 대기 중인 질문에 답하거나 닫아주세요", "prompt.action.stop": "중지", "prompt.action.enhance": "프롬프트 개선", "prompt.action.resetModel": "모델을 기본값으로 재설정", @@ -686,6 +695,9 @@ export const dict = { "settings.providers.tag.config": "구성", "settings.providers.tag.custom": "사용자 지정", "settings.providers.tag.other": "기타", + "settings.providers.modeModels": "모드별 모델", + "settings.providers.modeModels.description": + "특정 모드의 기본 모델을 재정의합니다. 설정하지 않으면 전역 기본 모델이 사용됩니다.", "settings.models.title": "모델", "settings.models.description": "모델 설정은 여기서 구성할 수 있습니다.", "settings.agents.title": "에이전트", @@ -832,12 +844,14 @@ export const dict = { "Kilo Code는 AI 코딩 어시스턴트입니다. 기능 구축, 버그 수정 또는 코드베이스 설명을 요청하세요.", "session.messages.scrollToBottom": "하단으로 스크롤", "session.status.writingResponse": "응답 작성 중...", + "session.status.retry": "재시도 중…", "session.status.retrying": "재시도 중 ({{ attempt }}번째 시도)… {{ message }}", "session.status.working": "작업 중...", "dialog.model.noProviders": "공급자 없음", "prompt.placeholder.connecting": "서버에 연결 중...", + "prompt.placeholder.error": "연결에 실패했습니다. 출력 패널을 확인하거나 확장 프로그램을 다시 시작하세요.", "prompt.placeholder.default": "메시지를 입력하세요... (Enter로 전송, Shift+Enter로 줄 바꿈)", "context.usage.sessionCost": "세션 비용", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 5a5205d4532..073781cdac7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -31,8 +31,8 @@ export const dict = { "command.settings.open": "Åpne innstillinger", "command.session.previous": "Forrige sesjon", "command.session.next": "Neste sesjon", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Forrige uleste økt", + "command.session.next.unseen": "Neste uleste økt", "command.session.archive": "Arkiver sesjon", "command.palette": "Kommandopalett", @@ -81,6 +81,14 @@ export const dict = { "command.session.undo.description": "Angre siste melding", "command.session.redo": "Gjør om", "command.session.redo.description": "Gjør om siste angrede melding", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Komprimer sesjon", "command.session.compact.description": "Oppsummer sesjonen for å redusere kontekststørrelsen", "command.session.fork": "Forgren fra melding", @@ -233,6 +241,7 @@ export const dict = { "prompt.action.attachFile": "Legg ved fil", "prompt.attachment.remove": "Fjern vedlegg", "prompt.action.send": "Send", + "prompt.action.send.blocked": "Svar på eller avvis det ventende spørsmålet først", "prompt.action.stop": "Stopp", "prompt.action.enhance": "Forbedre prompt", "prompt.action.resetModel": "Tilbakestill modell til standard", @@ -398,6 +407,7 @@ export const dict = { "error.page.title": "Noe gikk galt", "error.page.description": "Det oppstod en feil under lasting av applikasjonen.", "error.page.details.label": "Feildetaljer", + "error.details.show": "Detaljer", "error.page.action.restart": "Start på nytt", "error.page.action.checking": "Sjekker...", "error.page.action.checkUpdates": "Se etter oppdateringer", @@ -689,6 +699,9 @@ export const dict = { "settings.providers.tag.config": "Konfigurasjon", "settings.providers.tag.custom": "Tilpasset", "settings.providers.tag.other": "Annet", + "settings.providers.modeModels": "Modell per modus", + "settings.providers.modeModels.description": + "Overstyr standardmodellen for bestemte moduser. Hvis ikke angitt, brukes den globale standardmodellen.", "settings.models.title": "Modeller", "settings.models.description": "Modellinnstillinger vil kunne konfigureres her.", "settings.agents.title": "Agenter", @@ -836,12 +849,14 @@ export const dict = { "Kilo Code er en AI-kodingsassistent. Be den om å bygge funksjoner, fikse feil eller forklare kodebasen din.", "session.messages.scrollToBottom": "Rull til bunnen", "session.status.writingResponse": "Skriver svar…", + "session.status.retry": "Prøver på nytt…", "session.status.retrying": "Prøver på nytt (forsøk {{ attempt }})… {{ message }}", "session.status.working": "Arbeider…", "dialog.model.noProviders": "Ingen leverandører", "prompt.placeholder.connecting": "Kobler til server...", + "prompt.placeholder.error": "Tilkobling mislyktes. Sjekk utdatapanelet eller start utvidelsen på nytt.", "prompt.placeholder.default": "Skriv en melding... (Enter for å sende, Shift+Enter for ny linje)", "context.usage.sessionCost": "Sesjonskostnad", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 97923dc9774..5af63b6d036 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "Otwórz ustawienia", "command.session.previous": "Poprzednia sesja", "command.session.next": "Następna sesja", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Poprzednia nieprzeczytana sesja", + "command.session.next.unseen": "Następna nieprzeczytana sesja", "command.session.archive": "Zarchiwizuj sesję", "command.palette": "Paleta poleceń", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "Cofnij ostatnią wiadomość", "command.session.redo": "Ponów", "command.session.redo.description": "Ponów ostatnią cofniętą wiadomość", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Kompaktuj sesję", "command.session.compact.description": "Podsumuj sesję, aby zmniejszyć rozmiar kontekstu", "command.session.fork": "Rozwidlij od wiadomości", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "Załącz plik", "prompt.attachment.remove": "Usuń załącznik", "prompt.action.send": "Wyślij", + "prompt.action.send.blocked": "Najpierw odpowiedz na oczekujące pytanie lub je odrzuć", "prompt.action.stop": "Zatrzymaj", "prompt.action.enhance": "Ulepsz prompt", "prompt.action.resetModel": "Zresetuj model do domyślnego", @@ -689,6 +698,9 @@ export const dict = { "settings.providers.tag.config": "Konfiguracja", "settings.providers.tag.custom": "Niestandardowe", "settings.providers.tag.other": "Inne", + "settings.providers.modeModels": "Model na tryb", + "settings.providers.modeModels.description": + "Zastąp domyślny model dla określonych trybów. Jeśli nie ustawiono, używany jest globalny domyślny model.", "settings.models.title": "Modele", "settings.models.description": "Ustawienia modeli będą tutaj konfigurowalne.", "settings.agents.title": "Agenci", @@ -835,12 +847,14 @@ export const dict = { "Kilo Code to asystent programowania AI. Poproś go o tworzenie funkcji, naprawę błędów lub wyjaśnienie bazy kodu.", "session.messages.scrollToBottom": "Przewiń na dół", "session.status.writingResponse": "Pisanie odpowiedzi…", + "session.status.retry": "Ponawianie…", "session.status.retrying": "Ponawiam próbę ({{ attempt }})… {{ message }}", "session.status.working": "Pracuję…", "dialog.model.noProviders": "Brak dostawców", "prompt.placeholder.connecting": "Łączenie z serwerem...", + "prompt.placeholder.error": "Połączenie nie powiodło się. Sprawdź panel wyjściowy lub uruchom ponownie rozszerzenie.", "prompt.placeholder.default": "Wpisz wiadomość... (Enter, aby wysłać, Shift+Enter dla nowej linii)", "context.usage.sessionCost": "Koszt sesji", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 08100091c9e..4f112c73096 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "Открыть настройки", "command.session.previous": "Предыдущая сессия", "command.session.next": "Следующая сессия", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "Предыдущая непрочитанная сессия", + "command.session.next.unseen": "Следующая непрочитанная сессия", "command.session.archive": "Архивировать сессию", "command.palette": "Палитра команд", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "Отменить последнее сообщение", "command.session.redo": "Повторить", "command.session.redo.description": "Повторить отменённое сообщение", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "Сжать сессию", "command.session.compact.description": "Сократить сессию для уменьшения размера контекста", "command.session.fork": "Создать ответвление", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "Прикрепить файл", "prompt.attachment.remove": "Удалить вложение", "prompt.action.send": "Отправить", + "prompt.action.send.blocked": "Сначала ответьте на ожидающий вопрос или отклоните его", "prompt.action.stop": "Остановить", "prompt.action.enhance": "Улучшить промпт", "prompt.action.resetModel": "Сбросить модель на значение по умолчанию", @@ -692,6 +701,9 @@ export const dict = { "settings.providers.tag.config": "Конфигурация", "settings.providers.tag.custom": "Пользовательский", "settings.providers.tag.other": "Другое", + "settings.providers.modeModels": "Модель для режима", + "settings.providers.modeModels.description": + "Переопределите модель по умолчанию для определённых режимов. Если не задано, используется глобальная модель по умолчанию.", "settings.models.title": "Модели", "settings.models.description": "Настройки моделей будут доступны здесь.", "settings.agents.title": "Агенты", @@ -839,12 +851,14 @@ export const dict = { "Kilo Code — это AI-помощник для программирования. Попросите его создать функции, исправить ошибки или объяснить вашу кодовую базу.", "session.messages.scrollToBottom": "Прокрутить вниз", "session.status.writingResponse": "Пишу ответ…", + "session.status.retry": "Повторная попытка…", "session.status.retrying": "Повторная попытка ({{ attempt }})… {{ message }}", "session.status.working": "Работаю…", "dialog.model.noProviders": "Нет провайдеров", "prompt.placeholder.connecting": "Подключение к серверу...", + "prompt.placeholder.error": "Не удалось подключиться. Проверьте панель вывода или перезапустите расширение.", "prompt.placeholder.default": "Введите сообщение... (Enter для отправки, Shift+Enter для новой строки)", "context.usage.sessionCost": "Стоимость сессии", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index cdfd4b6bf06..7e974c6bc3c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -28,8 +28,8 @@ export const dict = { "command.settings.open": "เปิดการตั้งค่า", "command.session.previous": "เซสชันก่อนหน้า", "command.session.next": "เซสชันถัดไป", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "เซสชันที่ยังไม่ได้อ่านก่อนหน้า", + "command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป", "command.session.archive": "จัดเก็บเซสชัน", "command.palette": "คำสั่งค้นหา", @@ -78,6 +78,14 @@ export const dict = { "command.session.undo.description": "ยกเลิกข้อความล่าสุด", "command.session.redo": "ทำซ้ำ", "command.session.redo.description": "ทำซ้ำข้อความที่ถูกยกเลิกล่าสุด", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "บีบอัดเซสชัน", "command.session.compact.description": "สรุปเซสชันเพื่อลดขนาดบริบท", "command.session.fork": "แตกแขนงจากข้อความ", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "แนบไฟล์", "prompt.attachment.remove": "เอาไฟล์แนบออก", "prompt.action.send": "ส่ง", + "prompt.action.send.blocked": "โปรดตอบหรือข้ามคำถามที่รอดำเนินการก่อน", "prompt.action.stop": "หยุด", "prompt.action.enhance": "ปรับปรุงพรอมต์", "prompt.action.resetModel": "รีเซ็ตโมเดลเป็นค่าเริ่มต้น", @@ -683,6 +692,9 @@ export const dict = { "settings.providers.tag.config": "กำหนดค่า", "settings.providers.tag.custom": "กำหนดเอง", "settings.providers.tag.other": "อื่น ๆ", + "settings.providers.modeModels": "โมเดลต่อโหมด", + "settings.providers.modeModels.description": + "แทนที่โมเดลเริ่มต้นสำหรับโหมดที่กำหนด หากไม่ได้ตั้งค่า จะใช้โมเดลเริ่มต้นทั่วไป", "settings.models.title": "โมเดล", "settings.models.description": "การตั้งค่าโมเดลจะสามารถกำหนดค่าได้ที่นี่", "settings.agents.title": "เอเจนต์", @@ -828,12 +840,14 @@ export const dict = { "session.messages.welcome": "Kilo Code เป็นผู้ช่วยเขียนโค้ด AI ขอให้สร้างฟีเจอร์ แก้ไขบัก หรืออธิบายโค้ดเบสของคุณ", "session.messages.scrollToBottom": "เลื่อนไปด้านล่าง", "session.status.writingResponse": "กำลังเขียนคำตอบ...", + "session.status.retry": "กำลังลองใหม่…", "session.status.retrying": "กำลังลองใหม่ (ครั้งที่ {{ attempt }})… {{ message }}", "session.status.working": "กำลังทำงาน...", "dialog.model.noProviders": "ไม่มีผู้ให้บริการ", "prompt.placeholder.connecting": "กำลังเชื่อมต่อกับเซิร์ฟเวอร์...", + "prompt.placeholder.error": "การเชื่อมต่อล้มเหลว ตรวจสอบแผงเอาต์พุตหรือรีสตาร์ทส่วนขยาย", "prompt.placeholder.default": "พิมพ์ข้อความ... (Enter เพื่อส่ง, Shift+Enter เพื่อขึ้นบรรทัดใหม่)", "context.usage.sessionCost": "ค่าใช้จ่ายเซสชัน", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index ab749eb7c67..d7c856ae4ee 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -32,8 +32,8 @@ export const dict = { "command.settings.open": "打开设置", "command.session.previous": "上一个会话", "command.session.next": "下一个会话", - "command.session.previous.unseen": "Previous unread session", - "command.session.next.unseen": "Next unread session", + "command.session.previous.unseen": "上一个未读会话", + "command.session.next.unseen": "下一个未读会话", "command.session.archive": "归档会话", "command.palette": "命令面板", @@ -82,6 +82,14 @@ export const dict = { "command.session.undo.description": "撤销上一条消息", "command.session.redo": "重做", "command.session.redo.description": "重做上一条撤销的消息", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "精简会话", "command.session.compact.description": "总结会话以减少上下文大小", "command.session.fork": "从消息分叉", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "附加文件", "prompt.attachment.remove": "移除附件", "prompt.action.send": "发送", + "prompt.action.send.blocked": "请先回答或忽略待处理的问题", "prompt.action.stop": "停止", "prompt.action.enhance": "优化提示词", "prompt.action.resetModel": "重置模型为默认值", @@ -676,6 +685,8 @@ export const dict = { "settings.providers.tag.config": "配置", "settings.providers.tag.custom": "自定义", "settings.providers.tag.other": "其他", + "settings.providers.modeModels": "按模式选择模型", + "settings.providers.modeModels.description": "为特定模式覆盖默认模型。如果未设置,将使用全局默认模型。", "settings.models.title": "模型", "settings.models.description": "模型设置将在此处可配置。", "settings.agents.title": "智能体", @@ -821,12 +832,14 @@ export const dict = { "session.messages.welcome": "Kilo Code 是一个 AI 编程助手。让它帮你构建功能、修复 bug 或解释代码库。", "session.messages.scrollToBottom": "滚动到底部", "session.status.writingResponse": "正在撰写回复…", + "session.status.retry": "正在重试…", "session.status.retrying": "正在重试(第 {{ attempt }} 次)… {{ message }}", "session.status.working": "处理中…", "dialog.model.noProviders": "无供应商", "prompt.placeholder.connecting": "正在连接服务器...", + "prompt.placeholder.error": "连接失败。请检查输出面板或重启扩展。", "prompt.placeholder.default": "输入消息... (Enter 发送,Shift+Enter 换行)", "context.usage.sessionCost": "会话费用", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 07ae7f89231..6746fe6c6cf 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -82,6 +82,14 @@ export const dict = { "command.session.undo.description": "復原上一則訊息", "command.session.redo": "重做", "command.session.redo.description": "重做上一則復原的訊息", + "revert.banner.count_one": "{{count}} message reverted", + "revert.banner.count_other": "{{count}} messages reverted", + "revert.banner.redo": "Redo", + "revert.banner.redo.all": "Redo All", + "revert.banner.hint": "Send a new message to make this permanent", + "revert.turn.revert": "Revert to here", + "revert.turn.copy": "Copy message", + "revert.bottom.undo": "Undo Changes", "command.session.compact": "精簡工作階段", "command.session.compact.description": "總結工作階段以減少上下文大小", "command.session.fork": "從訊息分支", @@ -230,6 +238,7 @@ export const dict = { "prompt.action.attachFile": "附加檔案", "prompt.attachment.remove": "移除附件", "prompt.action.send": "傳送", + "prompt.action.send.blocked": "請先回答或忽略待處理的問題", "prompt.action.stop": "停止", "prompt.action.enhance": "改善提示詞", "prompt.action.resetModel": "重置模型為預設值", @@ -678,6 +687,8 @@ export const dict = { "settings.providers.tag.config": "設定", "settings.providers.tag.custom": "自訂", "settings.providers.tag.other": "其他", + "settings.providers.modeModels": "按模式選擇模型", + "settings.providers.modeModels.description": "為特定模式覆寫預設模型。如果未設定,將使用全域預設模型。", "settings.models.title": "模型", "settings.models.description": "可在此調整模型設定。", "settings.agents.title": "Agents", @@ -823,12 +834,14 @@ export const dict = { "session.messages.welcome": "Kilo Code 是一個 AI 程式設計助手。讓它幫你建構功能、修復 bug 或解釋程式碼庫。", "session.messages.scrollToBottom": "捲動至底部", "session.status.writingResponse": "正在撰寫回覆…", + "session.status.retry": "正在重試…", "session.status.retrying": "正在重試(第 {{ attempt }} 次)… {{ message }}", "session.status.working": "處理中…", "dialog.model.noProviders": "沒有供應商", "prompt.placeholder.connecting": "正在連線至伺服器...", + "prompt.placeholder.error": "連線失敗。請檢查輸出面板或重新啟動擴充功能。", "prompt.placeholder.default": "輸入訊息... (Enter 送出,Shift+Enter 換行)", "context.usage.sessionCost": "工作階段費用", diff --git a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx index cdfc06c47ce..3cdf84c201a 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx @@ -152,6 +152,11 @@ export function mockSessionValue(overrides?: { getSessionModel: () => ({ providerID: "kilo", modelID: "anthropic/claude-sonnet-4-6" }), setSessionModel: noop, setSessionAgent: noop, + revert: () => undefined, + revertedCount: () => 0, + summary: () => undefined, + revertSession: noop, + unrevertSession: noop, variantList: () => [], currentVariant: () => undefined, selectVariant: noop, diff --git a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx index 7904c68d66d..72070bf8758 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx @@ -162,7 +162,8 @@ const globPermission: PermissionRequest = { sessionID: SESSION_ID, toolName: "glob", patterns: ["**/*.md"], - args: { pattern: "**/*.md", rules: ["**/*.md"] }, + always: ["*"], + args: { pattern: "**/*.md" }, tool: { messageID: ASST_MSG_ID, callID: "call-glob-001" }, } @@ -171,6 +172,7 @@ const bashPermission: PermissionRequest = { sessionID: SESSION_ID, toolName: "bash", patterns: ["bun test"], + always: ["bun *"], args: { command: "bun test", rules: ["bun *", "bun test"] }, tool: { messageID: ASST_MSG_ID, callID: "call-bash-001" }, } @@ -180,7 +182,8 @@ const dockPermission: PermissionRequest = { sessionID: SESSION_ID, toolName: "write", patterns: ["src/main.tsx", "src/utils.ts"], - args: { rules: ["src/main.tsx", "src/utils.ts"] }, + always: ["*"], + args: {}, // No `tool` field — this is a non-tool (dock) permission } @@ -292,7 +295,8 @@ const todoWritePermission: PermissionRequest = { sessionID: SESSION_ID, toolName: "todowrite", patterns: ["*"], - args: { rules: ["*"] }, + always: ["*"], + args: {}, tool: { messageID: ASST_MSG_ID, callID: "call-todo-001" }, } @@ -612,7 +616,8 @@ const editPermission: PermissionRequest = { sessionID: SESSION_ID, toolName: "edit", patterns: ["src/components/App.tsx", "src/utils/helpers.ts"], - args: { rules: ["src/components/App.tsx", "src/utils/helpers.ts"] }, + always: ["*"], + args: {}, tool: { messageID: ASST_MSG_ID, callID: "call-edit-001" }, } @@ -645,7 +650,8 @@ const websearchPermission: PermissionRequest = { sessionID: SESSION_ID, toolName: "websearch", patterns: ["*"], - args: { rules: ["*"] }, + always: ["*"], + args: {}, tool: { messageID: ASST_MSG_ID, callID: "call-websearch-001" }, } @@ -677,8 +683,9 @@ const externalDirPermission: PermissionRequest = { id: "perm-extdir-001", sessionID: SESSION_ID, toolName: "external_directory", - patterns: ["/home/user/other-project/config.json"], - args: { rules: ["/home/user/other-project/config.json"] }, + patterns: ["/home/user/other-project/*"], + always: ["/home/user/other-project/*"], + args: { filepath: "/home/user/other-project/config.json" }, tool: { messageID: ASST_MSG_ID, callID: "call-extdir-001" }, } @@ -711,6 +718,7 @@ const bashManyRulesPermission: PermissionRequest = { sessionID: SESSION_ID, toolName: "bash", patterns: ["npm install"], + always: ["npm install *"], args: { command: "npm install", rules: ["npm *", "npm install", "npm run *", "npm test", "npm run build", "npx *"], @@ -747,6 +755,7 @@ const subagentPermission: PermissionRequest = { sessionID: "child-session-001", toolName: "bash", patterns: ["git status"], + always: ["git status *"], args: { command: "git status", rules: ["git *", "git status"] }, tool: { messageID: ASST_MSG_ID, callID: "call-subagent-001" }, } diff --git a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx index 253d7b99018..fe65248a094 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx @@ -2,15 +2,20 @@ /** * Stories for Marketplace components. * - * Renders SkillsMarketplace and ItemCard directly with mock data + * Renders MarketplaceListView and ItemCard directly with mock data * so no API requests are made. */ import type { Meta, StoryObj } from "storybook-solidjs-vite" import { StoryProviders } from "./StoryProviders" -import SkillsMarketplace from "../components/marketplace/SkillsMarketplace" -import ItemCard from "../components/marketplace/ItemCard" -import type { SkillMarketplaceItem, MarketplaceInstalledMetadata } from "../types/marketplace" +import { MarketplaceListView } from "../components/marketplace/MarketplaceListView" +import { ItemCard } from "../components/marketplace/ItemCard" +import type { + SkillMarketplaceItem, + McpMarketplaceItem, + ModeMarketplaceItem, + MarketplaceInstalledMetadata, +} from "../types/marketplace" import "../components/marketplace/marketplace.css" const meta: Meta = { @@ -98,13 +103,160 @@ const MOCK_SKILLS: SkillMarketplaceItem[] = [ }, ] +const MOCK_MCPS: McpMarketplaceItem[] = [ + { + type: "mcp", + id: "github-mcp", + name: "GitHub", + description: + "Interact with GitHub repositories, issues, and pull requests. Search code, manage branches, and automate workflows.", + url: "https://github.com/modelcontextprotocol/servers/tree/main/src/github", + content: + '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" } }', + parameters: [{ name: "GitHub Token", key: "GITHUB_TOKEN", placeholder: "ghp_xxxxxxxxxxxx" }], + author: "Anthropic", + tags: ["version-control", "development"], + }, + { + type: "mcp", + id: "postgres-mcp", + name: "PostgreSQL", + description: "Query and manage PostgreSQL databases. Run SQL, inspect schemas, and manage connections.", + url: "https://github.com/modelcontextprotocol/servers/tree/main/src/postgres", + content: [ + { + name: "npx", + content: + '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres", "${CONNECTION_STRING}"] }', + parameters: [ + { name: "Connection String", key: "CONNECTION_STRING", placeholder: "postgresql://user:pass@localhost/db" }, + ], + }, + { + name: "Docker", + content: + '{ "command": "docker", "args": ["run", "--rm", "-e", "CONNECTION_STRING=${CONNECTION_STRING}", "mcp/postgres"] }', + parameters: [ + { name: "Connection String", key: "CONNECTION_STRING", placeholder: "postgresql://user:pass@localhost/db" }, + ], + prerequisites: ["Docker must be installed and running"], + }, + ], + author: "Anthropic", + tags: ["database", "sql"], + }, + { + type: "mcp", + id: "filesystem-mcp", + name: "Filesystem", + description: "Read, write, and manage files on the local filesystem with configurable access controls.", + url: "https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem", + content: '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "${ALLOWED_DIR}"] }', + parameters: [{ name: "Allowed Directory", key: "ALLOWED_DIR", placeholder: "/path/to/directory" }], + tags: ["filesystem", "development"], + }, + { + type: "mcp", + id: "slack-mcp", + name: "Slack", + description: "Send and receive messages, manage channels, and search conversations in Slack workspaces.", + url: "https://github.com/modelcontextprotocol/servers/tree/main/src/slack", + content: + '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-slack"], "env": { "SLACK_TOKEN": "${SLACK_TOKEN}" } }', + parameters: [{ name: "Slack Bot Token", key: "SLACK_TOKEN", placeholder: "xoxb-xxxxxxxxxxxx" }], + author: "Anthropic", + tags: ["communication", "productivity"], + }, + { + type: "mcp", + id: "brave-search-mcp", + name: "Brave Search", + description: "Search the web using the Brave Search API for real-time information retrieval.", + url: "https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search", + content: + '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"], "env": { "BRAVE_API_KEY": "${BRAVE_API_KEY}" } }', + parameters: [{ name: "API Key", key: "BRAVE_API_KEY", placeholder: "BSA-xxxxxxxxxxxx" }], + tags: ["search", "web"], + }, + { + type: "mcp", + id: "puppeteer-mcp", + name: "Puppeteer", + description: "Automate browser interactions, take screenshots, and scrape web pages using headless Chrome.", + url: "https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer", + content: '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-puppeteer"] }', + prerequisites: ["Chrome or Chromium must be installed"], + tags: ["browser", "automation", "web"], + }, +] + +const MOCK_MODES: ModeMarketplaceItem[] = [ + { + type: "mode", + id: "architect", + name: "Architect", + description: + "High-level system design and planning. Focuses on architecture decisions, component boundaries, and technical specifications without writing implementation code.", + content: "name: Architect\nroleDefinition: You are a software architect...\ngroups:\n - read\n", + author: "Kilo", + tags: ["planning", "design"], + }, + { + type: "mode", + id: "reviewer", + name: "Code Reviewer", + description: + "Reviews code for bugs, security issues, and best practices. Provides actionable feedback with specific line references.", + content: "name: Code Reviewer\nroleDefinition: You are a code reviewer...\ngroups:\n - read\n", + author: "Kilo", + tags: ["review", "quality"], + }, + { + type: "mode", + id: "docs-writer", + name: "Documentation Writer", + description: "Generates and maintains documentation including READMEs, API docs, and inline code comments.", + content: "name: Documentation Writer\nroleDefinition: You write documentation...\ngroups:\n - read\n - edit\n", + tags: ["documentation", "writing"], + }, + { + type: "mode", + id: "tdd", + name: "Test-Driven Developer", + description: + "Follows strict TDD methodology: write failing tests first, implement minimum code to pass, then refactor.", + content: + "name: Test-Driven Developer\nroleDefinition: You follow TDD...\ngroups:\n - read\n - edit\n - command\n", + author: "Community", + tags: ["testing", "methodology"], + }, + { + type: "mode", + id: "debug", + name: "Debugger", + description: "Systematically diagnoses and fixes bugs. Uses logs, stack traces, and bisection to isolate issues.", + content: "name: Debugger\nroleDefinition: You are a debugger...\ngroups:\n - read\n - command\n", + tags: ["debugging", "troubleshooting"], + }, +] + const EMPTY_METADATA: MarketplaceInstalledMetadata = { project: {}, global: {} } -const PARTIAL_INSTALLED: MarketplaceInstalledMetadata = { +const PARTIAL_INSTALLED_SKILLS: MarketplaceInstalledMetadata = { project: { "nextjs-developer": { type: "skill" } }, global: { "python-data-science": { type: "skill" } }, } +const PARTIAL_INSTALLED_MCPS: MarketplaceInstalledMetadata = { + project: { "github-mcp": { type: "mcp" } }, + global: { "postgres-mcp": { type: "mcp" } }, +} + +const PARTIAL_INSTALLED_MODES: MarketplaceInstalledMetadata = { + project: { architect: { type: "mode" } }, + global: { reviewer: { type: "mode" } }, +} + const noop = () => {} // --------------------------------------------------------------------------- @@ -116,10 +268,13 @@ export const SkillsTabWithItems: Story = { render: () => (
- @@ -133,10 +288,13 @@ export const SkillsTabWithInstalled: Story = { render: () => (
- @@ -150,7 +308,16 @@ export const SkillsTabEmpty: Story = { render: () => (
- +
), @@ -181,7 +348,7 @@ export const InstalledSkillCard: Story = {
), } + +// --------------------------------------------------------------------------- +// MCP Stories +// --------------------------------------------------------------------------- + +export const McpTabWithItems: Story = { + name: "MCP tab — with items", + render: () => ( + +
+ +
+
+ ), +} + +export const McpTabWithInstalled: Story = { + name: "MCP tab — some installed", + render: () => ( + +
+ +
+
+ ), +} + +export const McpTabEmpty: Story = { + name: "MCP tab — empty state", + render: () => ( + +
+ +
+
+ ), +} + +export const SingleMcpCard: Story = { + name: "ItemCard — single MCP not installed", + render: () => ( + +
+ +
+
+ ), +} + +export const InstalledMcpCard: Story = { + name: "ItemCard — installed MCP", + render: () => ( + +
+ +
+
+ ), +} + +// --------------------------------------------------------------------------- +// Mode Stories +// --------------------------------------------------------------------------- + +export const ModesTabWithItems: Story = { + name: "Modes tab — with items", + render: () => ( + +
+ +
+
+ ), +} + +export const ModesTabWithInstalled: Story = { + name: "Modes tab — some installed", + render: () => ( + +
+ +
+
+ ), +} + +export const ModesTabEmpty: Story = { + name: "Modes tab — empty state", + render: () => ( + +
+ +
+
+ ), +} + +export const SingleModeCard: Story = { + name: "ItemCard — single mode not installed", + render: () => ( + +
+ +
+
+ ), +} + +export const InstalledModeCard: Story = { + name: "ItemCard — installed mode", + render: () => ( + +
+ +
+
+ ), +} diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 5f3aa64783e..569093124b1 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -446,6 +446,75 @@ background: var(--vscode-button-hoverBackground); } +/* ============================================ + Revert Banner + ============================================ */ + +.revert-banner { + margin: 8px 12px; + padding: 8px 12px; + border-radius: 4px; + background: var(--surface-secondary, var(--vscode-editorWidget-background, #252526)); + border: 1px solid var(--border-base, var(--vscode-editorWidget-border, #454545)); + font-size: 12px; +} + +.revert-banner-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.revert-banner-info { + display: flex; + align-items: center; + gap: 6px; + color: var(--text-muted, var(--vscode-descriptionForeground)); +} + +.revert-banner-count { + font-weight: 500; +} + +.revert-banner-actions { + display: flex; + gap: 4px; + flex-shrink: 0; +} + +.revert-banner-files { + margin-top: 6px; + display: flex; + flex-direction: column; + gap: 2px; +} + +.revert-banner-file { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 1px 0; + color: var(--text-base, var(--vscode-foreground)); +} + +.revert-banner-filename { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + direction: rtl; + text-align: left; +} + +.revert-banner-hint { + margin-top: 6px; + color: var(--text-muted, var(--vscode-descriptionForeground)); + font-style: italic; + font-size: 11px; +} + /* ============================================ Prompt Input ============================================ */ @@ -707,6 +776,7 @@ border-radius: 4px; border: 1px solid var(--vscode-widget-border, rgba(255, 255, 255, 0.1)); display: block; + cursor: pointer; } .image-attachment-remove { @@ -1377,14 +1447,6 @@ margin-top: 0; } - [data-slot="permission-session-hint"] { - font-size: 11px; - color: var(--text-weak, var(--vscode-descriptionForeground)); - margin: 0; - padding: 4px 0 0; - text-align: left; - } - [data-slot="permission-actions"] { display: flex; align-items: center; diff --git a/packages/kilo-vscode/webview-ui/src/types/marketplace.ts b/packages/kilo-vscode/webview-ui/src/types/marketplace.ts index d41a7351266..943d2bec10a 100644 --- a/packages/kilo-vscode/webview-ui/src/types/marketplace.ts +++ b/packages/kilo-vscode/webview-ui/src/types/marketplace.ts @@ -1,3 +1,17 @@ +export interface McpParameter { + name: string + key: string + placeholder?: string + optional?: boolean +} + +export interface McpInstallationMethod { + name: string + content: string + parameters?: McpParameter[] + prerequisites?: string[] +} + export interface MarketplaceItemBase { id: string name: string @@ -11,7 +25,8 @@ export interface MarketplaceItemBase { export interface McpMarketplaceItem extends MarketplaceItemBase { type: "mcp" url: string - content: string | unknown[] + content: string | McpInstallationMethod[] + parameters?: McpParameter[] } export interface ModeMarketplaceItem extends MarketplaceItemBase { @@ -41,8 +56,7 @@ export interface MarketplaceInstalledMetadata { } export interface MarketplaceFilters { - search?: string type?: string - status?: string + search?: string tags?: string[] } diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 61ffec879d7..9a39433680a 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -113,12 +113,34 @@ export interface Message { tokens?: TokenUsage } +// File diff info (matches Snapshot.FileDiff from CLI backend) +export interface SessionFileDiff { + file: string + before: string + after: string + additions: number + deletions: number + status?: "added" | "deleted" | "modified" +} + // Session info (simplified for webview) export interface SessionInfo { id: string title?: string createdAt: string updatedAt: string + revert?: { + messageID: string + partID?: string + snapshot?: string + diff?: string + } | null + summary?: { + additions: number + deletions: number + files: number + diffs?: SessionFileDiff[] + } | null } // Cloud session info (from Kilo cloud API) @@ -135,6 +157,7 @@ export interface PermissionRequest { sessionID: string toolName: string patterns: string[] + always: string[] args: Record & { rules?: string[] } message?: string tool?: { messageID: string; callID: string } @@ -448,6 +471,12 @@ export interface SessionDeletedMessage { sessionID: string } +export interface MessageRemovedMessage { + type: "messageRemoved" + sessionID: string + messageID: string +} + export interface MessagesLoadedMessage { type: "messagesLoaded" sessionID: string @@ -1102,6 +1131,7 @@ export type ExtensionMessage = | SessionCreatedMessage | SessionUpdatedMessage | SessionDeletedMessage + | MessageRemovedMessage | MessagesLoadedMessage | MessageCreatedMessage | SessionsLoadedMessage @@ -1198,6 +1228,17 @@ export interface AbortRequest { sessionID: string } +export interface RevertSessionRequest { + type: "revertSession" + sessionID: string + messageID: string +} + +export interface UnrevertSessionRequest { + type: "unrevertSession" + sessionID: string +} + export interface PermissionResponseRequest { type: "permissionResponse" permissionId: string @@ -1670,6 +1711,13 @@ export interface OpenSubAgentViewerRequest { title?: string } +// Preview an image attachment in VS Code's built-in image viewer +export interface PreviewImageRequest { + type: "previewImage" + dataUrl: string + filename: string +} + // Set default base branch (webview → extension) export interface SetDefaultBaseBranchRequest { type: "agentManager.setDefaultBaseBranch" @@ -1679,6 +1727,8 @@ export interface SetDefaultBaseBranchRequest { export type WebviewMessage = | SendMessageRequest | AbortRequest + | RevertSessionRequest + | UnrevertSessionRequest | PermissionResponseRequest | CreateSessionRequest | ClearSessionRequest @@ -1767,6 +1817,7 @@ export type WebviewMessage = | OpenChangesRequest | RetryConnectionRequest | OpenSubAgentViewerRequest + | PreviewImageRequest | SetDefaultBaseBranchRequest | FetchMarketplaceDataMessage | FilterMarketplaceItemsMessage diff --git a/packages/opencode/package.json b/packages/opencode/package.json index ebee235af3a..d53387e0df8 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.0.48", + "version": "7.0.49", "name": "@kilocode/cli", "type": "module", "license": "MIT", @@ -81,13 +81,15 @@ "@gitlab/opencode-gitlab-auth": "1.3.3", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", + "@kilocode/kilo-gateway": "workspace:*", + "@kilocode/kilo-telemetry": "workspace:*", + "@kilocode/plugin": "workspace:*", + "@kilocode/sdk": "workspace:*", "@modelcontextprotocol/sdk": "1.25.2", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", - "@kilocode/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", - "@kilocode/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", "@opentui/core": "0.1.86", @@ -121,6 +123,7 @@ "opentui-spinner": "0.0.6", "partial-json": "0.1.7", "remeda": "catalog:", + "simple-git": "3.31.1", "solid-js": "catalog:", "strip-ansi": "7.1.2", "tree-sitter-bash": "0.25.0", @@ -134,9 +137,6 @@ "zod": "catalog:", "zod-to-json-schema": "3.24.5", "@morphllm/morphsdk": "0.2.148", - "@kilocode/kilo-gateway": "workspace:*", - "@kilocode/kilo-telemetry": "workspace:*", - "simple-git": "3.31.1" }, "overrides": { "drizzle-orm": "1.0.0-beta.16-ea816b6" diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index 785e4c4fe91..c2cd4d17d9c 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -158,12 +158,12 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { {/* kilocode_change */} - + {/* kilocode_change */} - This will allow the following patterns until Kilo is restarted + This will allow the following patterns permanently {(pattern) => ( diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 540e9976db1..10bd055e08b 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1426,7 +1426,7 @@ export namespace Config { export async function update(config: Info) { const filepath = path.join(Instance.directory, "config.json") const existing = await loadFile(filepath) - await Filesystem.writeJson(filepath, stripNulls(mergeDeep(existing, config) as Record)) // kilocode_change - strip null delete sentinels + await Filesystem.writeJson(filepath, mergeConfig(existing, config)) // kilocode_change await Instance.dispose() } @@ -1462,6 +1462,35 @@ export namespace Config { } // kilocode_change end + // kilocode_change start — merge config with normalization pipeline + /** + * Merge a patch into an existing config: + * 1. Normalize permission scalars → objects when the patch has an object + * (e.g. existing `"bash": "ask"` + patch `"bash": { "npm *": "allow" }` + * → promotes existing to `"bash": { "*": "ask" }` so mergeDeep works) + * 2. Deep-merge + * 3. Strip null delete sentinels + */ + function mergeConfig(existing: Info, patch: Info): Info { + const e = { ...existing } as Record + const p = patch as Record + // Normalize permission scalars before merge (clone to avoid mutating the input) + const existingPerm = e.permission + const patchPerm = p.permission + if (isRecord(existingPerm) && isRecord(patchPerm)) { + const cloned = { ...existingPerm } + for (const [key, patchValue] of Object.entries(patchPerm)) { + const existingValue = cloned[key] + if (typeof existingValue === "string" && isRecord(patchValue)) { + cloned[key] = { "*": existingValue } + } + } + e.permission = cloned + } + return stripNulls(mergeDeep(e, p) as Record) as Info + } + // kilocode_change end + function patchJsonc(input: string, patch: unknown, path: string[] = []): string { if (!isRecord(patch)) { // kilocode_change - null means "delete this key" — pass undefined to jsonc-parser's modify() @@ -1478,11 +1507,15 @@ export namespace Config { // scalar (e.g. permission.bash is "ask" as a string), jsonc-parser cannot // add child keys to it. Detect this case and replace the whole node with // the patch object in a single modify() call instead of recursing. + // For permission keys, promote the scalar to { "*": scalarValue } so the + // wildcard default is preserved. For other keys, replace directly. if (path.length > 0) { const tree = parseTree(input) const node = tree && findNodeAtLocation(tree, path) if (node && node.type !== "object") { - const edits = modify(input, path, patch, { + const isPermissionKey = path[0] === "permission" && path.length === 2 + const replacement = isPermissionKey ? { "*": node.value, ...patch } : patch + const edits = modify(input, path, replacement, { formattingOptions: { insertSpaces: true, tabSize: 2 }, }) return applyEdits(input, edits) @@ -1530,7 +1563,10 @@ export namespace Config { }) } - export async function updateGlobal(config: Info) { + // kilocode_change start — add dispose option to skip Instance.disposeAll for permission-only changes + export async function updateGlobal(config: Info, options?: { dispose?: boolean }) { + const dispose = options?.dispose ?? true + // kilocode_change end const filepath = globalConfigFile() const before = await Filesystem.readText(filepath).catch((err: any) => { if (err.code === "ENOENT") return "{}" @@ -1540,7 +1576,7 @@ export namespace Config { const next = await (async () => { if (!filepath.endsWith(".jsonc")) { const existing = parseConfig(before, filepath) - const merged = stripNulls(mergeDeep(existing, config) as Record) as Info // kilocode_change - strip null delete sentinels + const merged = mergeConfig(existing, config) // kilocode_change await Filesystem.writeJson(filepath, merged) return merged } @@ -1553,17 +1589,13 @@ export namespace Config { global.reset() - void Instance.disposeAll() - .catch(() => undefined) - .finally(() => { - GlobalBus.emit("event", { - directory: "global", - payload: { - type: Event.Disposed.type, - properties: {}, - }, - }) - }) + GlobalBus.emit("event", { + directory: "global", + payload: { + type: Event.Disposed.type, + properties: {}, + }, + }) return next } diff --git a/packages/opencode/src/kilocode/ts-check.ts b/packages/opencode/src/kilocode/ts-check.ts new file mode 100644 index 00000000000..449609df830 --- /dev/null +++ b/packages/opencode/src/kilocode/ts-check.ts @@ -0,0 +1,170 @@ +// kilocode_change - new file +import type { Diagnostic } from "vscode-languageserver-types" +import { Log } from "../util/log" +import { Filesystem } from "../util/filesystem" +import path from "path" +import fs from "fs/promises" + +export namespace TsCheck { + const log = Log.create({ service: "ts-check" }) + + // Match: file(line,col): error TSxxxx: message + const DIAG_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.*)$/ + + export async function run(root: string): Promise> { + const result = new Map() + const bin = await resolve(root) + if (!bin) { + log.info("no typescript checker found", { root }) + return result + } + + log.info("running ts check", { bin, root }) + const start = Date.now() + + // --incremental writes a .tsbuildinfo cache so subsequent runs only + // re-check changed files. First run is cold (~1.3s), warm runs + // reuse the cache and typically finish in ~200-400ms. + const proc = Bun.spawn([bin, "--noEmit", "--pretty", "false", "--incremental"], { + cwd: root, + stdout: "pipe", + stderr: "pipe", + env: { ...process.env }, + }) + + const TIMEOUT = 30_000 + const done = Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]) + const settled = await Promise.race([ + done.then(([out, err]) => ({ out, err, timedOut: false as const })), + new Promise<{ out: string; err: string; timedOut: true }>((r) => + setTimeout(() => r({ out: "", err: "", timedOut: true }), TIMEOUT), + ), + ]) + if (settled.timedOut) { + log.warn("ts check timed out, killing process", { elapsed: Date.now() - start }) + proc.kill() + } + + const stdout = settled.out + const stderr = settled.err + + log.info("ts check done", { + elapsed: Date.now() - start, + lines: stdout.split("\n").length, + }) + + if (stderr.trim()) { + log.info("ts check stderr", { stderr: stderr.slice(0, 500) }) + } + + for (const line of stdout.split("\n")) { + const m = DIAG_RE.exec(line) + if (!m) continue + if (m.length < 7) continue + + const file = m[1]! + const row = parseInt(m[2]!, 10) - 1 + const col = parseInt(m[3]!, 10) - 1 + const sev = m[4]! + const abs = path.isAbsolute(file) ? file : path.resolve(root, file) + const normalized = Filesystem.normalizePath(abs) + + const diag: Diagnostic = { + range: { + start: { line: row, character: col }, + end: { line: row, character: col }, + }, + severity: sev === "error" ? 1 : 2, + message: m[6]!, + source: "ts", + code: m[5]!, + } + + const arr = result.get(normalized) ?? [] + arr.push(diag) + result.set(normalized, arr) + } + + return result + } + + // Resolve the native tsgo binary directly, avoiding the node.js wrapper + // (node_modules/.bin/tsgo is a #!/usr/bin/env node script that spawns a + // node process just to execFileSync the native binary — adding ~200MB overhead). + async function resolve(root: string): Promise { + // 1. Try resolving the native tsgo binary from the platform-specific package + const native = await native_tsgo(root) + if (native) return native + + // 2. Try workspace-local tsc from node_modules + const local = await local_tsc(root) + if (local) return local + + // 3. Try global tsc (fallback) + const tsc = Bun.which("tsc") + if (tsc) return tsc + + return undefined + } + + // Walk up from root looking for a usable tsc binary. + // On Windows the JS entrypoint has no shebang support, so use the .cmd shim. + async function local_tsc(root: string): Promise { + const shim = process.platform === "win32" ? path.join(".bin", "tsc.cmd") : path.join("typescript", "bin", "tsc") + let dir = root + while (true) { + const bin = path.join(dir, "node_modules", shim) + if (await exists(bin)) return bin + const parent = path.dirname(dir) + if (parent === dir) break + dir = parent + } + return undefined + } + + // Resolve the native tsgo binary by finding the platform-specific package. + // The @typescript/native-preview npm package includes platform-specific + // optional dependencies like @typescript/native-preview-darwin-arm64 that + // contain the actual native binary at lib/tsgo. + // Exported for use by the LSP server spawn (tsgo --lsp --stdio). + export async function native_tsgo(root: string): Promise { + const pkg = `@typescript/native-preview-${process.platform}-${process.arch}` + + // Walk up from root looking in node_modules (including .bun hoisted paths) + let dir = root + while (true) { + // Standard node_modules layout + const standard = path.join(dir, "node_modules", pkg, "lib", "tsgo") + if (await exists(standard)) return standard + + // Bun hoisted layout: node_modules/.bun/@/node_modules//lib/tsgo + const bun = path.join(dir, "node_modules", ".bun") + if (await exists(bun)) { + const match = await scan_bun(bun, pkg) + if (match) return match + } + + const parent = path.dirname(dir) + if (parent === dir) break + dir = parent + } + + return undefined + } + + // Scan .bun hoisted directory for the platform package + async function scan_bun(dir: string, pkg: string): Promise { + const prefix = pkg.replace("/", "+") + const entries = await fs.readdir(dir).catch(() => [] as string[]) + for (const entry of entries) { + if (!entry.startsWith(prefix + "@")) continue + const bin = path.join(dir, entry, "node_modules", pkg, "lib", "tsgo") + if (await exists(bin)) return bin + } + return undefined + } + + async function exists(p: string): Promise { + return Filesystem.exists(p) + } +} diff --git a/packages/opencode/src/kilocode/ts-client.ts b/packages/opencode/src/kilocode/ts-client.ts new file mode 100644 index 00000000000..d1b21d08dad --- /dev/null +++ b/packages/opencode/src/kilocode/ts-client.ts @@ -0,0 +1,95 @@ +// kilocode_change - new file +// Lightweight TypeScript diagnostic client that shells out to tsgo/tsc +// instead of spawning a persistent typescript-language-server process. +// This drops memory from ~500MB persistent to ~50MB peak (0 idle). + +import { LSPClient } from "../lsp/client" +import { Bus } from "../bus" +import { TsCheck } from "./ts-check" +import { Log } from "../util/log" +import { withTimeout } from "../util/timeout" +import path from "path" +import { Instance } from "../project/instance" + +export namespace TsClient { + const log = Log.create({ service: "ts-client" }) + + export function create(input: { root: string }): LSPClient.Info { + const diagnostics = new Map() + // Pending tsgo run promise — used for coalescing rapid calls. + // Only set when waitForDiagnostics() triggers a run, NOT on + // notify.open() (which is a warm-up call from read.ts). + let pending: Promise | undefined + + function check(): Promise { + if (pending) return pending + pending = TsCheck.run(client.root) + .then((result) => { + diagnostics.clear() + for (const [file, diags] of result) { + diagnostics.set(file, diags) + } + for (const file of result.keys()) { + Bus.publish(LSPClient.Event.Diagnostics, { + path: file, + serverID: client.serverID, + }) + } + }) + .catch((err) => { + log.error("ts check failed", { error: err }) + }) + .finally(() => { + pending = undefined + }) + return pending + } + + const client: LSPClient.Info = { + root: input.root, + get serverID() { + return "typescript" + }, + get connection(): any { + // LSP namespace methods (hover, definition, etc.) call + // connection.sendRequest() directly. Provide a stub that + // rejects so those code paths surface a clear error instead + // of crashing with "cannot read property sendRequest of undefined". + return { + sendRequest() { + return Promise.reject( + new Error("TypeScript LSP operations are not supported in lightweight diagnostic mode"), + ) + }, + sendNotification() { + return Promise.resolve() + }, + } + }, + notify: { + async open(_input: { path: string }) { + // No-op. Warm-up calls from read.ts (touchFile(path, false)) + // trigger notify.open() but should NOT spawn tsgo. The actual + // check is deferred to waitForDiagnostics() which is only + // called when tools need diagnostics (write, edit, apply_patch). + }, + }, + get diagnostics() { + return diagnostics + }, + async waitForDiagnostics(_input: { path: string }) { + // Run tsgo --noEmit and wait for results. Coalesces concurrent calls. + // 30s cap matches the process timeout in TsCheck.run(). Silent catch + // matches the real LSPClient's .catch(() => {}) on its 3s timeout. + await withTimeout(check(), 30_000).catch(() => {}) + }, + async shutdown() { + log.info("shutting down ts-client") + diagnostics.clear() + }, + } + + log.info("created lightweight ts client", { root: input.root }) + return client + } +} diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index 8c621212fe3..c5a13b37d27 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -10,6 +10,7 @@ import { Config } from "../config/config" import { spawn } from "child_process" import { Instance } from "../project/instance" import { Flag } from "@/flag/flag" +import { TsClient } from "../kilocode/ts-client" // kilocode_change export namespace LSP { const log = Log.create({ service: "lsp" }) @@ -235,6 +236,16 @@ export namespace LSP { continue } + // kilocode_change start - use lightweight tsgo-based client when persistent LSP is not enabled + if (server.id === "typescript" && !Flag.KILO_EXPERIMENTAL_LSP_TOOL) { + const client = TsClient.create({ root }) + s.clients.push(client) + result.push(client) + Bus.publish(Event.Updated, {}) + continue + } + // kilocode_change end + const inflight = s.spawning.get(root + server.id) if (inflight) { const client = await inflight @@ -253,6 +264,16 @@ export namespace LSP { }) const client = await task + // kilocode_change start - fallback to lightweight client when tsgo LSP spawn fails + if (!client && server.id === "typescript") { + s.broken.delete(root + server.id) + const fallback = TsClient.create({ root }) + s.clients.push(fallback) + result.push(fallback) + Bus.publish(Event.Updated, {}) + continue + } + // kilocode_change end if (!client) continue result.push(client) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index e63dabc656a..8b4fa1b0fa9 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -10,6 +10,7 @@ import fs from "fs/promises" import { Filesystem } from "../util/filesystem" import { Instance } from "../project/instance" import { Flag } from "../flag/flag" +import { TsCheck } from "../kilocode/ts-check" // kilocode_change import { Archive } from "../util/archive" import { Process } from "../util/process" import { which } from "../util/which" @@ -99,6 +100,11 @@ export namespace LSPServer { }, } + // kilocode_change start - tsgo native LSP or lightweight diagnostic client + // When KILO_EXPERIMENTAL_LSP_TOOL is enabled, spawn tsgo --lsp --stdio as a + // persistent LSP server (full diagnostics, hover, go-to-definition, etc.). + // Otherwise spawn() returns undefined and getClients() in index.ts falls + // through to the lightweight TsClient that shells out to tsgo --noEmit on demand. export const Typescript: Info = { id: "typescript", root: NearestRoot( @@ -107,26 +113,19 @@ export namespace LSPServer { ), extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"], async spawn(root) { - const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {}) - log.info("typescript server", { tsserver }) - if (!tsserver) return - const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], { - cwd: root, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - }) + if (!Flag.KILO_EXPERIMENTAL_LSP_TOOL) return undefined + const bin = await TsCheck.native_tsgo(root) + if (!bin) { + log.info("tsgo native binary not found, falling back to lightweight client") + return undefined + } + log.info("spawning tsgo --lsp", { bin, root }) return { - process: proc, - initialization: { - tsserver: { - path: tsserver, - }, - }, + process: spawn(bin, ["--lsp", "--stdio"], { cwd: root }), } }, } + // kilocode_change end export const Vue: Info = { id: "vue", diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 86de010dbc5..bf96d6a3501 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -65,6 +65,52 @@ export namespace PermissionNext { return rulesets.flat() } + // kilocode_change start — inverse of fromConfig: convert rules back to config format + /** + * Permissions typed as PermissionAction in the config schema (scalar-only). + * These must be serialized as "allow"/"deny"/"ask", not as { "*": "allow" }. + */ + const SCALAR_ONLY_PERMISSIONS = new Set([ + "todowrite", + "todoread", + "question", + "webfetch", + "websearch", + "codesearch", + "doom_loop", + ]) + + export function toConfig(rules: Ruleset): Config.Permission { + const result: Config.Permission = {} + for (const rule of rules) { + const existing = result[rule.permission] + + // Scalar-only permissions (e.g. websearch, todowrite, doom_loop) only + // accept PermissionAction ("allow"/"deny"/"ask"), not object form. + // Use scalar format for "*"; skip non-wildcard patterns (they can't be + // represented in the config schema — they only work in-memory). + if (SCALAR_ONLY_PERMISSIONS.has(rule.permission)) { + if (rule.pattern === "*") result[rule.permission] = rule.action + continue + } + + if (existing === undefined) { + // Use object format to avoid replacing existing granular rules + // when merged via updateGlobal (e.g. { read: "allow" } would wipe + // { read: { "*": "ask", "src/*": "allow" } }) + result[rule.permission] = { [rule.pattern]: rule.action } + continue + } + if (typeof existing === "string") { + result[rule.permission] = { "*": existing, [rule.pattern]: rule.action } + continue + } + existing[rule.pattern] = rule.action + } + return result + } + // kilocode_change end + export const Request = z .object({ id: Identifier.schema("permission"), @@ -172,14 +218,22 @@ export namespace PermissionNext { const existing = s.pending[input.requestID] if (!existing) throw new NotFoundError({ message: `Permission request ${input.requestID} not found` }) - const validRules = new Set(existing.info.metadata?.rules ?? []) + // Combine metadata.rules (bash hierarchy) and always (all tools). + // Set preserves insertion order and deduplicates. + const validRules = new Set([...(existing.info.metadata?.rules ?? []), ...existing.info.always]) const permission = existing.info.permission - for (const pattern of input.approvedAlways ?? []) { - if (validRules.has(pattern)) s.approved.push({ permission, pattern, action: "allow" }) + const approvedSet = new Set(input.approvedAlways ?? []) + const deniedSet = new Set(input.deniedAlways ?? []) + const newRules: Ruleset = [] + for (const pattern of validRules) { + if (approvedSet.has(pattern)) newRules.push({ permission, pattern, action: "allow" }) + if (deniedSet.has(pattern)) newRules.push({ permission, pattern, action: "deny" }) } - for (const pattern of input.deniedAlways ?? []) { - if (validRules.has(pattern)) s.approved.push({ permission, pattern, action: "deny" }) + s.approved.push(...newRules) + + if (newRules.length > 0) { + await Config.updateGlobal({ permission: toConfig(newRules) }) } }, ) @@ -254,6 +308,16 @@ export namespace PermissionNext { // UI to manage it // db().insert(PermissionTable).values({ projectID: Instance.project.id, data: s.approved }) // .onConflictDoUpdate({ target: PermissionTable.projectID, set: { data: s.approved } }).run() + // kilocode_change start - persist always rules to global config + const alwaysRules: Ruleset = existing.info.always.map((pattern) => ({ + permission: existing.info.permission, + pattern, + action: "allow" as const, + })) + if (alwaysRules.length > 0) { + await Config.updateGlobal({ permission: toConfig(alwaysRules) }) + } + // kilocode_change end return } }, diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index 4d019f6a7ee..f786c643cc9 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -171,6 +171,7 @@ export const GlobalRoutes = lazy(() => }, }), async (c) => { + Config.global.reset() // kilocode_change - reset cached global config so re-init reads fresh data from disk await Instance.disposeAll() GlobalBus.emit("event", { directory: "global", diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index b21981d9ae5..38292e107c4 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -526,6 +526,7 @@ export namespace Server { return c.json(await LSP.status()) }, ) + .get( "/formatter", describeRoute({ diff --git a/packages/opencode/test/permission/next.always-rules.test.ts b/packages/opencode/test/permission/next.always-rules.test.ts index f6a3d8e494a..01c86340889 100644 --- a/packages/opencode/test/permission/next.always-rules.test.ts +++ b/packages/opencode/test/permission/next.always-rules.test.ts @@ -82,7 +82,7 @@ describe("saveAlwaysRules", () => { }) }) - test("ignores patterns not in metadata.rules", async () => { + test("ignores patterns not in metadata.rules or always", async () => { await using tmp = await tmpdir({ git: true }) await Instance.provide({ directory: tmp.path, @@ -97,7 +97,7 @@ describe("saveAlwaysRules", () => { ruleset: [], }) - // "curl" is not in metadata.rules — should be silently ignored + // "curl" is not in metadata.rules or always — should be silently ignored await PermissionNext.saveAlwaysRules({ requestID: "permission_3", approvedAlways: ["npm install", "curl http://evil.com"], @@ -133,6 +133,40 @@ describe("saveAlwaysRules", () => { }) }) + test("accepts patterns from always array (non-bash tools)", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const askPromise = PermissionNext.ask({ + id: "permission_nonbash", + sessionID: "session_test", + permission: "read", + patterns: ["src/main.ts"], + metadata: {}, + always: ["*"], + ruleset: [], + }) + + // "*" is in always — should be accepted even without metadata.rules + await PermissionNext.saveAlwaysRules({ requestID: "permission_nonbash", approvedAlways: ["*"] }) + await PermissionNext.reply({ requestID: "permission_nonbash", reply: "once" }) + await expect(askPromise).resolves.toBeUndefined() + + // "*" wildcard should auto-allow any read + const result = await PermissionNext.ask({ + sessionID: "session_test", + permission: "read", + patterns: ["any/file.ts"], + metadata: {}, + always: [], + ruleset: [], + }) + expect(result).toBeUndefined() + }, + }) + }) + test("accepts hierarchy patterns from metadata.rules", async () => { await using tmp = await tmpdir({ git: true }) await Instance.provide({ @@ -166,4 +200,132 @@ describe("saveAlwaysRules", () => { }, }) }) + + test("mixed allow/deny preserves metadata.rules order", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const askPromise = PermissionNext.ask({ + id: "permission_5", + sessionID: "session_test", + permission: "bash", + patterns: ["npm install lodash"], + // rules ordered broad → specific + metadata: { rules: ["npm *", "npm install *"] }, + always: ["npm install *"], + ruleset: [], + }) + + // Deny broad, allow specific — specific should win + await PermissionNext.saveAlwaysRules({ + requestID: "permission_5", + approvedAlways: ["npm install *"], + deniedAlways: ["npm *"], + }) + await PermissionNext.reply({ requestID: "permission_5", reply: "once" }) + await expect(askPromise).resolves.toBeUndefined() + + // "npm install foo" matches both rules; "npm install *" (allow) comes + // after "npm *" (deny) in metadata.rules order, so allow wins + const result = await PermissionNext.ask({ + sessionID: "session_test", + permission: "bash", + patterns: ["npm install foo"], + metadata: {}, + always: [], + ruleset: [], + }) + expect(result).toBeUndefined() + }, + }) + }) + + test("deny broad + allow specific: specific allow wins", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const askPromise = PermissionNext.ask({ + id: "permission_6", + sessionID: "session_test", + permission: "bash", + patterns: ["git log --oneline"], + metadata: { rules: ["git *", "git log *"] }, + always: ["git log *"], + ruleset: [], + }) + + await PermissionNext.saveAlwaysRules({ + requestID: "permission_6", + approvedAlways: ["git log *"], + deniedAlways: ["git *"], + }) + await PermissionNext.reply({ requestID: "permission_6", reply: "once" }) + await expect(askPromise).resolves.toBeUndefined() + + // "git log --oneline" should be allowed (specific allow after broad deny) + const allowed = await PermissionNext.ask({ + sessionID: "session_test", + permission: "bash", + patterns: ["git log --oneline"], + metadata: {}, + always: [], + ruleset: [], + }) + expect(allowed).toBeUndefined() + + // "git status" should be denied (only matches broad deny) + await expect( + PermissionNext.ask({ + sessionID: "session_test", + permission: "bash", + patterns: ["git status"], + metadata: {}, + always: [], + ruleset: [], + }), + ).rejects.toBeInstanceOf(PermissionNext.DeniedError) + }, + }) + }) + + test("rules not in metadata.rules are silently ignored", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const askPromise = PermissionNext.ask({ + id: "permission_7", + sessionID: "session_test", + permission: "bash", + patterns: ["npm install"], + metadata: { rules: ["npm *"] }, + always: ["npm *"], + ruleset: [], + }) + + // "curl" is not in metadata.rules — should be silently ignored + await PermissionNext.saveAlwaysRules({ + requestID: "permission_7", + approvedAlways: ["npm *", "curl *"], + }) + await PermissionNext.reply({ requestID: "permission_7", reply: "once" }) + await expect(askPromise).resolves.toBeUndefined() + + // curl should still require permission (not auto-allowed) + const curlPromise = PermissionNext.ask({ + id: "permission_curl2", + sessionID: "session_test", + permission: "bash", + patterns: ["curl http://example.com"], + metadata: {}, + always: [], + ruleset: [], + }) + await PermissionNext.reply({ requestID: "permission_curl2", reply: "reject" }) + await expect(curlPromise).rejects.toBeInstanceOf(PermissionNext.RejectedError) + }, + }) + }) }) diff --git a/packages/opencode/test/permission/next.toConfig.test.ts b/packages/opencode/test/permission/next.toConfig.test.ts new file mode 100644 index 00000000000..e4c1bac79b4 --- /dev/null +++ b/packages/opencode/test/permission/next.toConfig.test.ts @@ -0,0 +1,88 @@ +import { test, expect } from "bun:test" +import { PermissionNext } from "../../src/permission/next" + +// toConfig tests (inverse of fromConfig) + +test("toConfig - single wildcard rule uses object format", () => { + const result = PermissionNext.toConfig([{ permission: "read", pattern: "*", action: "allow" }]) + expect(result).toEqual({ read: { "*": "allow" } }) +}) + +test("toConfig - single non-wildcard rule uses object format", () => { + const result = PermissionNext.toConfig([{ permission: "bash", pattern: "npm *", action: "allow" }]) + expect(result).toEqual({ bash: { "npm *": "allow" } }) +}) + +test("toConfig - multiple rules for same permission use object format", () => { + const result = PermissionNext.toConfig([ + { permission: "bash", pattern: "*", action: "ask" }, + { permission: "bash", pattern: "npm *", action: "allow" }, + ]) + expect(result).toEqual({ bash: { "*": "ask", "npm *": "allow" } }) +}) + +test("toConfig - mixed permissions", () => { + const result = PermissionNext.toConfig([ + { permission: "read", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "npm *", action: "allow" }, + { permission: "bash", pattern: "git *", action: "allow" }, + ]) + expect(result).toEqual({ + read: { "*": "allow" }, + bash: { "npm *": "allow", "git *": "allow" }, + }) +}) + +test("toConfig - empty rules returns empty object", () => { + const result = PermissionNext.toConfig([]) + expect(result).toEqual({}) +}) + +test("toConfig - wildcard then specific promotes to object", () => { + const result = PermissionNext.toConfig([ + { permission: "bash", pattern: "*", action: "ask" }, + { permission: "bash", pattern: "rm *", action: "deny" }, + ]) + expect(result).toEqual({ bash: { "*": "ask", "rm *": "deny" } }) +}) + +test("toConfig - roundtrip with fromConfig (simple) always uses object format", () => { + const config = { read: "allow" as const, bash: "ask" as const } + const rules = PermissionNext.fromConfig(config) + const result = PermissionNext.toConfig(rules) + // toConfig always uses object format to avoid erasing existing granular rules on merge + expect(result).toEqual({ read: { "*": "allow" }, bash: { "*": "ask" } }) +}) + +test("toConfig - roundtrip with fromConfig (object)", () => { + const config = { bash: { "*": "ask" as const, "npm *": "allow" as const, "git *": "allow" as const } } + const rules = PermissionNext.fromConfig(config) + const result = PermissionNext.toConfig(rules) + expect(result).toEqual(config) +}) + +test("toConfig - scalar-only permission uses scalar format", () => { + const result = PermissionNext.toConfig([{ permission: "websearch", pattern: "*", action: "allow" }]) + expect(result).toEqual({ websearch: "allow" }) +}) + +test("toConfig - scalar-only permission with non-wildcard pattern is skipped", () => { + // doom_loop uses always: [toolName], so pattern can be "bash" etc. + // Non-wildcard patterns for scalar-only permissions can't be represented + // in the config schema — they only work in-memory (known limitation). + const result = PermissionNext.toConfig([{ permission: "doom_loop", pattern: "bash", action: "allow" }]) + expect(result).toEqual({}) +}) + +test("toConfig - mixed scalar-only and rule-capable permissions", () => { + const result = PermissionNext.toConfig([ + { permission: "websearch", pattern: "*", action: "allow" }, + { permission: "todowrite", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "npm *", action: "allow" }, + ]) + expect(result).toEqual({ + websearch: "allow", + todowrite: "allow", + bash: { "npm *": "allow" }, + }) +}) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 76b1e2a58d2..95e9d47ac36 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/plugin", - "version": "7.0.48", + "version": "7.0.49", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/plugin/tsconfig.json b/packages/plugin/tsconfig.json index 58072c81c96..4d6d87dec9a 100644 --- a/packages/plugin/tsconfig.json +++ b/packages/plugin/tsconfig.json @@ -6,7 +6,8 @@ "module": "preserve", "declaration": true, "moduleResolution": "bundler", - "lib": ["es2022", "dom", "dom.iterable"] + "lib": ["es2022", "dom", "dom.iterable"], + "types": ["node"] }, "include": ["src"] } diff --git a/packages/script/package.json b/packages/script/package.json index 4de96455e64..be4c7b7bc4f 100644 --- a/packages/script/package.json +++ b/packages/script/package.json @@ -8,7 +8,7 @@ "exports": { ".": "./src/index.ts" }, - "version": "7.0.48", + "version": "7.0.49", "dependencies": {}, "peerDependencies": {} } diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 00bea503e99..cd5560aa5fd 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/sdk", - "version": "7.0.48", + "version": "7.0.49", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/tsconfig.json b/packages/sdk/js/tsconfig.json index 3ab5fcb7689..54e6b62d0b2 100644 --- a/packages/sdk/js/tsconfig.json +++ b/packages/sdk/js/tsconfig.json @@ -7,6 +7,7 @@ "declaration": true, "moduleResolution": "nodenext", "lib": ["es2022", "dom", "dom.iterable"], + "types": ["node"], "composite": true, "rootDir": "src" }, diff --git a/packages/storybook/package.json b/packages/storybook/package.json index ea8e4f3245a..123a9a185ed 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -26,7 +26,7 @@ "typescript": "catalog:", "vite": "catalog:" }, - "version": "7.0.48", + "version": "7.0.49", "dependencies": {}, "peerDependencies": {} } diff --git a/packages/ui/package.json b/packages/ui/package.json index a83c6cec900..b9c3a80a08f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "7.0.48", + "version": "7.0.49", "type": "module", "license": "MIT", "exports": { diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 53fa77841f8..77a3e40af46 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -673,6 +673,7 @@ export function UserMessageDisplay(props: { interrupted?: boolean animate?: boolean queued?: boolean + onRevert?: () => void }) { const data = useData() const dialog = useDialog() diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index f4e44caa776..ec49757aced 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -121,7 +121,6 @@ export const dict = { "ui.permission.deny": "رفض", "ui.permission.allowAlways": "السماح دائمًا", "ui.permission.allowOnce": "السماح مرة واحدة", - "ui.permission.sessionHint": '"السماح دائمًا" ينطبق على هذه الجلسة فقط. استخدم الإعدادات للأذونات العامة.', "ui.message.expand": "توسيع الرسالة", "ui.message.collapse": "طي الرسالة", @@ -129,6 +128,7 @@ export const dict = { "ui.message.copyMessage": "نسخ الرسالة", "ui.message.copyResponse": "نسخ الرد", "ui.message.copied": "تم النسخ!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "تمت المقاطعة", "ui.message.queued": "في الانتظار", "ui.message.attachment.alt": "مرفق", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 5a90870e67c..76552e301de 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -121,8 +121,6 @@ export const dict = { "ui.permission.deny": "Negar", "ui.permission.allowAlways": "Permitir sempre", "ui.permission.allowOnce": "Permitir uma vez", - "ui.permission.sessionHint": - '"Permitir sempre" aplica-se apenas a esta sessão. Use as configurações para permissões globais.', "ui.message.expand": "Expandir mensagem", "ui.message.collapse": "Recolher mensagem", @@ -130,6 +128,7 @@ export const dict = { "ui.message.copyMessage": "Copiar mensagem", "ui.message.copyResponse": "Copiar resposta", "ui.message.copied": "Copiado!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Interrompido", "ui.message.queued": "Na fila", "ui.message.attachment.alt": "anexo", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 54f646184a5..4570b6b5a09 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -125,8 +125,6 @@ export const dict = { "ui.permission.deny": "Zabrani", "ui.permission.allowAlways": "Uvijek dozvoli", "ui.permission.allowOnce": "Dozvoli jednom", - "ui.permission.sessionHint": - '"Uvijek dozvoli" primjenjuje se samo na ovu sesiju. Koristite postavke za globalne dozvole.', "ui.message.expand": "Proširi poruku", "ui.message.collapse": "Sažmi poruku", @@ -134,6 +132,7 @@ export const dict = { "ui.message.copyMessage": "Kopiraj poruku", "ui.message.copyResponse": "Kopiraj odgovor", "ui.message.copied": "Kopirano!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Prekinuto", "ui.message.queued": "U redu", "ui.message.attachment.alt": "prilog", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index a85aa6aff56..3aa9460c8e6 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -120,8 +120,6 @@ export const dict = { "ui.permission.deny": "Afvis", "ui.permission.allowAlways": "Tillad altid", "ui.permission.allowOnce": "Tillad én gang", - "ui.permission.sessionHint": - '"Tillad altid" gælder kun for denne session. Brug indstillinger til globale tilladelser.', "ui.message.expand": "Udvid besked", "ui.message.collapse": "Skjul besked", @@ -129,6 +127,7 @@ export const dict = { "ui.message.copyMessage": "Kopier besked", "ui.message.copyResponse": "Kopier svar", "ui.message.copied": "Kopieret!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Afbrudt", "ui.message.queued": "I kø", "ui.message.attachment.alt": "vedhæftning", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index 8adbbab35c1..3756b3a9d33 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -126,8 +126,6 @@ export const dict = { "ui.permission.deny": "Verweigern", "ui.permission.allowAlways": "Immer erlauben", "ui.permission.allowOnce": "Einmal erlauben", - "ui.permission.sessionHint": - '"Immer erlauben" gilt nur für diese Sitzung. Globale Berechtigungen in den Einstellungen ändern.', "ui.message.expand": "Nachricht erweitern", "ui.message.collapse": "Nachricht reduzieren", @@ -135,6 +133,7 @@ export const dict = { "ui.message.copyMessage": "Nachricht kopieren", "ui.message.copyResponse": "Antwort kopieren", "ui.message.copied": "Kopiert!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Unterbrochen", "ui.message.queued": "In Warteschlange", "ui.message.attachment.alt": "Anhang", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index dbfac689cff..139b28a36ea 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -122,7 +122,6 @@ export const dict: Record = { "ui.permission.deny": "Deny", "ui.permission.allowAlways": "Allow always", "ui.permission.allowOnce": "Allow once", - "ui.permission.sessionHint": '"Allow always" applies to this session only. Use settings for global permissions.', "ui.message.expand": "Expand message", "ui.message.collapse": "Collapse message", @@ -130,6 +129,7 @@ export const dict: Record = { "ui.message.copyMessage": "Copy message", "ui.message.copyResponse": "Copy response", "ui.message.copied": "Copied", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Interrupted", "ui.message.queued": "Queued", "ui.message.attachment.alt": "attachment", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index b2a9f07e791..25e8afff770 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -121,8 +121,6 @@ export const dict = { "ui.permission.deny": "Denegar", "ui.permission.allowAlways": "Permitir siempre", "ui.permission.allowOnce": "Permitir una vez", - "ui.permission.sessionHint": - '"Permitir siempre" se aplica solo a esta sesión. Usa la configuración para permisos globales.', "ui.message.expand": "Expandir mensaje", "ui.message.collapse": "Colapsar mensaje", @@ -130,6 +128,7 @@ export const dict = { "ui.message.copyMessage": "Copiar mensaje", "ui.message.copyResponse": "Copiar respuesta", "ui.message.copied": "¡Copiado!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Interrumpido", "ui.message.queued": "En cola", "ui.message.attachment.alt": "adjunto", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 3840a2d742f..bb730a77d70 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -121,8 +121,6 @@ export const dict = { "ui.permission.deny": "Refuser", "ui.permission.allowAlways": "Toujours autoriser", "ui.permission.allowOnce": "Autoriser une fois", - "ui.permission.sessionHint": - '"Toujours autoriser" s\'applique uniquement à cette session. Utilisez les paramètres pour les autorisations globales.', "ui.message.expand": "Développer le message", "ui.message.collapse": "Réduire le message", @@ -130,6 +128,7 @@ export const dict = { "ui.message.copyMessage": "Copier le message", "ui.message.copyResponse": "Copier la réponse", "ui.message.copied": "Copié !", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Interrompu", "ui.message.queued": "En file", "ui.message.attachment.alt": "pièce jointe", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 28ba3ba1f88..cf416dd2ea1 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -120,8 +120,6 @@ export const dict = { "ui.permission.deny": "拒否", "ui.permission.allowAlways": "常に許可", "ui.permission.allowOnce": "今回のみ許可", - "ui.permission.sessionHint": - "「常に許可」はこのセッションにのみ適用されます。グローバルな権限は設定で変更してください。", "ui.message.expand": "メッセージを展開", "ui.message.collapse": "メッセージを折りたたむ", @@ -129,6 +127,7 @@ export const dict = { "ui.message.copyMessage": "メッセージをコピー", "ui.message.copyResponse": "応答をコピー", "ui.message.copied": "コピーしました!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "中断", "ui.message.queued": "待機中", "ui.message.attachment.alt": "添付ファイル", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index 58ec61fc95f..ca7c780647c 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -121,7 +121,6 @@ export const dict = { "ui.permission.deny": "거부", "ui.permission.allowAlways": "항상 허용", "ui.permission.allowOnce": "한 번만 허용", - "ui.permission.sessionHint": '"항상 허용"은 현재 세션에만 적용됩니다. 전역 권한은 설정에서 변경하세요.', "ui.message.expand": "메시지 펼치기", "ui.message.collapse": "메시지 접기", @@ -129,6 +128,7 @@ export const dict = { "ui.message.copyMessage": "메시지 복사", "ui.message.copyResponse": "응답 복사", "ui.message.copied": "복사됨!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "중단됨", "ui.message.queued": "대기 중", "ui.message.attachment.alt": "첨부 파일", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 1399e4bd925..bf4d612970b 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -124,8 +124,6 @@ export const dict: Record = { "ui.permission.deny": "Avslå", "ui.permission.allowAlways": "Tillat alltid", "ui.permission.allowOnce": "Tillat én gang", - "ui.permission.sessionHint": - '"Tillat alltid" gjelder bare for denne økten. Bruk innstillinger for globale tillatelser.', "ui.message.expand": "Utvid melding", "ui.message.collapse": "Skjul melding", @@ -133,6 +131,7 @@ export const dict: Record = { "ui.message.copyMessage": "Kopier melding", "ui.message.copyResponse": "Kopier svar", "ui.message.copied": "Kopiert!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Avbrutt", "ui.message.queued": "I kø", "ui.message.attachment.alt": "vedlegg", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index a6ef98bf854..c65ef51abad 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -120,8 +120,6 @@ export const dict = { "ui.permission.deny": "Odmów", "ui.permission.allowAlways": "Zezwalaj zawsze", "ui.permission.allowOnce": "Zezwól raz", - "ui.permission.sessionHint": - '"Zezwalaj zawsze" dotyczy tylko tej sesji. Użyj ustawień, aby zmienić globalne uprawnienia.', "ui.message.expand": "Rozwiń wiadomość", "ui.message.collapse": "Zwiń wiadomość", @@ -129,6 +127,7 @@ export const dict = { "ui.message.copyMessage": "Kopiuj wiadomość", "ui.message.copyResponse": "Kopiuj odpowiedź", "ui.message.copied": "Skopiowano!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Przerwano", "ui.message.queued": "W kolejce", "ui.message.attachment.alt": "załącznik", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 63c52029a24..b27040faa37 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -120,8 +120,6 @@ export const dict = { "ui.permission.deny": "Запретить", "ui.permission.allowAlways": "Разрешить всегда", "ui.permission.allowOnce": "Разрешить один раз", - "ui.permission.sessionHint": - "«Разрешить всегда» применяется только к текущей сессии. Для глобальных разрешений используйте настройки.", "ui.message.expand": "Развернуть сообщение", "ui.message.collapse": "Свернуть сообщение", @@ -129,6 +127,7 @@ export const dict = { "ui.message.copyMessage": "Копировать сообщение", "ui.message.copyResponse": "Копировать ответ", "ui.message.copied": "Скопировано!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Прервано", "ui.message.queued": "В очереди", "ui.message.attachment.alt": "вложение", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index b57f789240b..58c0436f3f1 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -122,7 +122,6 @@ export const dict = { "ui.permission.deny": "ปฏิเสธ", "ui.permission.allowAlways": "อนุญาตเสมอ", "ui.permission.allowOnce": "อนุญาตครั้งเดียว", - "ui.permission.sessionHint": '"อนุญาตเสมอ" ใช้ได้เฉพาะในเซสชันนี้เท่านั้น ใช้การตั้งค่าสำหรับสิทธิ์ส่วนกลาง', "ui.message.expand": "ขยายข้อความ", "ui.message.collapse": "ย่อข้อความ", @@ -130,6 +129,7 @@ export const dict = { "ui.message.copyMessage": "คัดลอกข้อความ", "ui.message.copyResponse": "คัดลอกคำตอบ", "ui.message.copied": "คัดลอกแล้ว!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "ถูกขัดจังหวะ", "ui.message.queued": "อยู่ในคิว", "ui.message.attachment.alt": "ไฟล์แนบ", diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts index 7987ede1fb1..d5d5642341b 100644 --- a/packages/ui/src/i18n/tr.ts +++ b/packages/ui/src/i18n/tr.ts @@ -134,6 +134,7 @@ export const dict = { "ui.message.copyMessage": "Mesajı kopyala", "ui.message.copyResponse": "Yanıtı kopyala", "ui.message.copied": "Kopyalandı", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "Kesildi", "ui.message.queued": "Sırada", "ui.message.attachment.alt": "ek", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 86cc1f44f90..ba1327af99c 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -125,7 +125,6 @@ export const dict = { "ui.permission.deny": "拒绝", "ui.permission.allowAlways": "始终允许", "ui.permission.allowOnce": "允许一次", - "ui.permission.sessionHint": '"始终允许" 仅适用于本次会话。如需全局权限设置,请使用设置。', "ui.message.expand": "展开消息", "ui.message.collapse": "收起消息", @@ -133,6 +132,7 @@ export const dict = { "ui.message.copyMessage": "复制消息", "ui.message.copyResponse": "复制回复", "ui.message.copied": "已复制!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "已中断", "ui.message.queued": "排队中", "ui.message.attachment.alt": "附件", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index b8ef55239dd..ed1397b1a94 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -125,7 +125,6 @@ export const dict = { "ui.permission.deny": "拒絕", "ui.permission.allowAlways": "永遠允許", "ui.permission.allowOnce": "允許一次", - "ui.permission.sessionHint": '"永遠允許" 僅適用於此工作階段。如需全域權限設定,請使用設定。', "ui.message.expand": "展開訊息", "ui.message.collapse": "收合訊息", @@ -133,6 +132,7 @@ export const dict = { "ui.message.copyMessage": "複製訊息", "ui.message.copyResponse": "複製回覆", "ui.message.copied": "已複製!", + "ui.message.revert": "Revert to here", "ui.message.interrupted": "已中斷", "ui.message.queued": "排隊中", "ui.message.attachment.alt": "附件", diff --git a/packages/util/package.json b/packages/util/package.json index a7bd318b887..9e6743d2c9b 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "7.0.48", + "version": "7.0.49", "private": true, "type": "module", "license": "MIT", diff --git a/script/publish.ts b/script/publish.ts index e27ad59f784..8b99c918412 100755 --- a/script/publish.ts +++ b/script/publish.ts @@ -84,5 +84,10 @@ await import(`../packages/sdk/js/script/publish.ts`) console.log("\n=== plugin ===\n") await import(`../packages/plugin/script/publish.ts`) +// kilocode_change start +console.log("\n=== vscode ===\n") +await import(`../packages/kilo-vscode/script/publish.ts`) +// kilocode_change end + const dir = fileURLToPath(new URL("..", import.meta.url)) process.chdir(dir) diff --git a/script/upstream/package.json b/script/upstream/package.json index 1b4f06241fe..8d6d1013f4a 100644 --- a/script/upstream/package.json +++ b/script/upstream/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/upstream-merge", - "version": "7.0.48", + "version": "7.0.49", "private": true, "type": "module", "description": "Scripts for automating upstream opencode merges into Kilo", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 50a5ac1b771..e709acfcadc 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "7.0.48", + "version": "7.0.49", "publisher": "sst-dev", "repository": { "type": "git",