mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 14:07:20 +08:00
Merge branch 'main' into catrielmuller/kilo-opencode-v1.2.21
This commit is contained in:
@@ -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=="],
|
||||
|
||||
|
||||
+2
-2
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "7.0.48",
|
||||
"version": "7.0.49",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "node_modules/.ts-dist",
|
||||
"isolatedModules": true,
|
||||
"types": ["bun", "node"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "7.0.48",
|
||||
"version": "7.0.49",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "تجاهل",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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": "閉じる",
|
||||
}
|
||||
|
||||
@@ -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": "닫기",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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ć",
|
||||
}
|
||||
|
||||
@@ -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": "Закрыть",
|
||||
}
|
||||
|
||||
@@ -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": "ปิด",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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": "关闭",
|
||||
}
|
||||
|
||||
@@ -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": "關閉",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"types": ["bun"],
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedIndexedAccess": false
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.0.48",
|
||||
"version": "7.0.49",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"] {
|
||||
|
||||
@@ -1206,7 +1206,7 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp
|
||||
return (
|
||||
<Show when={throttledText()}>
|
||||
<div data-component="reasoning-part">
|
||||
<Collapsible open={open()} onOpenChange={setOpen}>
|
||||
<Collapsible open={open()} onOpenChange={setOpen} class="tool-collapsible">
|
||||
<Collapsible.Trigger>
|
||||
<div data-slot="reasoning-header">
|
||||
<Icon name="brain" size="small" />
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cf93ca79d15deab7dc52473f68305b521c62d10e968a9258e09609ad9c04fde6
|
||||
size 15837
|
||||
oid sha256:67cb95242834a2ac3633b70a68a8ff9a9f343938092f06926a44639ba5c01121
|
||||
size 15968
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14bf991adcbc6fcbf53f4cff2275dcec255943b5545c624231aeb73529ca2d6e
|
||||
size 32478
|
||||
oid sha256:6d93bf6465c33895aa54bc2479315ea2fba38d06488f3d7cdf868b60caaefd9e
|
||||
size 32623
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<T extends { path: string }>(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, "")
|
||||
}
|
||||
@@ -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<string, unknown>
|
||||
message: string
|
||||
tool?: { messageID: string; callID: string }
|
||||
@@ -174,6 +180,7 @@ export type WebviewMessage =
|
||||
| { type: "permissionError"; permissionID: string }
|
||||
| { type: "sessionCreated"; session: ReturnType<typeof sessionToWebview> }
|
||||
| { type: "sessionUpdated"; session: ReturnType<typeof sessionToWebview> }
|
||||
| { 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,
|
||||
|
||||
@@ -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<boolean> {
|
||||
this.loaded = true
|
||||
|
||||
if (this.connectionService) {
|
||||
const state = this.connectionService.getConnectionState()
|
||||
return state === "connected"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
public supportsFim(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
+123
-686
@@ -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",
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+3
-6
@@ -63,13 +63,8 @@ vi.mock("vscode", () => {
|
||||
|
||||
vi.mock("../AutocompleteModel", () => {
|
||||
class AutocompleteModel {
|
||||
public loaded = false
|
||||
public profileName = "test-profile"
|
||||
|
||||
public async reload(): Promise<void> {
|
||||
this.loaded = true
|
||||
}
|
||||
|
||||
public getModelName(): string {
|
||||
return "test-model"
|
||||
}
|
||||
@@ -169,7 +164,9 @@ async function createManager(): Promise<AutocompleteServiceManager> {
|
||||
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()
|
||||
|
||||
|
||||
-11
@@ -15,10 +15,6 @@ export class ChatTextAreaAutocomplete {
|
||||
this.telemetry = new AutocompleteTelemetry("chat-textarea")
|
||||
}
|
||||
|
||||
async initialize(): Promise<boolean> {
|
||||
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: "" }
|
||||
|
||||
-1
@@ -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)
|
||||
|
||||
@@ -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": "إجمالي تكلفة الجلسة:",
|
||||
|
||||
@@ -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ó:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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 :",
|
||||
|
||||
@@ -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": "सत्र की कुल लागत:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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": "セッション合計コスト:",
|
||||
|
||||
@@ -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": "세션 총 비용:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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": "Общая стоимость сессии:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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": "ค่าใช้จ่ายรวมของเซสชัน:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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": "Загальна вартість сесії:",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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": "会话总费用:",
|
||||
|
||||
@@ -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": "工作階段總費用:",
|
||||
|
||||
@@ -137,7 +137,6 @@ export interface AutocompleteStatusBarStateProps {
|
||||
model?: string
|
||||
provider?: string
|
||||
profileName?: string | null
|
||||
hasKilocodeProfileWithNoBalance?: boolean
|
||||
hasNoUsableProvider?: boolean
|
||||
totalSessionCost: number
|
||||
completionCount: number
|
||||
|
||||
@@ -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<InstallResult> {
|
||||
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<InstallResult> {
|
||||
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<string, unknown>): Record<string, unknown> {
|
||||
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<InstallResult> {
|
||||
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<RemoveResult> {
|
||||
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<RemoveResult> {
|
||||
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<RemoveResult> {
|
||||
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<Record<string, Record<string, unknown>>> {
|
||||
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<string, unknown>,
|
||||
): Promise<void> {
|
||||
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<string, string> = {
|
||||
read: "read",
|
||||
edit: "edit",
|
||||
browser: "bash",
|
||||
command: "bash",
|
||||
mcp: "mcp",
|
||||
}
|
||||
const ALL_PERMISSIONS = ["read", "edit", "bash", "mcp"]
|
||||
|
||||
function convertModeToAgent(content: string): Record<string, unknown> {
|
||||
const mode = yaml.parse(content) as Record<string, unknown>
|
||||
const groups = (mode.groups ?? []) as Array<string | [string, Record<string, unknown>]>
|
||||
|
||||
const permission: Record<string, unknown> = {}
|
||||
const allowed = new Set<string>()
|
||||
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, unknown>): 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<string[]> {
|
||||
const resolved = path.resolve(dir)
|
||||
const escaped: string[] = []
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"])
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ba76e2d5b993e999914fd0538650728adf78f7ad70978d8ee9982525f6ab5e8
|
||||
size 17626
|
||||
oid sha256:3ef8ec5886054bf0e7ae288c66ce5175204e74728c3e37f3669c551be98755b7
|
||||
size 12476
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:29872fa36a6a6ba9e20765f92380b86f9a91beb9702b3cdb95914ea46a2cf40c
|
||||
size 17833
|
||||
oid sha256:54ff3bd3916245aa1dc65384db4a0b5908b4d78cc3700157112f1be63ce054f9
|
||||
size 12663
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce915e449cdec411c00df613ffc1017f95dba69737e0474fc8c483ea9f284026
|
||||
size 17872
|
||||
oid sha256:9edcfe9f76314110e9c1795ee0008ca35187ce28a19c86cfcf1f8bf499fe151c
|
||||
size 12714
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b39675360da34fc6cb9c1ff4e51afeff79417ca4baa88816967d2b0a4ece858f
|
||||
size 18043
|
||||
oid sha256:4e314513c9b4e964ea3b9a7cdbfc561e57eab2fa867aadfd370d1c0ad68c11b8
|
||||
size 12903
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c3b7d96f123e880b1395d160d17a48f385ef59344326c44d7cd13b342d2ca49e
|
||||
size 24193
|
||||
oid sha256:a3b9925b5424dd81e0fd60dc3cc4307730abdc010cccd73e7243292880dd6b09
|
||||
size 16833
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:739b8eb94e5cde39e4127a6ed3c499392174a380812e2e3497965643cac3dee9
|
||||
size 22265
|
||||
oid sha256:ef1d55223be06fd9d16566bc80a525c84c933a337ee4ccf2c41809e4984571b6
|
||||
size 17276
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:62ef30b05bf2ec47a7ddd02a03f5f1cded5ced9cf9332dd3bd50d6809bcce96d
|
||||
size 18447
|
||||
oid sha256:5fb585654180101c29bd4e7efbc7129bf29e8681bc764cdae0d8233fe302a29e
|
||||
size 12711
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1f9864861136b578c4e46ece7dae5a139ada7e7be3d219462ef3a549b94239ec
|
||||
size 18648
|
||||
oid sha256:c0702dda15977143eeac1557040a583946f2fed4b1610c97a772cff4ae3f2482
|
||||
size 12842
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:514803fa46f43ff58d8d403e630afc5c01a8841ac420d406755515bceb38beae
|
||||
size 23132
|
||||
oid sha256:9784fcf6612167075eb796887c9ac6ad8ab1596ef03dd0ace2b159f82dcb3675
|
||||
size 17978
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:578f6daa54a25fba38b049d623e2b12069b0cbe1c5e094031d8a0b0af0a56f52
|
||||
size 23966
|
||||
oid sha256:606cc8d90900a26682852c9eb14d24305f31672ecd40265954b7b85cca7d3c48
|
||||
size 18850
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:91ba11c0a81ced5dfe824a4c9e8fdcbaebaaddc660ef2b245df56903e180e4b7
|
||||
size 18949
|
||||
oid sha256:ab71bff42d0bd60c84f75cc04d91fdae8516667913152a0ae4e792268368c9d1
|
||||
size 13844
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6f8bc26191620881314eff9ac921abba4e201c5af7ec8d222e405fdee9ddae9a
|
||||
size 17909
|
||||
oid sha256:8d37fd5942ec471c7f534b7b65ac568cdde7cd1b7f3542c50a72500ac3823cae
|
||||
size 12827
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce112e9762168271fd8b7b4316b740008269e9d248a668887adce3dde13478f0
|
||||
size 18362
|
||||
oid sha256:04377b0ddbaa0e2f4f126adff431db902fbff65ce653f2bc59434d150a833578
|
||||
size 15638
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d499d73467c37052d1277deefdd419e370a8239d7e117806d8442b2b9cc851a7
|
||||
size 18886
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string, string> = {
|
||||
"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([])
|
||||
})
|
||||
})
|
||||
@@ -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<string, Record<string, string>> = {
|
||||
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<string, Record<string, string>> = {
|
||||
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<string, Record<string, string>> = {
|
||||
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<string, Record<string, string>> = {
|
||||
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<string[]> {
|
||||
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<Missing[]> {
|
||||
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<Missing[]> {
|
||||
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<Missing[]> {
|
||||
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<string, string>,
|
||||
locales: Record<string, Record<string, string>>,
|
||||
): 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([])
|
||||
})
|
||||
})
|
||||
@@ -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" }])
|
||||
})
|
||||
})
|
||||
@@ -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([])
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:de6be977bd3805fdbbbb1fdaf3498201c1097adea26ebe460c56b681d1828e39
|
||||
size 9457
|
||||
oid sha256:c5ada07e94ff10490732ba4b27d8e204102816aa00e472324a31ee9f8c7e8484
|
||||
size 9470
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3164d510232a0457cf935a3032f736825486f133e89994e544c678ccc7e6849a
|
||||
size 30652
|
||||
oid sha256:0bbec9564c9fa46fce155ada8e176641e540c912461102b7b424d317b3283c38
|
||||
size 31033
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dc40d7bbc5f3eaee3b48b4f90046a10e54db3288e71214eec190e4b0fa61cc16
|
||||
size 22777
|
||||
oid sha256:48ebccc3ab71403efa74803f140b17d97a000989b64dd42c65d8f9774d059cb8
|
||||
size 23139
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3da4a2d29601e45eb4b5fe007a62a203c2b5151b2d74437d22630d2ddc8582a6
|
||||
size 28723
|
||||
oid sha256:371621a81d9c42d1dc327492fb2e3d2516857dfa189adf3c924ac6951d33e910
|
||||
size 29073
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user