diff --git a/.gitattributes b/.gitattributes index 453ae820fe8..d351e0fc83e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,6 @@ +# Enforce LF line endings for all text files +* text=auto eol=lf + # Git LFS tracking for binary/media files *.gif filter=lfs diff=lfs merge=lfs -text *.mp4 filter=lfs diff=lfs merge=lfs -text @@ -5,3 +8,13 @@ # Visual regression baseline snapshots packages/kilo-ui/tests/**/*.png filter=lfs diff=lfs merge=lfs -text packages/kilo-vscode/tests/**/*.png filter=lfs diff=lfs merge=lfs -text + +# Hide non-English localization files by default in GitHub PR diffs. +**/i18n/*.ts linguist-generated=true +**/i18n/en.ts linguist-generated=false +**/i18n/en*.ts linguist-generated=false +**/i18n/package-nls-en.ts linguist-generated=false +**/i18n/index.ts linguist-generated=false +**/i18n/parity.test.ts linguist-generated=false +packages/kilo-i18n/src/*.ts linguist-generated=true +packages/kilo-i18n/src/en.ts linguist-generated=false diff --git a/.kilocode/skills/vscode-visual-regression/SKILL.md b/.kilocode/skills/vscode-visual-regression/SKILL.md index 7b2f3bc0460..35b9eda9659 100644 --- a/.kilocode/skills/vscode-visual-regression/SKILL.md +++ b/.kilocode/skills/vscode-visual-regression/SKILL.md @@ -59,7 +59,7 @@ export const MyStoryName: Story = { name: "MyComponent — description of variant", render: () => ( -
+
@@ -71,7 +71,8 @@ export const MyStoryName: Story = { - **Always start with `/** @jsxImportSource solid-js \*/`\*\* — required for SolidJS JSX compilation. - **Always wrap in ``** — provides all required contexts (VSCode, Server, Config, Provider, Session, I18n, Dialog, Marked, Data, Diff, Code). Without it, components that call `useVSCode()`, `useSession()`, etc. will throw. -- **Set explicit dimensions** on a wrapper div so the screenshot has a deterministic size. Use `width: "420px"` for sidebar-width components (the standard VS Code sidebar). Use `width: "200px"` for narrow/collapsed states. +- **Do NOT set an explicit `width` on the wrapper div.** The Playwright viewport is already 420px wide (or 200px for narrow stories). Setting `width: "420px"` leaves no room for a vertical scrollbar and causes right-side cropping in screenshots. Let the viewport control the width. +- **Use `max-height` not `height` for the wrapper div** when you need to constrain vertical size. A fixed `height` forces a scrollbar even when content is short; `max-height` avoids unnecessary scrollbars that would eat into the available horizontal space. - **Meta `title`** determines the snapshot subdirectory. Use PascalCase or slash-notation (e.g., `"Composite/Webview"`). Playwright transforms it: `"Composite/Webview"` becomes `composite-webview/` in the snapshots folder. - **Export name** determines the story ID. The Storybook ID is `{lowercase-title}--{kebab-export-name}`. For example, `title: "Chat"` + `export const ChatViewIdle` produces ID `chat--chat-view-idle`. - **Snapshot path** is derived automatically: `tests/visual-regression.spec.ts-snapshots/{title-slug}/{variant-slug}.png`. Example: `chat/chat-view-idle-chromium-linux.png`. diff --git a/.opencode/command/issues.md b/.opencode/command/issues.md index d1e37d4a7df..224f22e3035 100644 --- a/.opencode/command/issues.md +++ b/.opencode/command/issues.md @@ -3,7 +3,7 @@ description: "find issue(s) on github" model: opencode/claude-haiku-4-5 --- -Search through existing issues in Kilo-Org/kilo using the gh cli to find issues matching this query: +Search through existing issues in Kilo-Org/kilocode using the gh cli to find issues matching this query: $ARGUMENTS diff --git a/README.md b/README.md index 31d9e5cf348..9aee29c4892 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@

- VS Code Marketplace - X (Twitter) - Substack Blog - Discord - Reddit + VS Code Marketplace + X (Twitter) + Substack Blog + Discord + Reddit

# 🚀 Kilo @@ -84,7 +84,7 @@ For most users: - **Windows (most PCs):** `kilo-windows-x64.zip` - **macOS Apple Silicon:** `kilo-darwin-arm64.zip` - **macOS Intel:** `kilo-darwin-x64.zip` -- **Linux x64:** `kilo-linux-x64.tar,gz` +- **Linux x64:** `kilo-linux-x64.tar.gz` - **Linux on ARM:** `kilo-linux-arm64.tar.gz` ### Autonomous Mode (CI/CD) diff --git a/bun.lock b/bun.lock index 1b88266b26c..533df254384 100644 --- a/bun.lock +++ b/bun.lock @@ -27,7 +27,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@kilocode/kilo-i18n": "workspace:*", "@kilocode/kilo-ui": "workspace:*", @@ -79,7 +79,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -112,7 +112,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -142,7 +142,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -171,10 +171,10 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@ai-sdk/anthropic": "2.0.65", - "@ai-sdk/openai": "2.0.89", + "@ai-sdk/openai": "2.0.101", "@ai-sdk/openai-compatible": "1.0.32", "@clack/prompts": "1.0.0-alpha.1", "@kilocode/plugin": "workspace:*", @@ -206,7 +206,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.1.3", + "version": "7.1.11", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -216,7 +216,7 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "@opentelemetry/api": "1.9.0", @@ -236,7 +236,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@kobalte/core": "0.13.11", "@opencode-ai/util": "workspace:*", @@ -271,7 +271,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-i18n": "workspace:*", @@ -324,7 +324,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.1.3", + "version": "7.1.11", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -344,7 +344,7 @@ "@ai-sdk/google-vertex": "3.0.106", "@ai-sdk/groq": "2.0.34", "@ai-sdk/mistral": "2.0.27", - "@ai-sdk/openai": "2.0.89", + "@ai-sdk/openai": "2.0.101", "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/perplexity": "2.0.23", "@ai-sdk/provider": "2.0.1", @@ -446,7 +446,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@kilocode/sdk": "workspace:*", "zod": "catalog:", @@ -460,14 +460,14 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.1.3", + "version": "7.1.11", "devDependencies": { "@types/bun": "catalog:", }, }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.1.3", + "version": "7.1.11", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -478,7 +478,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.1.3", + "version": "7.1.11", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -501,7 +501,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", @@ -548,7 +548,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "7.1.3", + "version": "7.1.11", "dependencies": { "zod": "catalog:", }, @@ -665,7 +665,7 @@ "@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gaptHgaXjMw3+eA0Q4FABcsj5nQNP6EpFaGUR+Pj5WJy7Kn6mApl975/x57224MfeJIShNpt8wFKK3tvh5ewKg=="], - "@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="], + "@ai-sdk/openai": ["@ai-sdk/openai@2.0.101", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kQ52HLV45T3bQbRzWExXW6+pkg3Nvq4dUnZHUPJXWgkUUsAhZjxHrXqPOc/0yfn/4+Dn2uLmIgAkP9IfzMMcNg=="], "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], @@ -4357,6 +4357,8 @@ "@ai-sdk/amazon-bedrock/aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="], + "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], @@ -4381,7 +4383,7 @@ "@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="], "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], @@ -4835,6 +4837,8 @@ "ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.90", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.56", "@ai-sdk/google": "2.0.46", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-C9MLe1KZGg1ZbupV2osygHtL5qngyCDA6ATatunyfTbIe8TXKG8HGni/3O6ifbnI5qxTidIn150Ox7eIFZVMYg=="], + "ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="], + "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -5599,6 +5603,8 @@ "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + "ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], diff --git a/nix/hashes.json b/nix/hashes.json index 8d54b85cb0c..80015c18486 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-85Pa4C5F3yqjMzIGBcWYGe+IitSg9nnTtixqZPRnFps=", - "aarch64-linux": "sha256-qeyu0LS1z8JXGa+2EVd/U94uQIV8zi53ZmymE9/trSU=", - "aarch64-darwin": "sha256-qwedJnAMcM/gTVSCTErsP++HM2lYQtLE+PM1QkT7fy8=", - "x86_64-darwin": "sha256-372zLuwN6TLhn4JIDorEc13mjMpC91UbukxE/Y14c/k=" + "x86_64-linux": "sha256-YjB3YfaA79nztpZoYEAlFBakS//G/Guprlqk4BYFgOA=", + "aarch64-linux": "sha256-ru470M8RVwyOPNGr6I4VfApQRHhYFFMPjOBneTRBJtc=", + "aarch64-darwin": "sha256-M6LIPBHnYH9AtIRAKyW44VN71Kb28nztzFteCkL0588=", + "x86_64-darwin": "sha256-HURoot79BFHYwoEp0XqVdI6cRdV7KGvmRRqOf+hUa9Q=" } } diff --git a/package.json b/package.json index dba58cb6a59..c2ac3d84d61 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,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.1.3", + "version": "7.1.11", "peerDependencies": {} } diff --git a/packages/app/package.json b/packages/app/package.json index 9d0b6e9b88a..44dc2a05125 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "7.1.3", + "version": "7.1.11", "description": "", "type": "module", "exports": { diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 663dc883665..445b83336a3 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "7.1.3", + "version": "7.1.11", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index dda2b1029fd..75eec5cdc73 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "7.1.3", + "version": "7.1.11", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 333088147a6..12b55abae7e 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "7.1.3" +version = "7.1.11" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.3/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.11/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.3/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.11/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.3/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.11/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.3/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.11/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.3/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.11/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/kilo-docs/components/PageVersionSwitcher.tsx b/packages/kilo-docs/components/PageVersionSwitcher.tsx new file mode 100644 index 00000000000..2c793355602 --- /dev/null +++ b/packages/kilo-docs/components/PageVersionSwitcher.tsx @@ -0,0 +1,45 @@ +import React from "react" +import type { Platform } from "../lib/types" + +interface Props { + platform?: Platform +} + +export function PageVersionSwitcher({ platform }: Props) { + if (!platform || platform === "all") return null + + const legacy = platform === "legacy" + return ( +
+ {legacy ? "\u24D8" : "\u2728"} + + {legacy + ? "This page applies to the legacy VSCode extension." + : "This page applies to the current VSCode extension & CLI."} + + + +
+ ) +} diff --git a/packages/kilo-docs/components/SideNav.tsx b/packages/kilo-docs/components/SideNav.tsx index 1960fa1f842..2897576d3fb 100644 --- a/packages/kilo-docs/components/SideNav.tsx +++ b/packages/kilo-docs/components/SideNav.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react" import { useRouter } from "next/router" import Link from "next/link" -import { SectionNav } from "../lib/types" +import { Platform, SectionNav } from "../lib/types" import { Nav } from "../lib/nav" // Define navigation items for each major section @@ -67,6 +67,16 @@ const ChevronLeft = () => ( ) +function PlatformBadge({ platform }: { platform?: Platform }) { + if (!platform || platform === "all") return null + const label = platform === "legacy" ? "Legacy" : "New" + return ( + + {label} + + ) +} + interface SideNavProps { isMobileOpen?: boolean onMobileClose?: () => void @@ -268,6 +278,7 @@ export function SideNav({ isMobileOpen = false, onMobileClose }: SideNavProps) {
{link.children} +
) diff --git a/packages/kilo-docs/components/Tabs.tsx b/packages/kilo-docs/components/Tabs.tsx index 43fe16707ca..6cc69abda61 100644 --- a/packages/kilo-docs/components/Tabs.tsx +++ b/packages/kilo-docs/components/Tabs.tsx @@ -27,16 +27,16 @@ export function Tabs({ children }: TabsProps) { ) const indexFromHash = () => { - if (typeof window === "undefined") return 0 const hash = window.location.hash.slice(1) if (!hash) return 0 const found = tabs.findIndex((tab) => slugify(tab.props.label) === hash) return found >= 0 ? found : 0 } - const [activeIndex, setActiveIndex] = useState(indexFromHash) + const [activeIndex, setActiveIndex] = useState(0) useEffect(() => { + setActiveIndex(indexFromHash()) const onHashChange = () => setActiveIndex(indexFromHash()) window.addEventListener("hashchange", onHashChange) return () => window.removeEventListener("hashchange", onHashChange) diff --git a/packages/kilo-docs/components/TopNav.tsx b/packages/kilo-docs/components/TopNav.tsx index 677cd82615e..3d458227c40 100644 --- a/packages/kilo-docs/components/TopNav.tsx +++ b/packages/kilo-docs/components/TopNav.tsx @@ -336,9 +336,8 @@ export function TopNav({ onMobileMenuToggle, isMobileMenuOpen = false, showMobil {/* Announcement banner */}

- We're replatforming our extensions on the new Kilo CLI. - Contribute to the new CLI and pre-release extensions at{" "} - Kilo-Org/kilocode. + We've replatformed our extensions on the new Kilo CLI. + Contribute at Kilo-Org/kilocode.

diff --git a/packages/kilo-docs/components/index.js b/packages/kilo-docs/components/index.js index 9bb2cc21b24..9297d31ab0e 100644 --- a/packages/kilo-docs/components/index.js +++ b/packages/kilo-docs/components/index.js @@ -7,6 +7,7 @@ export * from "./Icon" export * from "./Image" export * from "./KiloCodeIcon" export * from "./PageFooter" +export * from "./PageVersionSwitcher" export * from "./SideNav" export * from "./Table" export * from "./TableOfContents" diff --git a/packages/kilo-docs/lib/nav/automate.ts b/packages/kilo-docs/lib/nav/automate.ts index e8ae1c02a8d..d4d9bb487e8 100644 --- a/packages/kilo-docs/lib/nav/automate.ts +++ b/packages/kilo-docs/lib/nav/automate.ts @@ -29,6 +29,7 @@ export const AutomateNav: NavSection[] = [ { href: "/automate/extending/auto-launch", children: "Auto-launch Configuration", + platform: "legacy", }, { href: "/automate/mcp/overview", @@ -53,8 +54,8 @@ export const AutomateNav: NavSection[] = [ { title: "Tools", links: [ - { href: "/automate/how-tools-work", children: "How Tools Work" }, - { href: "/automate/tools", children: "Tools Details" }, + { href: "/automate/how-tools-work", children: "How Tools Work", platform: "legacy" }, + { href: "/automate/tools", children: "Tools Details", platform: "legacy" }, ], }, ] diff --git a/packages/kilo-docs/lib/nav/code-with-ai.ts b/packages/kilo-docs/lib/nav/code-with-ai.ts index 59eac752024..e5d621b250f 100644 --- a/packages/kilo-docs/lib/nav/code-with-ai.ts +++ b/packages/kilo-docs/lib/nav/code-with-ai.ts @@ -41,10 +41,10 @@ export const CodeWithAiNav: NavSection[] = [ children: "Free & Budget Models", }, { - href: "/code-with-ai/agents/using-modes", - children: "Modes", + href: "/code-with-ai/agents/using-agents", + children: "Agents", subLinks: [ - { href: "/code-with-ai/agents/using-modes", children: "Using Modes" }, + { href: "/code-with-ai/agents/using-agents", children: "Using Agents" }, { href: "/code-with-ai/agents/orchestrator-mode", children: "Orchestrator Mode", diff --git a/packages/kilo-docs/lib/nav/contributing.ts b/packages/kilo-docs/lib/nav/contributing.ts index e02eb80fdaa..34fb344f30f 100644 --- a/packages/kilo-docs/lib/nav/contributing.ts +++ b/packages/kilo-docs/lib/nav/contributing.ts @@ -13,10 +13,6 @@ export const ContributingNav: NavSection[] = [ href: "/contributing/ecosystem", children: "Ecosystem", }, - { - href: "/contributing/cline-to-kilo-migration", - children: "Cline to Kilo Migration", - }, ], }, { diff --git a/packages/kilo-docs/lib/nav/customize.ts b/packages/kilo-docs/lib/nav/customize.ts index ca3760e1b5f..7687bad119f 100644 --- a/packages/kilo-docs/lib/nav/customize.ts +++ b/packages/kilo-docs/lib/nav/customize.ts @@ -20,9 +20,10 @@ export const CustomizeNav: NavSection[] = [ { href: "/customize/custom-subagents", children: "Custom Subagents", + platform: "new", }, { href: "/customize/agents-md", children: "agents.md" }, - { href: "/customize/workflows", children: "Workflows" }, + { href: "/customize/workflows", children: "Workflows", platform: "new" }, { href: "/customize/skills", children: "Skills" }, { href: "/customize/prompt-engineering", @@ -36,6 +37,7 @@ export const CustomizeNav: NavSection[] = [ { href: "/customize/context/codebase-indexing", children: "Codebase Indexing", + platform: "legacy", }, { href: "/customize/context/context-condensing", @@ -48,6 +50,7 @@ export const CustomizeNav: NavSection[] = [ { href: "/customize/context/large-projects", children: "Large Projects", + platform: "legacy", }, ], }, diff --git a/packages/kilo-docs/lib/nav/getting-started.ts b/packages/kilo-docs/lib/nav/getting-started.ts index 38132a38eb0..45d16d25fcc 100644 --- a/packages/kilo-docs/lib/nav/getting-started.ts +++ b/packages/kilo-docs/lib/nav/getting-started.ts @@ -30,8 +30,12 @@ export const GettingStartedNav: NavSection[] = [ children: "Settings", subLinks: [ { href: "/getting-started/settings/auto-approving-actions", children: "Auto-Approving Actions" }, - { href: "/getting-started/settings/auto-cleanup", children: "Auto Cleanup" }, - { href: "/getting-started/settings/system-notifications", children: "System Notifications" }, + { href: "/getting-started/settings/auto-cleanup", children: "Auto Cleanup", platform: "legacy" }, + { + href: "/getting-started/settings/system-notifications", + children: "System Notifications", + platform: "legacy", + }, ], }, { href: "/getting-started/adding-credits", children: "Adding Credits" }, diff --git a/packages/kilo-docs/lib/nav/kiloclaw.ts b/packages/kilo-docs/lib/nav/kiloclaw.ts index 50262d29f6f..90ee60a72dc 100644 --- a/packages/kilo-docs/lib/nav/kiloclaw.ts +++ b/packages/kilo-docs/lib/nav/kiloclaw.ts @@ -7,19 +7,62 @@ export const KiloClawNav: NavSection[] = [ { href: "/kiloclaw/overview", children: "Overview" }, { href: "/kiloclaw/dashboard", children: "Dashboard" }, { href: "/kiloclaw/pre-installed-software", children: "Pre-installed Software" }, - { href: "/kiloclaw/control-ui", children: "Control UI" }, - { href: "/kiloclaw/chat-platforms", children: "Chat Platforms" }, { - href: "/kiloclaw/development-tools/github", + href: "/kiloclaw/control-ui/overview", + children: "Control UI", + subLinks: [ + { href: "/kiloclaw/control-ui/overview", children: "Overview" }, + { href: "/kiloclaw/control-ui/changing-models", children: "Changing Models" }, + { href: "/kiloclaw/control-ui/exec-approvals", children: "Exec Approvals" }, + { href: "/kiloclaw/control-ui/version-pinning", children: "Version Pinning" }, + ], + }, + { + href: "/kiloclaw/chat-platforms", + children: "Chat Platforms", + subLinks: [ + { href: "/kiloclaw/chat-platforms", children: "Overview" }, + { href: "/kiloclaw/chat-platforms/telegram", children: "Telegram" }, + { href: "/kiloclaw/chat-platforms/discord", children: "Discord" }, + { href: "/kiloclaw/chat-platforms/slack", children: "Slack" }, + ], + }, + { + href: "/kiloclaw/development-tools", children: "Development Tools", subLinks: [ + { href: "/kiloclaw/development-tools", children: "Overview" }, { href: "/kiloclaw/development-tools/github", children: "GitHub" }, { href: "/kiloclaw/development-tools/google", children: "Google Workspace" }, ], }, - { href: "/kiloclaw/version-pinning", children: "Version Pinning" }, - { href: "/kiloclaw/troubleshooting", children: "Troubleshooting" }, - { href: "/kiloclaw/pricing", children: "Pricing" }, + { + href: "/kiloclaw/tools", + children: "Tools", + subLinks: [ + { href: "/kiloclaw/tools", children: "Overview" }, + { href: "/kiloclaw/tools/1password", children: "1Password" }, + { href: "/kiloclaw/tools/brave-search", children: "Brave Search" }, + { href: "/kiloclaw/tools/agentcard", children: "AgentCard" }, + ], + }, + { + href: "/kiloclaw/troubleshooting/common-questions", + children: "Troubleshooting", + subLinks: [ + { href: "/kiloclaw/troubleshooting/common-questions", children: "Common Questions" }, + { href: "/kiloclaw/troubleshooting/gateway-process", children: "Gateway Process States" }, + { href: "/kiloclaw/troubleshooting/architecture", children: "Architecture Notes" }, + ], + }, + { + href: "/kiloclaw/faq/general", + children: "FAQ", + subLinks: [ + { href: "/kiloclaw/faq/general", children: "General" }, + { href: "/kiloclaw/faq/pricing", children: "Pricing" }, + ], + }, ], }, ] diff --git a/packages/kilo-docs/lib/types.ts b/packages/kilo-docs/lib/types.ts index 7e5fdf7a22e..5feef035784 100644 --- a/packages/kilo-docs/lib/types.ts +++ b/packages/kilo-docs/lib/types.ts @@ -1,6 +1,9 @@ +export type Platform = "legacy" | "new" | "all" + export interface NavLink { href: string children: string + platform?: Platform // "legacy" = stable VSCode only, "new" = new VSCode + CLI only, omitted = universal subLinks?: NavLink[] // Optional nested links for second-level navigation } diff --git a/packages/kilo-docs/lychee.toml b/packages/kilo-docs/lychee.toml index 627a395310a..dccd254c000 100644 --- a/packages/kilo-docs/lychee.toml +++ b/packages/kilo-docs/lychee.toml @@ -32,4 +32,6 @@ exclude = [ '^https?://github\.com/', # Redirects to login page, blocked by bots '^https?://vercel\.link/', + # API base URL, returns 404 when fetched directly + '^https?://api\.apertis\.ai/v1/?$', ] diff --git a/packages/kilo-docs/markdoc/partials/install-jetbrains.md b/packages/kilo-docs/markdoc/partials/install-jetbrains.md index 7dce539d9a8..a64c1c1974e 100644 --- a/packages/kilo-docs/markdoc/partials/install-jetbrains.md +++ b/packages/kilo-docs/markdoc/partials/install-jetbrains.md @@ -18,7 +18,7 @@ Before installing the Kilo Code plugin, ensure you have: 1. If you don't have a JetBrains IDE installed, download one from [jetbrains.com](https://www.jetbrains.com/) 2. Then, you can click the button below to install Kilo Code directly from the JetBrains Marketplace: -[![Install Kilo Code](https://img.shields.io/badge/Install%20Kilo%20Code-F8F674?style=for-the-badge)](https://plugins.jetbrains.com/plugin/28350-kilo-code) +[![Install Kilo Code](https://raster.shields.io/badge/Install%20Kilo%20Code-F8F674?style=for-the-badge)](https://plugins.jetbrains.com/plugin/28350-kilo-code) ### Install from JetBrains Marketplace diff --git a/packages/kilo-docs/markdoc/partials/install-vscode.md b/packages/kilo-docs/markdoc/partials/install-vscode.md index 54a22e5adbd..1edf8af2dc9 100644 --- a/packages/kilo-docs/markdoc/partials/install-vscode.md +++ b/packages/kilo-docs/markdoc/partials/install-vscode.md @@ -3,7 +3,7 @@ 1. If you don't have VS Code installed, download it from [code.visualstudio.com](https://code.visualstudio.com/) 2. Then, you can click the button below to install Kilo Code directly from the VS Code Marketplace: -[![Install Kilo Code](https://img.shields.io/badge/Install%20Kilo%20Code-F8F674?style=for-the-badge)](vscode:extension/kilocode.kilo-code) +[![Install Kilo Code](https://raster.shields.io/badge/Install%20Kilo%20Code-F8F674?style=for-the-badge)](vscode:extension/kilocode.kilo-code) ### Install from VS Code Marketplace diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index 6c72df87e34..32f86b32191 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.1.3", + "version": "7.1.11", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-docs/pages/_app.tsx b/packages/kilo-docs/pages/_app.tsx index 6d45a81782c..245ab29af70 100644 --- a/packages/kilo-docs/pages/_app.tsx +++ b/packages/kilo-docs/pages/_app.tsx @@ -3,7 +3,7 @@ import Head from "next/head" import { useRouter } from "next/router" import posthog from "posthog-js" -import { CopyPageButton, PageFooter, SideNav, TableOfContents, TopNav } from "../components" +import { CopyPageButton, PageFooter, PageVersionSwitcher, SideNav, TableOfContents, TopNav } from "../components" import "prismjs" import "prismjs/components/prism-bash.min" @@ -129,6 +129,8 @@ export default function MyApp({ Component, pageProps }: AppProps) { const toc = pageProps.markdoc?.content ? collectHeadings(pageProps.markdoc.content) : [] + const platform = markdoc?.frontmatter?.platform + return ( <> @@ -179,6 +181,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
+ {markdoc && } {markdoc && }
diff --git a/packages/kilo-docs/pages/ai-providers/anthropic.md b/packages/kilo-docs/pages/ai-providers/anthropic.md index 80897edd3cc..767aed584cb 100644 --- a/packages/kilo-docs/pages/ai-providers/anthropic.md +++ b/packages/kilo-docs/pages/ai-providers/anthropic.md @@ -17,12 +17,56 @@ Anthropic is an AI safety and research company that builds reliable, interpretab ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Anthropic" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Anthropic API key into the "Anthropic API Key" field. 4. **Select Model:** Choose your desired Claude model from the "Model" dropdown. 5. **(Optional) Custom Base URL:** If you need to use a custom base URL for the Anthropic API, check "Use custom base URL" and enter the URL. Most people won't need to adjust this. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Anthropic and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export ANTHROPIC_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "anthropic": { + "env": ["ANTHROPIC_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "anthropic/claude-sonnet-4-20250514", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Prompt Caching:** Claude 3 models support [prompt caching](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching), which can significantly reduce costs and latency for repeated prompts. diff --git a/packages/kilo-docs/pages/ai-providers/bedrock.md b/packages/kilo-docs/pages/ai-providers/bedrock.md index ecdb8a6c86d..64f4bb7aea2 100644 --- a/packages/kilo-docs/pages/ai-providers/bedrock.md +++ b/packages/kilo-docs/pages/ai-providers/bedrock.md @@ -34,6 +34,9 @@ You have three options for configuring AWS credentials: ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Bedrock" from the "API Provider" dropdown. 3. **Select Authentication Method:** @@ -48,6 +51,53 @@ You have three options for configuring AWS credentials: 5. **(Optional) Cross-Region Inference:** Check "Use cross-region inference" if you want to access models in a region different from your configured AWS region. 6. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add AWS Bedrock. The extension uses the AWS credentials chain for authentication — configure your AWS credentials using the AWS CLI or environment variables before adding the provider. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Bedrock uses the AWS credentials chain for authentication. Configure your AWS credentials using the AWS CLI or environment variables: + +**Environment variables:** + +```bash +export AWS_ACCESS_KEY_ID="your-access-key" +export AWS_SECRET_ACCESS_KEY="your-secret-key" +export AWS_REGION="us-east-1" +``` + +Or use an AWS profile: + +```bash +aws configure --profile bedrock +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "amazon-bedrock": {}, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "amazon-bedrock/anthropic.claude-sonnet-4-20250514-v1:0", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Permissions:** Ensure your IAM user or role has the necessary permissions to invoke Bedrock models. The `bedrock:InvokeModel` permission is required. diff --git a/packages/kilo-docs/pages/ai-providers/cerebras.md b/packages/kilo-docs/pages/ai-providers/cerebras.md index 7f965ece593..24c19bdc421 100644 --- a/packages/kilo-docs/pages/ai-providers/cerebras.md +++ b/packages/kilo-docs/pages/ai-providers/cerebras.md @@ -17,11 +17,55 @@ Cerebras is known for their ultra-fast AI inference powered by the Cerebras CS-3 ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Cerebras" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Cerebras API key into the "Cerebras API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Cerebras and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export CEREBRAS_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "cerebras": { + "env": ["CEREBRAS_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "cerebras/llama-4-scout-17b-16e-instruct", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Inference Speed:** Cerebras models deliver some of the fastest inference speeds available, reducing wait times during development. diff --git a/packages/kilo-docs/pages/ai-providers/chutes-ai.md b/packages/kilo-docs/pages/ai-providers/chutes-ai.md index d0a4a14f62f..348b3b84feb 100644 --- a/packages/kilo-docs/pages/ai-providers/chutes-ai.md +++ b/packages/kilo-docs/pages/ai-providers/chutes-ai.md @@ -20,11 +20,55 @@ Always refer to the official Chutes AI documentation or your dashboard for the m ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Chutes AI" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Chutes AI API key into the "Chutes AI API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Chutes AI and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export CHUTES_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "chutes": { + "env": ["CHUTES_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "chutes/model-name", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Free Access:** Chutes AI provides free API access, making it an excellent option for experimentation and development without immediate costs. diff --git a/packages/kilo-docs/pages/ai-providers/claude-code.md b/packages/kilo-docs/pages/ai-providers/claude-code.md index 7cd5136a435..239e3d779ca 100644 --- a/packages/kilo-docs/pages/ai-providers/claude-code.md +++ b/packages/kilo-docs/pages/ai-providers/claude-code.md @@ -34,11 +34,60 @@ The specific models available depend on your Claude subscription and plan. See [ ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Claude Code" from the "API Provider" dropdown. 3. **Select Model:** Choose your desired Claude model from the "Model" dropdown. 4. **(Optional) Custom CLI Path:** If you installed Claude Code to a location other than the default `claude` command, enter the full path to your Claude executable in the "Claude Code Path" field. Most users won't need to change this. +{% /tab %} +{% tab label="VSCode" %} + +{% callout type="warning" %} +Claude Code credentials no longer work in Kilo Code. Please use the [Anthropic provider](/docs/ai-providers/anthropic) with an API key instead. +{% /callout %} + +{% /tab %} +{% tab label="CLI" %} + +Claude Code uses your existing Anthropic credentials (from the `claude` CLI). Make sure the Claude Code CLI is installed and authenticated: + +```bash +claude --version +claude auth login +``` + +If you have an `ANTHROPIC_API_KEY` environment variable set, the Claude CLI will use it automatically: + +```bash +export ANTHROPIC_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "anthropic": { + "env": ["ANTHROPIC_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "anthropic/claude-sonnet-4-20250514", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **No API Keys Required:** Claude Code uses your existing CLI authentication, so you don't need to manage separate API keys. diff --git a/packages/kilo-docs/pages/ai-providers/deepseek.md b/packages/kilo-docs/pages/ai-providers/deepseek.md index dea4ad27bf8..d70c070f3e8 100644 --- a/packages/kilo-docs/pages/ai-providers/deepseek.md +++ b/packages/kilo-docs/pages/ai-providers/deepseek.md @@ -17,11 +17,55 @@ Kilo Code supports accessing models through the DeepSeek API, including `deepsee ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "DeepSeek" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your DeepSeek API key into the "DeepSeek API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add DeepSeek and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export DEEPSEEK_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "deepseek": { + "env": ["DEEPSEEK_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "deepseek/deepseek-chat", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Pricing:** Refer to the [DeepSeek Pricing](https://api-docs.deepseek.com/quick_start/pricing/) page for details on model costs. diff --git a/packages/kilo-docs/pages/ai-providers/fireworks.md b/packages/kilo-docs/pages/ai-providers/fireworks.md index d89a0796df6..f354b533760 100644 --- a/packages/kilo-docs/pages/ai-providers/fireworks.md +++ b/packages/kilo-docs/pages/ai-providers/fireworks.md @@ -17,11 +17,55 @@ Fireworks AI is a high-performance platform for running AI models that offers fa ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Fireworks AI" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Fireworks AI API key into the "Fireworks AI API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Fireworks AI and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export FIREWORKS_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "fireworks-ai": { + "env": ["FIREWORKS_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "fireworks-ai/accounts/fireworks/models/llama4-scout-instruct-basic", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Performance:** Fireworks AI is optimized for speed and offers excellent performance for both chat and completion tasks. diff --git a/packages/kilo-docs/pages/ai-providers/gemini.md b/packages/kilo-docs/pages/ai-providers/gemini.md index 4914f9ea7b6..3a0ffe09eec 100644 --- a/packages/kilo-docs/pages/ai-providers/gemini.md +++ b/packages/kilo-docs/pages/ai-providers/gemini.md @@ -17,11 +17,55 @@ Kilo Code supports Google's Gemini family of models through the Google AI Gemini ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Google Gemini" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Gemini API key into the "Gemini API Key" field. 4. **Select Model:** Choose your desired Gemini model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Google Gemini and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export GOOGLE_GENERATIVE_AI_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "google": { + "env": ["GOOGLE_GENERATIVE_AI_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "google/gemini-2.5-pro", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Pricing:** Gemini API usage is priced based on input and output tokens. Refer to the [Gemini pricing page](https://ai.google.dev/pricing) for detailed information. diff --git a/packages/kilo-docs/pages/ai-providers/glama.md b/packages/kilo-docs/pages/ai-providers/glama.md index bc50910b431..fb000fc64bb 100644 --- a/packages/kilo-docs/pages/ai-providers/glama.md +++ b/packages/kilo-docs/pages/ai-providers/glama.md @@ -26,11 +26,31 @@ Refer to the [Glama documentation](https://glama.ai/models) for the most up-to-d ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Glama" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Glama API key into the "Glama API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Glama and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +{% callout type="warning" %} +Glama is not yet available as a CLI provider. Check the [Kilo Code releases](https://github.com/Kilo-Org/kilocode/releases) for updates on provider support. +{% /callout %} + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Pricing:** Glama operates on a pay-per-use basis. Pricing varies depending on the model you choose. diff --git a/packages/kilo-docs/pages/ai-providers/groq.md b/packages/kilo-docs/pages/ai-providers/groq.md index 77bb3efadbc..58fcf4f1657 100644 --- a/packages/kilo-docs/pages/ai-providers/groq.md +++ b/packages/kilo-docs/pages/ai-providers/groq.md @@ -20,17 +20,54 @@ Kilo Code will attempt to fetch the list of available models from the Groq API. ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Groq" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Groq API key into the "Groq API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. -## Tips and Notes +{% /tab %} +{% tab label="VSCode" %} -- **High-Speed Inference:** Groq's LPUs provide exceptionally fast response times, making it ideal for interactive development workflows. -- **Token Limits:** Some models have specific `max_tokens` limits that are automatically handled by Kilo Code (e.g., the `moonshotai/kimi-k2-instruct` model). -- **Cost Efficiency:** Groq often provides competitive pricing for high-speed inference compared to other providers. -- **Model Selection:** Choose models based on your specific needs - larger models like `llama3-70b-8192` for complex reasoning tasks, or smaller models like `llama3-8b-8192` for faster, simpler operations. +Open **Settings** (gear icon) and go to the **Providers** tab to add Groq and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export GROQ_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "groq": { + "env": ["GROQ_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "groq/llama-3.3-70b-versatile", +} +``` + +{% /tab %} +{% /tabs %} ## Supported Models @@ -46,13 +83,6 @@ Kilo Code supports the following models through Groq: **Note:** Model availability may change. Refer to the [Groq documentation](https://console.groq.com/docs/models) for the latest model list and specifications. -## Configuration in Kilo Code - -1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. -2. **Select Provider:** Choose "Groq" from the "API Provider" dropdown. -3. **Enter API Key:** Paste your Groq API key into the "Groq API Key" field. -4. **Select Model:** Choose your desired model from the "Model" dropdown. - ## Model-Specific Features ### Kimi K2 Model diff --git a/packages/kilo-docs/pages/ai-providers/inception.md b/packages/kilo-docs/pages/ai-providers/inception.md index 6a8f302e391..8431a37fc6f 100644 --- a/packages/kilo-docs/pages/ai-providers/inception.md +++ b/packages/kilo-docs/pages/ai-providers/inception.md @@ -23,11 +23,55 @@ Refer to Inception's current website and developer documentation for the most up ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Inception" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Inception API key into the "Inception API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Inception and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export INCEPTION_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "inception": { + "env": ["INCEPTION_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "inception/mercury-coder-small-beta", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Enterprise Focus:** Inception is designed for production-grade AI applications with emphasis on reliability and consistency. diff --git a/packages/kilo-docs/pages/ai-providers/index.md b/packages/kilo-docs/pages/ai-providers/index.md index 4f75c4e985a..6981043e579 100644 --- a/packages/kilo-docs/pages/ai-providers/index.md +++ b/packages/kilo-docs/pages/ai-providers/index.md @@ -58,11 +58,7 @@ Route requests through unified APIs with additional features: - **Regional** - Better latency in certain locations {% callout type="note" %} -All API keys use VS Code's Secret Storage—never stored in plain text. -{% /callout %} - -{% callout type="warning" title="Time-to-first-byte timeout" %} -For all providers, there is a **five-minute timeout** on time to first token. This means if a provider does not begin streaming a response within five minutes of the request being sent, the request will be cancelled. This is a constraint of the Bun runtime and cannot be easily configured. +In the **VSCode (Legacy)** version, API keys use VS Code's Secret Storage. In the current **VSCode & CLI** version, keys are set via environment variables or referenced in `kilo.json` config files. See individual provider pages for setup instructions for each platform. {% /callout %} ## Next Steps diff --git a/packages/kilo-docs/pages/ai-providers/lmstudio.md b/packages/kilo-docs/pages/ai-providers/lmstudio.md index 13efa7b8361..e22058308b2 100644 --- a/packages/kilo-docs/pages/ai-providers/lmstudio.md +++ b/packages/kilo-docs/pages/ai-providers/lmstudio.md @@ -27,12 +27,50 @@ Kilo Code supports running models locally using LM Studio. LM Studio provides a ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "LM Studio" from the "API Provider" dropdown. 3. **Enter Model ID:** Enter the _file name_ of the model you loaded in LM Studio (e.g., `codellama-7b.Q4_0.gguf`). You can find this in the LM Studio "Local Server" tab. 4. **(Optional) Base URL:** By default, Kilo Code will connect to LM Studio at `http://localhost:1234`. If you've configured LM Studio to use a different address or port, enter the full URL here. 5. **(Optional) Timeout:** By default, API requests time out after 10 minutes. Local models can be slow, if you hit this timeout you can consider increasing it here: VS Code Extensions panel > Kilo Code gear menu > Settings > API Request Timeout. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add LM Studio. No API key is needed since LM Studio runs locally. You can configure the base URL if LM Studio is running on a different host or port. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +LM Studio runs locally, so no API key is needed. Configure the base URL if LM Studio is running on a different host or port: + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "lmstudio": { + "baseURL": "http://localhost:1234/v1", + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "lmstudio/codellama-7b", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Resource Requirements:** Running large language models locally can be resource-intensive. Make sure your computer meets the minimum requirements for the model you choose. diff --git a/packages/kilo-docs/pages/ai-providers/minimax.md b/packages/kilo-docs/pages/ai-providers/minimax.md index 4c6fd6adc5b..4f36072519f 100644 --- a/packages/kilo-docs/pages/ai-providers/minimax.md +++ b/packages/kilo-docs/pages/ai-providers/minimax.md @@ -17,11 +17,55 @@ MiniMax is a global AI foundation model company focused on fast, cost-efficient ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Navigate to **Providers**. Choose **MiniMax** from the API Provider dropdown. 3. **Enter API Key:** Paste your MiniMax API key into the MiniMax API Key field. 4. **Select Model:** Choose your desired MiniMax model from the Model dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add MiniMax and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export MINIMAX_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "minimax": { + "env": ["MINIMAX_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "minimax/MiniMax-M1", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Performance:** MiniMax M2.1 emphasizes fast inference, strong coding ability, and exceptional tool-calling performance. diff --git a/packages/kilo-docs/pages/ai-providers/mistral.md b/packages/kilo-docs/pages/ai-providers/mistral.md index 8a902adb401..e1ce39948b2 100644 --- a/packages/kilo-docs/pages/ai-providers/mistral.md +++ b/packages/kilo-docs/pages/ai-providers/mistral.md @@ -17,11 +17,55 @@ Kilo Code supports accessing models through the Mistral AI API, including both s ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Mistral" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Mistral API key into the "Mistral API Key" field if you're using a `mistral` model. If you intend to use `codestral-latest`, see the "Codestral" section below. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Mistral and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export MISTRAL_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "mistral": { + "env": ["MISTRAL_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "mistral/mistral-large-latest", +} +``` + +{% /tab %} +{% /tabs %} + ## Using Codestral [Codestral](https://docs.mistral.ai/capabilities/code_generation/) is a model specifically designed for code generation and interaction. diff --git a/packages/kilo-docs/pages/ai-providers/moonshot.md b/packages/kilo-docs/pages/ai-providers/moonshot.md index 44ee85619a1..7b92cf10287 100644 --- a/packages/kilo-docs/pages/ai-providers/moonshot.md +++ b/packages/kilo-docs/pages/ai-providers/moonshot.md @@ -17,11 +17,55 @@ Moonshot.ai is a Chinese AI company known for their **Kimi** models featuring ul ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Moonshot.ai" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Moonshot.ai API key into the "Moonshot.ai API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Moonshot.ai and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export MOONSHOT_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "moonshotai": { + "env": ["MOONSHOT_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "moonshotai/moonshot-v1-auto", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Ultra-Long Context:** Kimi models excel at handling large codebases and complex projects with their extended context windows. diff --git a/packages/kilo-docs/pages/ai-providers/ollama.md b/packages/kilo-docs/pages/ai-providers/ollama.md index d50417072ca..33f8199e24e 100644 --- a/packages/kilo-docs/pages/ai-providers/ollama.md +++ b/packages/kilo-docs/pages/ai-providers/ollama.md @@ -72,12 +72,50 @@ By default, API requests time out after 10 minutes. Local models can be slow, if ### Configure Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + - Open the Kilo Code panel ({% kiloCodeIcon size="1em" /%}). - Click the Settings gear icon ({% codicon name="gear" /%}). - Select "Ollama" as the API Provider. - Select the model configured in the previous step. - (Optional) You can configure the base URL if you're running Ollama on a different machine. The default is `http://localhost:11434`. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Ollama. No API key is needed since Ollama runs locally. You can configure the base URL if Ollama is running on a different host. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Ollama runs locally, so no API key is needed. Configure the base URL if Ollama is running on a different host: + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "ollama": { + "baseURL": "http://localhost:11434/v1", + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "ollama/qwen3-coder:30b", +} +``` + +{% /tab %} +{% /tabs %} + ## Further Reading Refer to the [Ollama documentation](https://ollama.com/docs) for more information on installing, configuring and using Ollama. diff --git a/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md b/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md index 2e2105f8367..4f24ddc2908 100644 --- a/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md +++ b/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md @@ -4,6 +4,9 @@ sidebar_label: ChatGPT Plus/Pro # Using ChatGPT Subscriptions With Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. Open Kilo Code settings (click the gear icon {% codicon name="gear" /%} in the Kilo Code panel). 2. In **API Provider**, select **OpenAI – ChatGPT Plus/Pro**. 3. Click **Sign in to OpenAI Codex**. @@ -11,6 +14,43 @@ sidebar_label: ChatGPT Plus/Pro 5. Back in Kilo Code settings, pick a model from the dropdown. 6. Save. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab. ChatGPT Plus/Pro uses OAuth authentication — follow the sign-in flow to connect your ChatGPT subscription. + +{% /tab %} +{% tab label="CLI" %} + +ChatGPT Plus/Pro uses OAuth authentication, which is only available in the VS Code extension. For the CLI, use the [OpenAI API provider](/docs/ai-providers/openai) with an API key instead: + +```bash +export OPENAI_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "openai": { + "env": ["OPENAI_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "openai/gpt-4.1", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Subscription Required:** You need an active ChatGPT Plus or Pro subscription. This provider won't work with free ChatGPT accounts. See [OpenAI's ChatGPT plans](https://openai.com/chatgpt/pricing) for more information. diff --git a/packages/kilo-docs/pages/ai-providers/openai-compatible.md b/packages/kilo-docs/pages/ai-providers/openai-compatible.md index f550cbab7cb..5e373573752 100644 --- a/packages/kilo-docs/pages/ai-providers/openai-compatible.md +++ b/packages/kilo-docs/pages/ai-providers/openai-compatible.md @@ -14,6 +14,9 @@ This document focuses on setting up providers _other than_ the official OpenAI A ## General Configuration +{% tabs %} +{% tab label="VSCode (Legacy)" %} + The key to using an OpenAI-compatible provider is to configure two main settings: 1. **Base URL:** This is the API endpoint for the provider. It will _not_ be `https://api.openai.com/v1` (that's for the official OpenAI API). @@ -34,6 +37,48 @@ You'll find these settings in the Kilo Code settings panel (click the {% codicon - Input Price - Output Price +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add an OpenAI Compatible provider. Enter your API key and the provider's base URL. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key and base URL as environment variables or configure them in your `kilo.json` config file: + +**Environment variable:** + +```bash +export OPENAI_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "openai-compatible": { + "env": ["OPENAI_API_KEY"], + "baseURL": "https://api.your-provider.com/v1", + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "openai-compatible/model-name", +} +``` + +{% /tab %} +{% /tabs %} + ### Full Endpoint URL Support Kilo Code supports full endpoint URLs in the Base URL field, providing greater flexibility for provider configuration: diff --git a/packages/kilo-docs/pages/ai-providers/openai.md b/packages/kilo-docs/pages/ai-providers/openai.md index 73dbb8904c7..4c5a379b539 100644 --- a/packages/kilo-docs/pages/ai-providers/openai.md +++ b/packages/kilo-docs/pages/ai-providers/openai.md @@ -17,11 +17,55 @@ Kilo Code supports accessing models directly through the official OpenAI API. ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "OpenAI" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your OpenAI API key into the "OpenAI API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add OpenAI and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export OPENAI_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "openai": { + "env": ["OPENAI_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "openai/gpt-4.1", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Pricing:** Refer to the [OpenAI Pricing](https://openai.com/pricing) page for details on model costs. diff --git a/packages/kilo-docs/pages/ai-providers/openrouter.md b/packages/kilo-docs/pages/ai-providers/openrouter.md index 84019fa69e9..bdcbbf1a528 100644 --- a/packages/kilo-docs/pages/ai-providers/openrouter.md +++ b/packages/kilo-docs/pages/ai-providers/openrouter.md @@ -16,12 +16,56 @@ OpenRouter is an AI platform that provides access to a wide variety of language ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "OpenRouter" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your OpenRouter API key into the "OpenRouter API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. 5. **(Optional) Custom Base URL:** If you need to use a custom base URL for the OpenRouter API, check "Use custom base URL" and enter the URL. Leave this blank for most users. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add OpenRouter and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export OPENROUTER_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "openrouter": { + "env": ["OPENROUTER_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "openrouter/anthropic/claude-sonnet-4-20250514", +} +``` + +{% /tab %} +{% /tabs %} + ## Supported Transforms OpenRouter provides an [optional "middle-out" message transform](https://openrouter.ai/docs/features/message-transforms) to help with prompts that exceed the maximum context size of a model. You can enable it by checking the "Compress prompts and message chains to the context size" box. diff --git a/packages/kilo-docs/pages/ai-providers/ovhcloud.md b/packages/kilo-docs/pages/ai-providers/ovhcloud.md index 344cb130e9b..7b9890a2598 100644 --- a/packages/kilo-docs/pages/ai-providers/ovhcloud.md +++ b/packages/kilo-docs/pages/ai-providers/ovhcloud.md @@ -24,7 +24,51 @@ You can report any bugs or feedbacks by chatting with us in our [Discord server] ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "OVHcloud AI Endpoints" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your AI Endpoints API key into the "OVHcloud AI Endpoints API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. + +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add OVHcloud AI Endpoints and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export OVHCLOUD_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "ovhcloud": { + "env": ["OVHCLOUD_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "ovhcloud/model-name", +} +``` + +{% /tab %} +{% /tabs %} diff --git a/packages/kilo-docs/pages/ai-providers/requesty.md b/packages/kilo-docs/pages/ai-providers/requesty.md index 6fc4a656d9a..fda3c5796e3 100644 --- a/packages/kilo-docs/pages/ai-providers/requesty.md +++ b/packages/kilo-docs/pages/ai-providers/requesty.md @@ -15,11 +15,55 @@ Kilo Code supports accessing models through the [Requesty](https://www.requesty. ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Requesty" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Requesty API key into the "Requesty API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Requesty and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export REQUESTY_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "requesty": { + "env": ["REQUESTY_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "requesty/anthropic/claude-sonnet-4-20250514", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Optimizations**: Requesty offers range of in-flight cost optimizations to lower your costs. diff --git a/packages/kilo-docs/pages/ai-providers/sap-ai-core.md b/packages/kilo-docs/pages/ai-providers/sap-ai-core.md index 6c110a58cc8..16dfec8bbdf 100644 --- a/packages/kilo-docs/pages/ai-providers/sap-ai-core.md +++ b/packages/kilo-docs/pages/ai-providers/sap-ai-core.md @@ -77,6 +77,9 @@ The exact list of available models depends on your SAP AI Core configuration and ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "SAP AI Core" from the "API Provider" dropdown. 3. **Enter Credentials:** @@ -91,6 +94,47 @@ The exact list of available models depends on your SAP AI Core configuration and 5. **Select Model:** Choose your desired model from the dropdown 6. **Select Deployment:** (Foundation Models Mode only) Choose an active deployment for your selected model +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add SAP AI Core. Enter your OAuth2 client credentials (Client ID, Client Secret, Base URL, and Auth URL) in the provider settings. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +SAP AI Core uses OAuth2 client credentials for authentication. Set the credentials as environment variables or in your config file: + +**Environment variables:** + +```bash +export AICORE_SERVICE_KEY='{"your": "service-key-json"}' +export AICORE_DEPLOYMENT_ID="your-deployment-id" +export AICORE_RESOURCE_GROUP="your-resource-group" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "sap-ai-core": {}, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "sap-ai-core/model-name", +} +``` + +{% /tab %} +{% /tabs %} + ## Deployments (Foundation Models Mode) When using Foundation Models mode: diff --git a/packages/kilo-docs/pages/ai-providers/unbound.md b/packages/kilo-docs/pages/ai-providers/unbound.md index 11e061a91ab..fd7f6fb6e27 100644 --- a/packages/kilo-docs/pages/ai-providers/unbound.md +++ b/packages/kilo-docs/pages/ai-providers/unbound.md @@ -20,11 +20,31 @@ Unbound allows you configure a list of supported models in your application, and ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Unbound" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Unbound API key into the "Unbound API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Unbound and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +{% callout type="warning" %} +Unbound is not yet available as a CLI provider. Check the [Kilo Code releases](https://github.com/Kilo-Org/kilocode/releases) for updates on provider support. +{% /callout %} + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Security Focus:** Unbound emphasizes security features for enterprise use. If your organization has strict security requirements for AI usage, Unbound might be a good option. diff --git a/packages/kilo-docs/pages/ai-providers/v0.md b/packages/kilo-docs/pages/ai-providers/v0.md index 5d8d0280bc3..2ba2ba4ac67 100644 --- a/packages/kilo-docs/pages/ai-providers/v0.md +++ b/packages/kilo-docs/pages/ai-providers/v0.md @@ -15,6 +15,9 @@ To use v0 with Kilo Code, you'll need: ## Configuration +{% tabs %} +{% tab label="VSCode (Legacy)" %} + Setting up v0 in Kilo Code is straightforward: 1. In Kilo Code settings (click the {% codicon name="gear" /%} icon): @@ -26,6 +29,48 @@ Setting up v0 in Kilo Code is straightforward: +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add an OpenAI Compatible provider. Set the base URL to `https://api.v0.dev/v1` and enter your v0 API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +v0 uses the OpenAI-compatible provider. Set the API key and base URL in your config: + +**Environment variable:** + +```bash +export OPENAI_API_KEY="your-v0-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "openai-compatible": { + "env": ["OPENAI_API_KEY"], + "baseURL": "https://api.v0.dev/v1", + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "openai-compatible/v0-1.0-md", +} +``` + +{% /tab %} +{% /tabs %} + ## Troubleshooting - **"Invalid API Key":** Double-check that you've entered the API key correctly. diff --git a/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md b/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md index 3147a1ac9a2..bbfd9ccf991 100644 --- a/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md +++ b/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md @@ -54,11 +54,55 @@ Check the model description in the dropdown for specific capabilities. ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "Vercel AI Gateway" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your Vercel AI Gateway API key into the "Vercel AI Gateway API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Vercel AI Gateway and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export AI_GATEWAY_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "vercel": { + "env": ["AI_GATEWAY_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "vercel/anthropic/claude-sonnet-4", +} +``` + +{% /tab %} +{% /tabs %} + --- ## Prompt Caching diff --git a/packages/kilo-docs/pages/ai-providers/vertex.md b/packages/kilo-docs/pages/ai-providers/vertex.md index 994a84b7b31..abbb56da3b8 100644 --- a/packages/kilo-docs/pages/ai-providers/vertex.md +++ b/packages/kilo-docs/pages/ai-providers/vertex.md @@ -20,6 +20,9 @@ Kilo Code supports accessing models through Google Cloud Platform's Vertex AI, a ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "GCP Vertex AI" from the "API Provider" dropdown. 3. **Configure Authentication:** @@ -31,6 +34,50 @@ Kilo Code supports accessing models through Google Cloud Platform's Vertex AI, a 5. **Select Region:** Choose the region where your Vertex AI resources are located (e.g., `us-east5`). 6. **Select Model:** Choose your desired model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add GCP Vertex AI. The extension uses Google Application Default Credentials (ADC) for authentication — run `gcloud auth application-default login` before adding the provider. Set your project ID and region in the provider settings. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Vertex AI uses Google Application Default Credentials (ADC) for authentication. Set up ADC using the Google Cloud CLI: + +```bash +gcloud auth application-default login +``` + +Set your project and region as environment variables: + +```bash +export GOOGLE_CLOUD_PROJECT="your-project-id" +export GOOGLE_CLOUD_LOCATION="us-east5" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "google-vertex": {}, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "google-vertex/claude-sonnet-4@20250514", +} +``` + +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Permissions:** Ensure your Google Cloud account has the necessary permissions to access Vertex AI and the specific models you want to use. diff --git a/packages/kilo-docs/pages/ai-providers/xai.md b/packages/kilo-docs/pages/ai-providers/xai.md index 309bd4f0c35..5435c6c7ecd 100644 --- a/packages/kilo-docs/pages/ai-providers/xai.md +++ b/packages/kilo-docs/pages/ai-providers/xai.md @@ -17,11 +17,55 @@ xAI is the company behind Grok, a large language model known for its conversatio ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "xAI" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your xAI API key into the "xAI API Key" field. 4. **Select Model:** Choose your desired Grok model from the "Model" dropdown. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add xAI and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export XAI_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "xai": { + "env": ["XAI_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "xai/grok-3", +} +``` + +{% /tab %} +{% /tabs %} + ## Reasoning Capabilities Some models feature specialized reasoning capabilities, allowing them to "think before responding" - particularly useful for complex problem-solving tasks. diff --git a/packages/kilo-docs/pages/ai-providers/zenmux.md b/packages/kilo-docs/pages/ai-providers/zenmux.md index 1e646d1b258..de7985830eb 100644 --- a/packages/kilo-docs/pages/ai-providers/zenmux.md +++ b/packages/kilo-docs/pages/ai-providers/zenmux.md @@ -16,12 +16,56 @@ import Codicon from "@site/src/components/Codicon"; ## Configuration in Kilo Code +{% tabs %} +{% tab label="VSCode (Legacy)" %} + 1. **Open Kilo Code Settings:** Click the gear icon () in the Kilo Code panel. 2. **Select Provider:** Choose "ZenMux" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your ZenMux API key into the "ZenMux API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. 5. **(Optional) Custom Base URL:** If you need to use a custom base URL for the ZenMux API, check "Use custom base URL" and enter the URL. Leave this blank for most users. +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add ZenMux and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export ZENMUX_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "zenmux": { + "env": ["ZENMUX_API_KEY"], + }, + }, +} +``` + +Then set your default model: + +```jsonc +{ + "model": "zenmux/openai/gpt-5", +} +``` + +{% /tab %} +{% /tabs %} + ## Supported Models ZenMux supports a wide range of models from various providers: diff --git a/packages/kilo-docs/pages/automate/agent-manager.md b/packages/kilo-docs/pages/automate/agent-manager.md index 5f47025343d..17162a67172 100644 --- a/packages/kilo-docs/pages/automate/agent-manager.md +++ b/packages/kilo-docs/pages/automate/agent-manager.md @@ -5,6 +5,108 @@ description: "Manage and orchestrate multiple AI agents" # Agent Manager +The Agent Manager is a control panel for running and orchestrating multiple Kilo Code agents, with support for parallel worktree-isolated sessions. + +{% tabs %} +{% tab label="VSCode" %} + +The Agent Manager is a **full-panel editor tab** built directly into the extension. All sessions share the single `kilo serve` backend process. It supports: + +- Multiple parallel sessions, each in its own git worktree +- A diff/review panel showing changes vs. the parent branch +- Dedicated VS Code integrated terminals per session +- Setup scripts and `.env` auto-copy on worktree creation +- Session import from existing branches, external worktrees, or GitHub PR URLs +- "Continue in Worktree" to promote a sidebar session to the Agent Manager + +## Opening the Agent Manager + +- Keyboard shortcut: `Cmd+Shift+M` (macOS) / `Ctrl+Shift+M` (Windows/Linux) +- Command Palette: "Kilo Code: Open Agent Manager" +- Click the Agent Manager icon in the sidebar toolbar + +The panel opens as an editor tab and stays active across focus changes. + +## Working with Worktrees + +Each Agent Manager session runs in an isolated git worktree on a separate branch, keeping your main branch clean. + +### Creating a New Worktree Session + +1. Click **New Worktree** or press `Cmd+Shift+W` to create a new worktree +2. Enter a branch name (or let Kilo generate one) +3. Type your first message to start the agent + +A new git worktree is created from your current branch. The agent works in isolation — your main branch is unaffected. + +### Multi-Version Mode + +You can run up to 4 parallel implementations of the same prompt across separate worktrees: + +1. Click the multi-version button and enter a prompt +2. Optionally assign different models to each version +3. Kilo creates one worktree + session per version and runs them in parallel + +### Importing Existing Work + +- **From a branch:** Import an existing git branch as a worktree +- **From a GitHub PR URL:** Paste a PR URL to import it as a worktree +- **From an external worktree:** Import a worktree that already exists on disk +- **Continue in Worktree:** From the sidebar chat, promote the current session to a new Agent Manager worktree + +## Sending Messages, Approvals, and Control + +- **Continue the conversation:** Send a follow-up message to the running agent +- **Approvals:** The Permission Dock shows tool approval prompts — approve once, approve always, or deny +- **Cancel:** Sends a cooperative stop signal to the agent +- **Stop:** Force-terminates the session and marks it as stopped + +## Diff / Review Panel + +Press `Cmd+Alt+D` to toggle the diff panel. It shows a live-updating diff between the worktree and its parent branch. + +- Select files and click **Apply to Main Branch** to merge changes +- Conflicts are surfaced with a resolution dialog +- Supports unified and split diff views + +## Terminals + +Each session has a dedicated integrated terminal. Press `Cmd+Alt+T` to focus the terminal for the active session. + +## Setup Scripts + +Place an executable script at `.kilo/setup-script` in your project root. It runs automatically whenever a new worktree is created (useful for `npm install`, env setup, etc.). `.env` files are also auto-copied from the main repo. + +## Session State and Persistence + +Agent Manager state is persisted in `.kilo/agent-manager.json`. Sessions, worktrees, and their order are restored on reload. + +## Keyboard Shortcuts (Agent Manager Panel) + +| Shortcut | Action | +| ----------------- | --------------------------------- | +| `Cmd+Shift+M` | Open Agent Manager | +| `Cmd+Shift+W` | New worktree | +| `Cmd+Shift+O` | Import/open worktree | +| `Cmd+Shift+X` | Close current worktree | +| `Cmd+N` | New tab (session) in worktree | +| `Cmd+W` | Close current tab | +| `Cmd+Alt+[` / `]` | Previous / next worktree | +| `Cmd+Alt+,` / `.` | Previous / next tab in worktree | +| `Cmd+Alt+T` | Show terminal for current session | +| `Cmd+Alt+D` | Toggle diff panel | +| `Cmd+Alt+?` | Show keyboard shortcuts | +| `Cmd+1` … `Cmd+9` | Jump to worktree/session by index | + +## Troubleshooting + +- **"Please open a folder…" error** — the Agent Manager requires a VS Code workspace folder +- **"Cannot use parallel mode from within a git worktree"** — open the main repository (where `.git` is a directory), not a worktree checkout +- **Worktree creation fails** — ensure Git is installed and the workspace is a valid git repository + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + The Agent Manager is a dedicated control panel for running and supervising Kilo Code agents as interactive CLI processes. It supports: - Local sessions @@ -22,7 +124,7 @@ This page reflects the actual implementation in the extension. ## Opening the Agent Manager -- Command Palette: “Kilo Code: Open Agent Manager” +- Command Palette: "Kilo Code: Open Agent Manager" - Or use the title/menu entry if available in your Kilo Code UI The panel opens as a webview and stays active across focus changes. @@ -34,13 +136,13 @@ The panel opens as a webview and stays active across focus changes. - Approve or reject, optionally adding a short note - Cancel vs Stop - Cancel sends a structured cancel message to the running process (clean cooperative stop) - - Stop force-terminates the underlying CLI process, updating status to “stopped” + - Stop force-terminates the underlying CLI process, updating status to "stopped" ## Resuming an existing session You can continue a session later (local or remote): -- If a session is not currently running, the Agent Manager will spawn a new CLI process attached to that session’s ID +- If a session is not currently running, the Agent Manager will spawn a new CLI process attached to that session's ID - Labels from the original session are preserved whenever possible - Your first follow-up message becomes the continuation input @@ -144,6 +246,9 @@ Message transcripts are fetched from a signed blob and exclude internal checkpoi - Verify you're logged in via extension settings or using CLI with kilocode provider - BYOK configurations do not support Agent Manager authentication +{% /tab %} +{% /tabs %} + ## Related features - [Sessions](/docs/collaborate/sessions-sharing) diff --git a/packages/kilo-docs/pages/automate/extending/shell-integration.md b/packages/kilo-docs/pages/automate/extending/shell-integration.md index 5c7a88187ac..4f1e3312846 100644 --- a/packages/kilo-docs/pages/automate/extending/shell-integration.md +++ b/packages/kilo-docs/pages/automate/extending/shell-integration.md @@ -7,6 +7,9 @@ description: "Integrate Kilo Code with your shell environment" Terminal Shell Integration is a key feature that enables Kilo Code to execute commands in your terminal and intelligently process their output. This bidirectional communication between the AI and your development environment unlocks powerful automation capabilities. +{% tabs %} +{% tab label="VSCode (Legacy)" %} + ## What is Shell Integration? Shell integration is automatically enabled in Kilo Code and connects directly to your terminal's command execution lifecycle without requiring any setup from you. This built-in feature allows Kilo Code to: @@ -340,6 +343,80 @@ The [VS Code Terminal Integration Test Extension](https://github.com/KJ7LNW/vsce - Note your environment (OS, VS Code version, shell, and any shell prompt customization) - Open an issue with these details to help improve shell integration +{% /tab %} +{% tab label="VSCode & CLI" %} + +## How Shell Execution Works + +The new CLI and extension take a fundamentally different approach to shell execution. Instead of relying on VS Code's terminal shell integration, the CLI spawns and manages shell processes directly using the `bash` tool. + +This means: + +- **No VS Code shell integration required** — the CLI handles shell execution independently +- **No shell integration setup or troubleshooting** — it works out of the box +- **Consistent behavior** across environments — the same shell execution logic runs whether you use the CLI directly or through the VS Code extension + +## The `bash` Tool + +The `bash` tool is the primary way the agent executes shell commands. It spawns a persistent shell session and runs commands within it. + +### Key Features + +- **Working directory control**: Use the `workdir` parameter to run commands in a specific directory, instead of `cd && ` patterns +- **Configurable timeout**: Set a per-command timeout in milliseconds (defaults to 2 minutes) +- **Real-time output streaming**: Command output is streamed back as it's produced +- **Process tree management**: The tool manages the full process tree, ensuring child processes are properly cleaned up + +### Security Analysis + +Commands are parsed using **Tree-sitter** before execution, enabling: + +- Path resolution to detect file access patterns +- External directory detection to flag commands that reach outside the project +- Structured analysis of command intent for safer auto-approval decisions + +### Shell Detection + +The CLI automatically detects the appropriate shell for your platform using `Shell.acceptable()`. This selects a compatible shell (bash, zsh, etc.) without requiring manual configuration. + +## Agent Manager Terminals (VS Code Extension) + +When using the Kilo Code VS Code extension with the Agent Manager, each agent session gets its own dedicated VS Code terminal. + +### Per-Session Terminals + +- Each session creates a terminal named **`Agent: {branch}`**, where `{branch}` is the git branch or worktree the session is working in +- The terminal's working directory is automatically set to the session's worktree directory +- Terminals are standard VS Code integrated terminals — you can interact with them directly + +### Keyboard Shortcuts + +| Shortcut | Action | +| --------------------------- | ---------------------------- | +| Cmd+/ | Focus the session's terminal | +| Cmd+. | Cycle agent mode | + +### Terminal Context Menu Actions + +Right-click in an Agent Manager terminal to access these actions: + +- **Add Terminal Content to Context** — sends the terminal's visible output to the agent as context +- **Fix This Command** — asks the agent to diagnose and fix the last failed command +- **Explain This Command** — asks the agent to explain what a command does + +## Troubleshooting + +Shell execution in the new CLI is significantly simpler than the **VSCode** version's terminal integration. Most issues are resolved by ensuring: + +1. **A supported shell is installed**: bash or zsh on macOS/Linux, PowerShell on Windows +2. **The shell is on your PATH**: The CLI needs to find the shell binary +3. **File permissions are correct**: The CLI needs execute permission on the shell binary + +If commands fail to execute, check the CLI's log output for error details. The CLI logs the shell it detected and any errors during command execution. + +{% /tab %} +{% /tabs %} + ## Support If you've followed these steps and are still experiencing problems, please: diff --git a/packages/kilo-docs/pages/automate/how-tools-work.md b/packages/kilo-docs/pages/automate/how-tools-work.md index ea7d88abe47..5086fdb075d 100644 --- a/packages/kilo-docs/pages/automate/how-tools-work.md +++ b/packages/kilo-docs/pages/automate/how-tools-work.md @@ -18,6 +18,20 @@ Describe what you want to accomplish in natural language, and Kilo Code will: ## Tool Categories +{% tabs %} +{% tab label="VSCode" %} + +| Category | Purpose | Tool Names | +| :------- | :----------------------------------------- | :----------------------------------------------------------- | +| Read | Access file content and code structure | `read`, `glob`, `grep` | +| Edit | Create or modify files and code | `edit`, `multiedit`, `write`, `apply_patch` | +| Execute | Run commands and perform system operations | `bash` | +| Web | Fetch and search web content | `webfetch`, `websearch`, `codesearch` | +| Workflow | Manage task flow and sub-agents | `question`, `task`, `todowrite`, `todoread`, `plan`, `skill` | + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + | Category | Purpose | Tool Names | | :------- | :----------------------------------------- | :----------------------------------------------------------------------- | | Read | Access file content and code structure | `read_file`, `search_files`, `list_files`, `list_code_definition_names` | @@ -26,10 +40,29 @@ Describe what you want to accomplish in natural language, and Kilo Code will: | Browser | Interact with web content | `browser_action` | | Workflow | Manage task flow and context | `ask_followup_question`, `attempt_completion`, `switch_mode`, `new_task` | +{% /tab %} +{% /tabs %} + ## Example: Using Tools Here's how a typical tool interaction works: +{% tabs %} +{% tab label="VSCode" %} + +{% callout type="info" title="Tool Approval UI" %} +When a tool is proposed, you'll see an approval prompt in the **Permission Dock** at the bottom of the chat. You can approve once, approve always (saves to config), or deny. +{% /callout %} + +**User:** Create a file named `greeting.js` that logs a greeting message + +**Kilo Code:** (Proposes the `write` tool) + +The extension shows the file path and proposed content for review. Click **Approve** to execute or **Deny** to cancel. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + {% callout type="info" title="Tool Approval UI" %} When a tool is proposed, you'll see Save and Reject buttons along with an optional Auto-approve checkbox for trusted operations. {% /callout %} @@ -56,8 +89,55 @@ greet('World'); **Kilo Code:** (Confirms file creation) +{% /tab %} +{% /tabs %} + ## Tool Safety and Approval +{% tabs %} +{% tab label="VSCode" %} + +Every tool use is subject to a permission check. The default action for any tool with no matching rule in your config is **`ask`** — meaning Kilo will pause and prompt you before executing it. + +**Default permissions by tool:** + +| Tool(s) | Default | +| :------------------------------------------- | :----------------------------------------------- | +| `read`, `glob`, `grep`, `list` | `ask` | +| `edit`, `write`, `multiedit`, `apply_patch` | `ask` | +| `bash` | `ask` (per-command) | +| `external_directory` | `ask` (when accessing paths outside the project) | +| `task` | `ask` | +| `webfetch`, `websearch`, `codesearch` | `ask` | +| `todowrite`, `todoread`, `question`, `skill` | `ask` | + +No tools are auto-approved out of the box. You must explicitly grant `allow` in your config, or approve them at runtime. + +**At runtime**, the **Permission Dock** floating UI in the chat panel shows each pending approval. For each tool call you can: + +- **Approve once** — execute this call only +- **Approve always** — save an `allow` rule to your config so future matching calls are auto-approved +- **Deny** — cancel the tool call + +To pre-configure permissions in your config file: + +```json +{ + "permission": { + "read": "allow", + "glob": "allow", + "grep": "allow", + "edit": "ask", + "bash": "ask" + } +} +``` + +This safety mechanism ensures you maintain control over which files are modified, what commands are executed, and how your codebase is changed. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Every tool use requires your explicit approval. When Kilo proposes a tool, you'll see: - A "Save" button to approve and execute the tool @@ -66,8 +146,37 @@ Every tool use requires your explicit approval. When Kilo proposes a tool, you'l This safety mechanism ensures you maintain control over which files are modified, what commands are executed, and how your codebase is changed. Always review tool proposals carefully before saving them. +{% /tab %} +{% /tabs %} + ## Core Tools Reference +{% tabs %} +{% tab label="VSCode" %} + +| Tool Name | Description | Category | +| :------------ | :----------------------------------------------------- | :------- | +| `read` | Reads file contents with line numbers | Read | +| `glob` | Finds files by glob pattern | Read | +| `grep` | Searches file contents with regex | Read | +| `edit` | Makes precise text replacements in a file | Edit | +| `multiedit` | Multiple edits in a single call | Edit | +| `write` | Creates new files or overwrites existing ones | Edit | +| `apply_patch` | Applies unified diffs (used with certain models) | Edit | +| `bash` | Runs shell commands | Execute | +| `webfetch` | Fetches a URL | Web | +| `websearch` | Searches the web (Kilo/OpenRouter users) | Web | +| `codesearch` | Semantic code search (Kilo/OpenRouter users) | Web | +| `question` | Asks you a clarifying question with selectable options | Workflow | +| `task` | Spawns a sub-agent session | Workflow | +| `todowrite` | Creates and updates a session TODO list | Workflow | +| `todoread` | Reads the current session TODO list | Workflow | +| `plan` | Enters structured planning mode | Workflow | +| `skill` | Invokes a reusable skill (Markdown instruction module) | Workflow | + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + | Tool Name | Description | Category | | :--------------------------- | :-------------------------------------------------- | :------- | | `read_file` | Reads the content of a file with line numbers | Read | @@ -84,6 +193,9 @@ This safety mechanism ensures you maintain control over which files are modified | `switch_mode` | Changes to a different operational mode | Workflow | | `new_task` | Creates a new subtask with a specific starting mode | Workflow | +{% /tab %} +{% /tabs %} + ## Learn More About Tools For more detailed information about each tool, including complete parameter references and advanced usage patterns, see the [Tool Use Overview](/docs/automate/tools) documentation. diff --git a/packages/kilo-docs/pages/automate/index.md b/packages/kilo-docs/pages/automate/index.md index 453f0163c50..d71a6dce541 100644 --- a/packages/kilo-docs/pages/automate/index.md +++ b/packages/kilo-docs/pages/automate/index.md @@ -36,7 +36,6 @@ Connect Kilo Code to external tools and services: - [**Using MCP in Kilo Code**](/docs/automate/mcp/using-in-kilo-code) — Configuration guide - [**STDIO & SSE Transports**](/docs/automate/mcp/server-transports) — Local and remote server options - [**MCP vs API**](/docs/automate/mcp/mcp-vs-api) — When to use MCP -- [**Using MCP in CLI**](/docs/automate/mcp/using-in-cli) — CLI-specific MCP setup ## Integrations diff --git a/packages/kilo-docs/pages/automate/mcp/using-in-kilo-code.md b/packages/kilo-docs/pages/automate/mcp/using-in-kilo-code.md index 43dd907c755..72f9994d1db 100644 --- a/packages/kilo-docs/pages/automate/mcp/using-in-kilo-code.md +++ b/packages/kilo-docs/pages/automate/mcp/using-in-kilo-code.md @@ -7,31 +7,103 @@ description: "How to use MCP servers in Kilo Code" Model Context Protocol (MCP) extends Kilo Code's capabilities by connecting to external tools and services. This guide covers everything you need to know about using MCP with Kilo Code. -{% youtube url="https://youtu.be/6O9RQoQRX8A" caption="Demostrating MCP installation in Kilo Code" /%} +{% youtube url="https://youtu.be/6O9RQoQRX8A" caption="Demonstrating MCP installation in Kilo Code" /%} ## Configuring MCP Servers -MCP server configurations can be managed at two levels: +{% tabs %} +{% tab label="VSCode" %} -1. **Global Configuration**: Stored in the `mcp_settings.json` file, accessible via VS Code settings (see below). These settings apply across all your workspaces unless overridden by a project-level configuration. -2. **Project-level Configuration**: Defined in a `.kilocode/mcp.json` file within your project's root directory. This allows you to set up project-specific servers and share configurations with your team by committing the file to version control. Kilo Code automatically detects and loads this file if it exists. +MCP server configurations are stored inside the main Kilo config file. There are two levels: -**Precedence**: If a server name exists in both global and project configurations, the **project-level configuration takes precedence**. +1. **Global Configuration**: `~/.config/kilo/kilo.jsonc` — applies to all projects. +2. **Project-level Configuration**: `kilo.jsonc` in your project root, or `.kilo/kilo.jsonc` for a cleaner setup. -### Editing MCP Settings Files +**Precedence**: Project-level configuration takes precedence over global configuration. -You can edit both global and project-level MCP configuration files directly from the Kilo Code settings. +### Editing MCP Settings -1. Click the {% codicon name="gear" /%} icon in the top navigation of the Kilo Code pane to open `Settings`. -2. Click the `Agent Behaviour` tab on the left side -3. Select the `MCP Servers` sub-tab -4. Click the appropriate button: - - **`Edit Global MCP`**: Opens the global `mcp_settings.json` file. - - **`Edit Project MCP`**: Opens the project-specific `.kilocode/mcp.json` file. If this file doesn't exist, Kilo Code will create it for you. +You can edit MCP settings from the Kilo Code settings UI: -{% image src="/docs/img/using-mcp-in-kilo-code/mcp-installed-config.png" alt="Edit Global MCP and Edit Project MCP buttons" width="600" caption="Edit Global MCP and Edit Project MCP buttons" /%} +1. Click the {% codicon name="gear" /%} icon in the sidebar toolbar to open Settings. +2. Click the `Agent Behaviour` tab on the left side. +3. Select the `MCP Servers` sub-tab. -Both files use a JSON format with a `mcpServers` object containing named server configurations: +From here you can add, edit, enable/disable, and delete MCP servers. Changes are written directly to the appropriate config file. + +### Config Format + +MCP servers are configured under the `mcp` key in `kilo.jsonc`: + +**Local (STDIO) server:** + +```json +{ + "mcp": { + "my-local-server": { + "type": "local", + "command": ["node", "/path/to/server.js"], + "environment": { + "API_KEY": "your_api_key" + }, + "enabled": true, + "timeout": 10000 + } + } +} +``` + +**Remote (HTTP/SSE) server:** + +```json +{ + "mcp": { + "my-remote-server": { + "type": "remote", + "url": "https://your-server-url.com/mcp", + "headers": { + "Authorization": "Bearer your-token" + }, + "enabled": true, + "timeout": 15000 + } + } +} +``` + +Remote servers support OAuth 2.0 authentication. If the server supports it, Kilo Code will automatically start the OAuth flow when you connect. You can also disable OAuth with `"oauth": false`. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +MCP server configurations can be managed at two levels: **global** (applies across all workspaces) and **project-level** (specific to a single project). Project-level configuration takes precedence over global settings. + +| Scope | Path | Description | +| ----------- | -------------------- | --------------------------------------------------------------- | +| **Global** | `mcp_settings.json` | Accessible via VS Code settings. Applies across all workspaces. | +| **Project** | `.kilocode/mcp.json` | In your project root. Auto-detected by Kilo Code. | + +Project-level configs can be committed to version control to share with your team. + +{% /tab %} +{% tab label="CLI" %} + +The CLI accepts several config filenames. The recommended file is `kilo.json`: + +| Scope | Recommended Path | Also supported | +| ----------- | ------------------------------------ | -------------------------------------------------------------- | +| **Global** | `~/.config/kilo/kilo.json` | `kilo.jsonc`, `opencode.json`, `opencode.jsonc`, `config.json` | +| **Project** | `./kilo.json` or `./.kilo/kilo.json` | `kilo.jsonc`, `opencode.jsonc`, `opencode.json` | + +{% /tab %} +{% /tabs %} + +## Configuration Format + +{% tabs %} +{% tab label="VSCode (Legacy)" %} + +Both global and project-level files use a JSON format with a `mcpServers` object containing named server configurations: ```json { @@ -51,11 +123,43 @@ Both files use a JSON format with a `mcpServers` object containing named server _Example of MCP Server config in Kilo Code (STDIO Transport)_ -### Understanding Transport Types +{% /tab %} +{% tab label="VSCode" %} -MCP supports three transport types for server communication: +In the VS Code extension, open **Settings → MCP** and click **Add Server** to configure a new server through the UI. You can also edit the config files directly — see the **CLI** tab for the JSON format. -#### STDIO Transport +{% /tab %} +{% tab label="CLI" %} + +Add MCP servers under the `mcp` key in your config file. Each server has a unique name that you can reference in prompts. + +```json +{ + "mcp": { + "my-server": { + "type": "local", + "command": ["npx", "-y", "my-mcp-command"], + "enabled": true + } + } +} +``` + +You can disable a server by setting `enabled` to `false` without removing it from your config. + +{% /tab %} +{% /tabs %} + +## Understanding Transport Types + +MCP supports two main transport types: + +- **Local (STDIO)**: Servers run as a child process on your machine, communicating over stdin/stdout. +- **Remote (HTTP/SSE)**: Servers hosted over HTTP/HTTPS. Kilo Code tries `StreamableHTTP` first, then falls back to `SSE` automatically. + +For more details, see [STDIO & SSE Transports](server-transports). + +### STDIO Transport Used for local servers running on your machine: @@ -69,6 +173,9 @@ For more in-depth information about how STDIO transport works, see [STDIO Transp STDIO configuration example: +{% tabs %} +{% tab label="VSCode (Legacy)" %} + ```json { "mcpServers": { @@ -85,7 +192,43 @@ STDIO configuration example: } ``` -#### Streamable HTTP Transport +{% /tab %} +{% tab label="VSCode" %} + +In the VS Code extension, open **Settings → MCP**, click **Add Server**, and choose **Local (stdio)**. Fill in the command, arguments, and optional environment variables through the UI. You can also edit the config files directly — see the **CLI** tab for the JSON format. + +{% /tab %} +{% tab label="CLI" %} + +```json +{ + "mcp": { + "my-local-server": { + "type": "local", + "command": ["npx", "-y", "my-mcp-command"], + "enabled": true, + "environment": { + "API_KEY": "your_api_key" + } + } + } +} +``` + +#### Local Server Options + +| Option | Type | Required | Description | +| ------------- | ------- | -------- | --------------------------------------------------------------------- | +| `type` | String | Yes | Must be `"local"`. | +| `command` | Array | Yes | Command and arguments to run the MCP server. | +| `environment` | Object | No | Environment variables to set when running the server. | +| `enabled` | Boolean | No | Enable or disable the MCP server on startup. | +| `timeout` | Number | No | Timeout in ms for fetching tools from the MCP server. Default: 30000. | + +{% /tab %} +{% /tabs %} + +### Streamable HTTP Transport Used for remote servers accessed over HTTP/HTTPS: @@ -94,7 +237,8 @@ Used for remote servers accessed over HTTP/HTTPS: - Requires network access - Allows centralized deployment and management -Streamable HTTP transport configuration example: +{% tabs %} +{% tab label="VSCode (Legacy)" %} ```json { @@ -112,7 +256,43 @@ Streamable HTTP transport configuration example: } ``` -#### SSE Transport +{% /tab %} +{% tab label="VSCode" %} + +In the VS Code extension, open **Settings → MCP**, click **Add Server**, and choose **Remote (HTTP)**. Enter the server URL and optional headers through the UI. You can also edit the config files directly — see the **CLI** tab for the JSON format. + +{% /tab %} +{% tab label="CLI" %} + +```json +{ + "mcp": { + "my-remote-server": { + "type": "remote", + "url": "https://my-mcp-server.com/mcp", + "enabled": true, + "headers": { + "Authorization": "Bearer MY_API_KEY" + } + } + } +} +``` + +#### Remote Server Options + +| Option | Type | Required | Description | +| --------- | ------- | -------- | --------------------------------------------------------------------- | +| `type` | String | Yes | Must be `"remote"`. | +| `url` | String | Yes | URL of the remote MCP server. | +| `enabled` | Boolean | No | Enable or disable the MCP server on startup. | +| `headers` | Object | No | HTTP headers to send with requests. | +| `timeout` | Number | No | Timeout in ms for fetching tools from the MCP server. Default: 30000. | + +{% /tab %} +{% /tabs %} + +### SSE Transport ⚠️ DEPRECATED: The SSE Transport has been deprecated as of MCP specification version 2025-03-26. Please use the HTTP Stream Transport instead, which implements the new Streamable HTTP transport specification. @@ -143,6 +323,24 @@ SSE configuration example: } ``` +## Managing MCP Servers + +{% tabs %} +{% tab label="VSCode (Legacy)" %} + +### Editing MCP Settings Files + +You can edit both global and project-level MCP configuration files directly from the Kilo Code settings. + +1. Click the {% codicon name="gear" /%} icon in the top navigation of the Kilo Code pane to open `Settings`. +2. Click the `Agent Behaviour` tab on the left side +3. Select the `MCP Servers` sub-tab +4. Click the appropriate button: + - **`Edit Global MCP`**: Opens the global `mcp_settings.json` file. + - **`Edit Project MCP`**: Opens the project-specific `.kilocode/mcp.json` file. If this file doesn't exist, Kilo Code will create it for you. + +{% image src="/docs/img/using-mcp-in-kilo-code/mcp-installed-config.png" alt="Edit Global MCP and Edit Project MCP buttons" width="600" caption="Edit Global MCP and Edit Project MCP buttons" /%} + ### Deleting a Server 1. Press the {% codicon name="trash" /%} next to the MCP server you would like to delete @@ -158,16 +356,95 @@ SSE configuration example: 1. Press the {% codicon name="activate" /%} toggle switch next to the MCP server to enable/disable it +{% /tab %} +{% tab label="VSCode" %} + +In the VS Code extension, manage MCP servers from **Settings → MCP**: + +- **Add a server**: Click **Add Server** and fill in the details +- **Enable/disable**: Toggle a server on or off without removing its configuration +- **Delete**: Remove a server from the list + +The extension also supports the `{env:VARIABLE_NAME}` syntax in config files to reference environment variables (see the **CLI** tab for details). + +{% /tab %} +{% tab label="CLI" %} + +### CLI Commands + +| Command | Description | +| ----------------- | ------------------------------- | +| `kilo mcp list` | List all configured MCP servers | +| `kilo mcp add` | Add an MCP server | +| `kilo mcp auth` | Authenticate with an MCP server | +| `kilo mcp logout` | Log out from an MCP server | +| `kilo mcp debug` | Debug an MCP server connection | + +Inside the interactive TUI, use the `/mcps` slash command to toggle MCP servers on or off. + +### Environment Variables + +Use `{env:VARIABLE_NAME}` syntax in config files to reference environment variables: + +```json +{ + "mcp": { + "my-server": { + "type": "remote", + "url": "https://mcp.example.com/mcp", + "headers": { + "Authorization": "Bearer {env:MY_API_KEY}" + } + } + } +} +``` + +{% /tab %} +{% /tabs %} + ### Network Timeout +{% tabs %} +{% tab label="VSCode" %} + +Set the `timeout` field (in milliseconds) in the server's config entry. The default is 10 seconds for local servers and 15 seconds for remote servers. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + To set the maximum time to wait for a response after a tool call to the MCP server: 1. Click the `Network Timeout` pulldown at the bottom of the individual MCP server's config box and change the time. Default is 1 minute but it can be set between 30 seconds and 5 minutes. {% image src="/docs/img/using-mcp-in-kilo-code/using-mcp-in-kilo-code-6.png" alt="Network Timeout pulldown" width="400" caption="Network Timeout pulldown" /%} +{% /tab %} +{% /tabs %} + ### Auto Approve Tools +{% tabs %} +{% tab label="VSCode" %} + +MCP tool calls use the same permission system as built-in tools. Each MCP tool's permission key is its namespaced name: `{server}_{tool}` (e.g. `my_server_do_something`). + +**At runtime:** When an MCP tool is called, the Permission Dock shows an approval prompt. Click **Approve Always** to save an allow rule to your config so future calls to that tool are auto-approved. + +**In your config file:** Add the tool name (or a wildcard pattern) to the `permission` key in `kilo.jsonc`: + +```json +{ + "permission": { + "my_server_do_something": "allow", + "my_server_*": "allow" + } +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + MCP tool auto-approval works on a per-tool basis and is disabled by default. To configure auto-approval: 1. First enable the global "Use MCP servers" auto-approval option in [auto-approving-actions](/docs/getting-started/settings/auto-approving-actions) @@ -178,41 +455,14 @@ MCP tool auto-approval works on a per-tool basis and is disabled by default. To When enabled, Kilo Code will automatically approve this specific tool without prompting. Note that the global "Use MCP servers" setting takes precedence - if it's disabled, no MCP tools will be auto-approved. -## Finding and Installing MCP Servers - -Kilo Code does not come with any pre-installed MCP servers. You'll need to find and install them separately. - -- **Community Repositories:** Check for community-maintained lists of MCP servers on GitHub -- **Ask Kilo Code:** You can ask Kilo Code to help you find or even create MCP servers -- **Build Your Own:** Create custom MCP servers using the SDK to extend Kilo Code with your own tools - -For full SDK documentation, visit the [MCP GitHub repository](https://github.com/modelcontextprotocol/). - -## Using MCP Tools in Your Workflow - -After configuring an MCP server, Kilo Code will automatically detect available tools and resources. To use them: - -1. Type your request in the Kilo Code chat interface -2. Kilo Code will identify when an MCP tool can help with your task -3. Approve the tool use when prompted (or use auto-approval) - -Example: "Analyze the performance of my API" might use an MCP tool that tests API endpoints. - -## Troubleshooting MCP Servers - -Common issues and solutions: - -- **Server Not Responding:** Check if the server process is running and verify network connectivity -- **Permission Errors:** Ensure proper API keys and credentials are configured in your `mcp_settings.json` (for global settings) or `.kilocode/mcp.json` (for project settings). -- **Tool Not Available:** Confirm the server is properly implementing the tool and it's not disabled in settings -- **Slow Performance:** Try adjusting the network timeout value for the specific MCP server - -{% callout type="tip" %} -**Reduce system prompt size:** If you're not using MCP, turn it off in Settings > Agent Behaviour > MCP Servers to significantly cut down the size of the system prompt and improve performance. -{% /callout %} +{% /tab %} +{% /tabs %} ## Platform-Specific MCP Configuration Examples +{% tabs %} +{% tab label="VSCode (Legacy)" %} + ### Windows Configuration Example When setting up MCP servers on Windows, you'll need to use the Windows Command Prompt (`cmd`) to execute commands. Here's an example of configuring a Puppeteer MCP server on Windows: @@ -252,4 +502,130 @@ For macOS or Linux, you would use a different configuration: {% /callout %} +{% /tab %} +{% tab label="VSCode" %} + +In the VS Code extension, use **Settings → MCP → Add Server** to add any of the examples below through the UI. You can also edit the config files directly — see the **CLI** tab for the JSON format. + +{% /tab %} +{% tab label="CLI" %} + +### Windows + +When setting up local MCP servers on Windows, use the full `cmd` invocation in the `command` array: + +```json +{ + "mcp": { + "puppeteer": { + "type": "local", + "command": ["cmd", "/c", "npx", "-y", "@modelcontextprotocol/server-puppeteer"], + "enabled": true + } + } +} +``` + The same approach can be used for other MCP servers on Windows, adjusting the package name as needed for different server types. + +### Figma Desktop + +Connect to the Figma Desktop app's MCP server: + +```json +{ + "mcp": { + "Figma Desktop": { + "type": "remote", + "url": "http://127.0.0.1:3845/mcp" + } + } +} +``` + +### Context7 + +Add the [Context7](https://github.com/upstash/context7) MCP server for documentation search: + +```json +{ + "mcp": { + "context7": { + "type": "remote", + "url": "https://mcp.context7.com/mcp" + } + } +} +``` + +### Everything Test Server + +Add the test MCP server for development: + +```json +{ + "mcp": { + "mcp_everything": { + "type": "local", + "command": ["npx", "-y", "@modelcontextprotocol/server-everything"] + } + } +} +``` + +{% /tab %} +{% /tabs %} + +## Finding and Installing MCP Servers + +Kilo Code does not come with any pre-installed MCP servers. You'll need to find and install them separately. + +- **Kilo Marketplace:** Browse community-contributed MCP server configurations and agent skills in the [Kilo Marketplace](https://github.com/Kilo-Org/kilo-marketplace). The marketplace includes ready-to-use configs for popular tools like Figma, Sentry, and more. +- **Community Repositories:** Check for community-maintained lists of MCP servers on GitHub +- **Ask Kilo Code:** You can ask Kilo Code to help you find or even create MCP servers +- **Build Your Own:** Create custom MCP servers using the SDK to extend Kilo Code with your own tools + +For full SDK documentation, visit the [MCP GitHub repository](https://github.com/modelcontextprotocol/). + +## Using MCP Tools in Your Workflow + +After configuring an MCP server, Kilo Code will automatically detect available tools and resources. To use them: + +1. Type your request in the Kilo Code chat interface +2. Kilo Code will identify when an MCP tool can help with your task +3. Approve the tool use when prompted (or use auto-approval) + +Example: "Analyze the performance of my API" might use an MCP tool that tests API endpoints. + +## Troubleshooting MCP Servers + +{% tabs %} +{% tab label="VSCode" %} + +- **Server Not Responding:** Check if the server process is running and verify network connectivity. Review server status in Settings > Agent Behaviour > MCP Servers. +- **`needs_auth` status:** For remote servers with OAuth, the extension will show a notification to start the auth flow. Click it to authenticate. +- **`failed` status:** Check the CLI output for error details. Ensure commands and paths are correct. +- **Tool Not Available:** Confirm the server is properly implementing the tool and it's not disabled in settings. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +- **Server Not Responding:** Check if the server process is running and verify network connectivity +- **Permission Errors:** Ensure proper API keys and credentials are configured in your `mcp_settings.json` (for global settings) or `.kilocode/mcp.json` (for project settings). +- **Tool Not Available:** Confirm the server is properly implementing the tool and it's not disabled in settings +- **Slow Performance:** Try adjusting the network timeout value for the specific MCP server + +{% /tab %} +{% tab label="CLI" %} + +- **Server Not Responding:** Check if the server process is running. Use `kilo mcp debug ` to inspect the connection. +- **Permission Errors:** Ensure API keys and credentials are set in your `kilo.jsonc` config or via `{env:VARIABLE_NAME}` references. +- **Tool Not Available:** Confirm the server is properly implementing the tool and it is not disabled (`"enabled": false`) in your config. +- **Slow Performance:** Increase the `timeout` value for the specific MCP server in your config. + +{% /tab %} +{% /tabs %} + +{% callout type="tip" %} +**Reduce system prompt size:** If you're not using MCP, turn it off in Settings > Agent Behaviour > MCP Servers to significantly cut down the size of the system prompt and improve performance. +{% /callout %} diff --git a/packages/kilo-docs/pages/automate/tools/index.md b/packages/kilo-docs/pages/automate/tools/index.md index ac473fefdc0..9949a96321d 100644 --- a/packages/kilo-docs/pages/automate/tools/index.md +++ b/packages/kilo-docs/pages/automate/tools/index.md @@ -11,6 +11,92 @@ Kilo Code implements a sophisticated tool system that allows AI models to intera ### Tool Groups +{% tabs %} +{% tab label="VSCode" %} + +Tools are organized into logical groups based on their functionality: + +| Category | Purpose | Tools | Common Use | +| ------------------ | --------------------------------- | ------------------------------------------------------------ | --------------------------------------- | +| **Read Group** | File system reading and searching | `read`, `glob`, `grep` | Code exploration and analysis | +| **Edit Group** | File system modifications | `edit`, `multiedit`, `write`, `apply_patch` | Code changes and file manipulation | +| **Execute Group** | Shell command execution | `bash` | Running scripts, building projects | +| **Web Group** | Fetch and search web content | `webfetch`, `websearch`, `codesearch` | Research, documentation lookup | +| **Browser Group** | Web browser automation | `kilo-playwright_*` (via built-in Playwright MCP) | Browser testing and interaction | +| **MCP Group** | External tool integration | MCP server tools (namespaced as `{server}_{tool}`) | Specialized functionality via MCP | +| **Workflow Group** | Sub-agents and task management | `question`, `task`, `todowrite`, `todoread`, `plan`, `skill` | Context switching and task organization | + +### Always Available Tools + +Certain tools are accessible regardless of the current agent: + +- `question`: Ask the user a clarifying question with selectable options +- `task`: Spawn a sub-agent session +- `todowrite` / `todoread`: Manage session task lists + +## Available Tools + +### Read Tools + +These tools help Kilo Code understand your code and project: + +- `read` - Reads file contents with line numbers +- `glob` - Finds files matching a glob pattern +- `grep` - Searches file contents with regex + +### Edit Tools + +These tools help Kilo Code make changes to your code: + +- `edit` - Makes precise text replacements in a file +- `multiedit` - Multiple edits in a single call +- `write` - Creates new files or fully overwrites existing ones +- `apply_patch` - Applies unified diffs (used with certain models) + +### Execute Tools + +These tools help Kilo Code run commands: + +- `bash` - Runs shell commands with configurable timeout and working directory + +### Web Tools + +These tools help Kilo Code access web content: + +- `webfetch` - Fetches a URL and returns the content +- `websearch` - Searches the web (available to Kilo/OpenRouter users) +- `codesearch` - Semantic code search (available to Kilo/OpenRouter users) + +### Browser Tools + +The VS Code extension has a built-in browser automation tool powered by [Playwright MCP](https://www.npmjs.com/package/@playwright/mcp). Enable it in Settings → Browser Automation. When enabled, it registers an MCP server named `kilo-playwright` and exposes tools such as: + +- `kilo-playwright_browser_navigate` - Navigate to a URL +- `kilo-playwright_browser_click` - Click an element +- `kilo-playwright_browser_type` - Type text into an element +- `kilo-playwright_browser_screenshot` - Capture a screenshot +- `kilo-playwright_browser_snapshot` - Capture an accessibility snapshot + +These follow the same permission model as all MCP tools (see below). + +### MCP Tools + +MCP server tools are automatically available when an MCP server is connected. Tool names are namespaced as `{server}_{tool}`. See [MCP Overview](/docs/automate/mcp/overview) for details. + +### Workflow Tools + +These tools help manage the conversation and task flow: + +- `question` - Asks you a clarifying question with selectable options +- `task` - Spawns a sub-agent (child session) +- `todowrite` - Creates and updates a session TODO list +- `todoread` - Reads the current session TODO list +- `plan` - Enters structured planning mode +- `skill` - Invokes a reusable skill (Markdown instruction module) + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Tools are organized into logical groups based on their functionality: | Category | Purpose | Tools | Common Use | @@ -80,6 +166,9 @@ These tools help manage the conversation and task flow: - [new_task](/docs/automate/tools/new-task) - Creates a new subtask - [update_todo_list](/docs/automate/tools/update-todo-list) - Tracks task progress with step-by-step checklists +{% /tab %} +{% /tabs %} + ## Tool Calling Mechanism ### When Tools Are Called diff --git a/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md b/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md index 94a48c21970..69ec4bdc5f0 100644 --- a/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md +++ b/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md @@ -131,5 +131,5 @@ Auto Model is actively being improved. We'd love to hear how it's working for yo ## Related - [Model Selection Guide](/docs/code-with-ai/agents/model-selection) - General guidance on choosing models -- [Using Modes](/docs/code-with-ai/agents/using-modes) - Learn about different Kilo Code modes +- [Using Agents](/docs/code-with-ai/agents/using-agents) - Learn about different Kilo Code agents - [Free & Budget Models](/docs/code-with-ai/agents/free-and-budget-models) - Cost-effective alternatives diff --git a/packages/kilo-docs/pages/code-with-ai/agents/chat-interface.md b/packages/kilo-docs/pages/code-with-ai/agents/chat-interface.md index 351af9549ec..7ae2bc19c1d 100644 --- a/packages/kilo-docs/pages/code-with-ai/agents/chat-interface.md +++ b/packages/kilo-docs/pages/code-with-ai/agents/chat-interface.md @@ -6,7 +6,7 @@ description: "Learn how to use the Kilo Code chat interface effectively" # Chatting with Kilo Code {% callout type="tip" %} -**Bottom line:** Kilo Code is an AI coding assistant that lives in VS Code. You chat with it in plain English, and it writes, edits, and explains code for you. +**Bottom line:** Kilo Code is an AI coding assistant. You chat with it in plain English, and it writes, edits, and explains code for you. {% /callout %} {% callout type="note" title="Prefer quick completions?" %} @@ -15,10 +15,26 @@ If you're typing code in the editor and want AI to finish your line or block, ch ## Quick Setup -Find the Kilo Code icon ({% kilo-code-icon /%}) in VS Code's Primary Side Bar. Click it to open the chat panel. +{% tabs %} +{% tab label="VSCode" %} + +Click the Kilo Code icon ({% kiloCodeIcon /%}) in VS Code's Primary Side Bar to open the sidebar chat. You can also pop it out into an editor tab for a larger workspace. + +{% /tab %} +{% tab label="CLI" %} + +Open your terminal and run `kilo` to launch the interactive terminal interface (TUI). You'll see a prompt where you can start typing requests immediately. The TUI is fully keyboard-driven — no mouse required. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +Find the Kilo Code icon ({% kiloCodeIcon /%}) in VS Code's Primary Side Bar. Click it to open the chat panel. **Lost the panel?** Go to View > Open View... and search for "Kilo Code" +{% /tab %} +{% /tabs %} + ## How to Talk to Kilo Code **The key insight:** Just type what you want in normal English. No special commands needed. @@ -46,6 +62,40 @@ Find the Kilo Code icon ({% kilo-code-icon /%}) in VS Code's Primary Side Bar. C ## The Chat Interface +{% tabs %} +{% tab label="VSCode" %} + +**Essential controls:** + +- **Input prompt** - Type your requests and press Enter to send +- **Action buttons** - Approve or reject proposed changes, answer questions +- **Agent dropdown** - Switch between agents (e.g. Code, Ask, Plan) from the sidebar +- **Session management** - Start new sessions or resume previous ones + +**Providing context:** + +The extension automatically passes context from your editor, including your open tabs and active file. You can type `@` in the chat input to get file autocomplete suggestions, or mention file paths naturally in your message (e.g., "update src/utils.ts to add a helper function"). The agent can also discover files on its own using its built-in tools. + +{% /tab %} +{% tab label="CLI" %} + +**Essential controls:** + +- **Input prompt** - Type your requests and press Enter to send +- **Action buttons** - Approve or reject proposed changes, answer questions +- **Agent cycling** - Switch between agents using keybinds or slash commands +- **Session management** - Start new sessions or resume previous ones +- **New task** - Start a new task, available using the `+` button at the top or `New Task` button above the chat input +- **Worktree** - Continue the current task with it's git state and session history in the Agent Manager in an isolated worktree +- **File changes** - Shows the number of lines changed and opens a diff view + +**Providing context:** + +Type `@` in the TUI to get file autocomplete suggestions, or mention file paths directly in your message (e.g., "look at src/utils.ts") and the agent will read them. When using the non-interactive `kilo run` command, you can pass `-f path/to/file.ts` to explicitly include files. The agent can also discover files on its own using its built-in tools. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + {% image src="/docs/img/the-chat-interface/the-chat-interface-1.png" alt="Chat interface components labeled with callouts" width="800" caption="Everything you need is right here" /%} **Essential controls:** @@ -56,6 +106,20 @@ Find the Kilo Code icon ({% kilo-code-icon /%}) in VS Code's Primary Side Bar. C - **Plus button** - Start a new task session - **Mode selector** - Choose how Kilo should approach your task +**Providing context with @-mentions:** + +Reference files and other context directly in your message using `@`: + +- `@file` - Reference a specific file +- `@url` - Include content from a URL +- `@problems` - Include current VS Code problems +- `@terminal` - Include terminal output +- `@git-changes` - Include uncommitted changes +- `@commit` - Reference a specific commit + +{% /tab %} +{% /tabs %} + ## Quick Interactions **Click to act:** @@ -85,29 +149,19 @@ Find the Kilo Code icon ({% kilo-code-icon /%}) in VS Code's Primary Side Bar. C ## Suggested Responses -When Kilo Code needs more information to complete a task, it uses the [`ask_followup_question`](/docs/automate/tools/ask-followup-question) tool. To make responding easier and faster, Kilo Code often provides suggested answers alongside the question. - -{% image src="/docs/img/suggested-responses/suggested-responses.png" alt="Example of Kilo Code asking a question with suggested response buttons below it" width="800" caption="Suggested responses appear as clickable buttons below questions" /%} +When Kilo Code needs more information to complete a task, it asks a follow-up question and often provides suggested answers to make responding faster. **How it works:** -1. **Question Appears** - Kilo Code asks a question using the `ask_followup_question` tool -2. **Suggestions Displayed** - If suggestions are provided, they appear as buttons below the question -3. **Interaction** - You can interact with these suggestions in two ways +1. **Question Appears** - Kilo Code asks a question using the `question` tool +2. **Options Displayed** - Selectable options are presented that you can choose from +3. **Selection** - Pick an option or type a custom response -**Interacting with suggestions:** +{% callout type="info" title="VSCode (Legacy)" collapsed=true %} +In the legacy extension, Kilo Code uses the `ask_followup_question` tool instead. Suggestions appear as clickable buttons below the question. You can click a button to send the answer directly, or hold `Shift` and click (or click the pencil icon {% codicon name="edit" /%}) to copy the suggestion into the input box for editing before sending. -You have two options for using suggested responses: - -1. **Direct Selection**: - - **Action**: Simply click the button containing the answer you want to provide - - **Result**: The selected answer is immediately sent back to Kilo Code as your response. This is the quickest way to reply if one of the suggestions perfectly matches your intent. - -2. **Edit Before Sending**: - - **Action**: - - Hold down `Shift` and click the suggestion button - - _Alternatively_, hover over the suggestion button and click the pencil icon ({% codicon name="edit" /%}) that appears - - **Result**: The text of the suggestion is copied into the chat input box. You can then modify the text as needed before pressing Enter to send your customized response. This is useful when a suggestion is close but needs minor adjustments. +{% image src="/docs/img/suggested-responses/suggested-responses.png" alt="Example of Kilo Code asking a question with suggested response buttons below it" width="800" caption="Suggested responses appear as clickable buttons below questions" /%} +{% /callout %} **Benefits:** @@ -119,6 +173,41 @@ This feature streamlines the interaction when Kilo Code requires clarification, ## Tips for Better Workflow +{% tabs %} +{% tab label="VSCode" %} + +{% callout type="tip" %} +**Switch agents for different tasks.** Use the agent dropdown, `/agents` slash command, or `Cmd+.` (`Ctrl+.` on Windows/Linux) to switch between agents like Code, Ask, and Plan. Each agent is tuned for a different type of task — see [Using Agents](/docs/code-with-ai/agents/using-agents) for details. +{% /callout %} + +{% callout type="tip" %} +**Your editor context is automatic.** The extension reads your open tabs and active file, so you don't need to manually reference every file. Focus your message on what you want done. +{% /callout %} + +{% callout type="tip" %} +**Pop out to an editor tab.** If the sidebar feels cramped, pop the chat into a full editor tab for more room. +{% /callout %} + +{% callout type="tip" %} +**Move Kilo Code to the Secondary Side Bar** for a better layout. Right-click on the Kilo Code icon in the Activity Bar and select **Move To → Secondary Side Bar**. This lets you see the Explorer, Search, Source Control, etc. alongside Kilo Code. + +{% image src="/docs/img/move-to-secondary.png" alt="Move to Secondary Side Bar" width="600" caption="Move Kilo Code to the Secondary Side Bar for better workspace organization" /%} +{% /callout %} + +{% /tab %} +{% tab label="CLI" %} + +{% callout type="tip" %} +**Switch agents for different tasks.** Use `/agents`, press `Tab` to cycle agents, or use `Ctrl+X a` to open the agent picker. Each agent is tuned for a different type of task — see [Using Agents](/docs/code-with-ai/agents/using-agents) for details. +{% /callout %} + +{% callout type="tip" %} +**The TUI is keyboard-driven.** Navigate, approve changes, and switch agents entirely from the keyboard — no mouse needed. +{% /callout %} + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + {% callout type="tip" %} **Move Kilo Code to the Secondary Side Bar** for a better layout. Right-click on the Kilo Code icon in the Activity Bar and select **Move To → Secondary Side Bar**. This lets you see the Explorer, Search, Source Control, etc. alongside Kilo Code. @@ -129,4 +218,7 @@ This feature streamlines the interaction when Kilo Code requires clarification, **Drag files directly into chat.** Once you have Kilo Code in a separate sidebar from the file explorer, you can drag files from the explorer into the chat window (even multiple at once). Just hold down the Shift key after you start dragging the files. {% /callout %} -Ready to start coding? Open the chat panel and describe what you want to build! +{% /tab %} +{% /tabs %} + +Ready to start coding? Start a session in Kilo Code and describe what you want to build! diff --git a/packages/kilo-docs/pages/code-with-ai/agents/context-mentions.md b/packages/kilo-docs/pages/code-with-ai/agents/context-mentions.md index e4bf59580b0..2320c8ea217 100644 --- a/packages/kilo-docs/pages/code-with-ai/agents/context-mentions.md +++ b/packages/kilo-docs/pages/code-with-ai/agents/context-mentions.md @@ -5,6 +5,92 @@ description: "How to provide context to Kilo Code using mentions" # Context Mentions +Providing the right context helps Kilo Code understand your project and perform tasks accurately. All platforms support `@`-mentions for referencing files, and the agent can also discover context on its own using built-in tools like `read`, `grep`, and `glob`. + +{% tabs %} +{% tab label="VSCode" %} + +The extension supports `@`-mention autocomplete for file paths and also uses a tool-based context model where the agent can automatically discover and read files using built-in tools. + +## How Context Works + +When you describe a task, the agent uses its tools — `read`, `grep`, `glob`, and others — to find and read relevant files on its own. You don't need to explicitly point it at files in most cases; just describe what you want done and the agent will locate the right code. + +### @-Mention Autocomplete + +Type `@` in the chat input followed by a filename to get autocomplete suggestions. Selecting a file attaches its contents to your message. This is the quickest way to reference a specific file. + +### Automatic Editor Context + +The extension automatically includes context from your editor with each message — your currently focused file and all open editor tabs. You don't need to mention these explicitly. + +Selected code and editor diagnostics (errors/warnings) are not included automatically. However, you can send these to Kilo Code through VS Code's Code Actions: select code or hover over an error, then use the lightbulb menu to find context-dependent actions like "Explain with Kilo Code" or "Fix with Kilo Code." + +### Tool-Based File Access + +Rather than attaching file contents up front, the agent reads files on demand during its work: + +| Tool | Purpose | Example | +| -------- | --------------------------------------------- | ------------------------------------------- | +| **read** | Read the contents of a specific file | Agent reads `src/utils.ts` to understand it | +| **glob** | Find files matching a pattern | Agent searches for `**/*.test.ts` | +| **grep** | Search file contents for a pattern | Agent searches for `function handleError` | +| **bash** | Run shell commands including `git` operations | Agent runs `git diff` or `git log` | + +This means the agent can explore your entire project as needed, rather than being limited to files you explicitly mention. + +## Best Practices + +| Practice | Description | +| ------------------------------ | -------------------------------------------------------------------------------------------------- | +| **Describe the task clearly** | The agent finds context on its own — focus on _what_ you want done rather than _where_ the code is | +| **Mention files when helpful** | If you know the exact file, mention its path to save the agent a search step | +| **Keep editor tabs relevant** | Open tabs are passed as context, so keep relevant files open | +| **Trust the agent's tools** | The agent can search, read, and explore your codebase — let it do the discovery work | + +{% /tab %} +{% tab label="CLI" %} + +The CLI uses a tool-based context model. The agent **automatically discovers and reads the context it needs** using built-in tools. In the TUI, you can type `@` to get file autocomplete suggestions for quick file references. + +## How Context Works + +When you describe a task, the agent uses its tools — `read`, `grep`, `glob`, and others — to find and read relevant files on its own. You don't need to explicitly point it at files in most cases; just describe what you want done and the agent will locate the right code. + +### Providing File Context + +In the terminal-based TUI, you can provide context in several ways: + +- **Type `@` for file autocomplete** — In the TUI, type `@` followed by a filename to get autocomplete suggestions for quick file references. +- **Mention file paths in your message** — Simply refer to files by path in your conversation text (e.g., "look at src/utils.ts") and the agent will read them. +- **Use `kilo run -f`** — When using the non-interactive `kilo run` command, pass `-f path/to/file.ts` to explicitly include a file's contents in the context. +- **Let the agent find files itself** — The agent has access to `glob` (find files by pattern), `grep` (search file contents), and `read` (read file contents) tools. Describe what you're looking for and it will locate the relevant code. + +### Tool-Based File Access + +Rather than attaching file contents up front, the agent reads files on demand during its work: + +| Tool | Purpose | Example | +| -------- | --------------------------------------------- | ------------------------------------------- | +| **read** | Read the contents of a specific file | Agent reads `src/utils.ts` to understand it | +| **glob** | Find files matching a pattern | Agent searches for `**/*.test.ts` | +| **grep** | Search file contents for a pattern | Agent searches for `function handleError` | +| **bash** | Run shell commands including `git` operations | Agent runs `git diff` or `git log` | + +This means the agent can explore your entire project as needed, rather than being limited to files you explicitly mention. + +## Best Practices + +| Practice | Description | +| ------------------------------ | -------------------------------------------------------------------------------------------------- | +| **Describe the task clearly** | The agent finds context on its own — focus on _what_ you want done rather than _where_ the code is | +| **Mention files when helpful** | If you know the exact file, mention its path to save the agent a search step | +| **Use `kilo run -f`** | Pass key files with `-f` when using `kilo run` for immediate context | +| **Trust the agent's tools** | The agent can search, read, and explore your codebase — let it do the discovery work | + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Context mentions are a powerful way to provide Kilo Code with specific information about your project, allowing it to perform tasks more accurately and efficiently. You can use mentions to refer to files, folders, problems, and Git commits. Context mentions start with the `@` symbol. {% image src="/docs/img/context-mentions/context-mentions.png" alt="Context Mentions Overview - showing the @ symbol dropdown menu in the chat interface" width="600" caption="Context mentions overview showing the @ symbol dropdown menu in the chat interface." /%} @@ -116,3 +202,6 @@ The dropdown automatically suggests: | **Click mentions** | Click mentions in chat history to open files or view content | | **Eliminate copy-pasting** | Use mentions instead of manually copying code or errors | | **Combine mentions** | "Fix @problems in @/src/component.ts using the pattern from commit @a1b2c3d" | + +{% /tab %} +{% /tabs %} diff --git a/packages/kilo-docs/pages/code-with-ai/agents/free-and-budget-models.md b/packages/kilo-docs/pages/code-with-ai/agents/free-and-budget-models.md index 8d588e1531b..ce24af58ff3 100644 --- a/packages/kilo-docs/pages/code-with-ai/agents/free-and-budget-models.md +++ b/packages/kilo-docs/pages/code-with-ai/agents/free-and-budget-models.md @@ -131,9 +131,9 @@ When you need more capability than free models provide, these options deliver ex **Use appropriate modes to limit expensive operations:** -- **[Ask Mode](/docs/code-with-ai/agents/using-modes#ask-mode):** Information gathering without code changes -- **[Architect Mode](/docs/code-with-ai/agents/using-modes#architect-mode):** Planning without expensive file operations -- **[Debug Mode](/docs/code-with-ai/agents/using-modes#debug-mode):** Focused troubleshooting +- **[Ask Agent](/docs/code-with-ai/agents/using-agents#ask):** Information gathering without code changes +- **[Plan Agent](/docs/code-with-ai/agents/using-agents#plan):** Planning without expensive file operations +- **[Debug Agent](/docs/code-with-ai/agents/using-agents#debug):** Focused troubleshooting **Custom modes for budget control:** diff --git a/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md b/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md index bf090ac3404..43d9dbec509 100644 --- a/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md +++ b/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md @@ -19,6 +19,33 @@ This isn't benchmarks from some lab. It's real usage data from developers like y While the specifics change constantly, some principles stay consistent: +### How to Select and Switch Models + +{% tabs %} +{% tab label="VSCode" %} + +- Use the **model selector** in the chat prompt area to pick a model for the current session. You can also type `/models` to open the model picker. +- Set per-agent defaults and a global default in the **Settings** panel (Models tab), or directly in the `kilo.jsonc` config file. +- **Model precedence:** Session override → Per-agent config → Global config → Recent models → Kilo Auto (free). + +{% /tab %} +{% tab label="CLI" %} + +- In the TUI, use the **model picker** (`Ctrl+X m` or `/models`) to switch models. +- For non-interactive use, pass `--model` flag to `kilo run` (e.g., `kilo run --model claude-sonnet-4-20250514`). +- Set the global default with the `model` key in `kilo.jsonc`, or configure per-agent models in the `agent` section. +- **Model precedence:** `--model` flag → Per-agent config → Last used in session → Global config → Recent models → First available. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +- Use the **model dropdown** in the chat panel to select a model for each conversation. +- Configure **API profiles** in Settings to group provider + model combinations and switch between them quickly. +- Models are **sticky per mode** — each mode (Code, Architect, Debug, etc.) remembers the last model you selected. + +{% /tab %} +{% /tabs %} + **For complex coding tasks**: Premium models (Claude Sonnet/Opus, GPT-5 class, Gemini Pro) typically handle nuanced requirements, large refactors, and architectural decisions better. **For everyday coding**: Mid-tier models often provide the best balance of speed, cost, and quality. They're fast enough to keep your flow state intact and capable enough for most tasks. diff --git a/packages/kilo-docs/pages/code-with-ai/agents/orchestrator-mode.md b/packages/kilo-docs/pages/code-with-ai/agents/orchestrator-mode.md index 9fca04b85c2..8fb065bcefa 100644 --- a/packages/kilo-docs/pages/code-with-ai/agents/orchestrator-mode.md +++ b/packages/kilo-docs/pages/code-with-ai/agents/orchestrator-mode.md @@ -1,42 +1,40 @@ --- title: "Orchestrator Mode" -description: "Using Orchestrator mode for complex multi-step tasks" +description: "Orchestrator mode is no longer needed — agents with full tool access now support subagents natively" --- -# Orchestrator Mode: Coordinate Complex Workflows +# Orchestrator Mode (Deprecated) -Orchestrator Mode (formerly known as Boomerang Tasks) allows you to break down complex projects into smaller, manageable pieces. Think of it like delegating parts of your work to specialized assistants. Each subtask runs in its own context, often using a different Kilo Code mode tailored for that specific job (like [`code`](/docs/code-with-ai/agents/using-modes#code-mode-default), [`architect`](/docs/code-with-ai/agents/using-modes#architect-mode), or [`debug`](/docs/code-with-ai/agents/using-modes#debug-mode)). - -{% youtube url="https://www.youtube.com/watch?v=20MmJNeOODo" caption="Orchestrator Mode explained and demonstrated" /%} - -## Why Use Orchestrator Mode? - -- **Tackle Complexity:** Break large, multi-step projects (e.g., building a full feature) into focused subtasks (e.g., design, implementation, documentation). -- **Use Specialized Modes:** Automatically delegate subtasks to the mode best suited for that specific piece of work, leveraging specialized capabilities for optimal results. -- **Maintain Focus & Efficiency:** Each subtask operates in its own isolated context with a separate conversation history. This prevents the parent (orchestrator) task from becoming cluttered with the detailed execution steps (like code diffs or file analysis results), allowing it to focus efficiently on the high-level workflow and manage the overall process based on concise summaries from completed subtasks. -- **Streamline Workflows:** Results from one subtask can be automatically passed to the next, creating a smooth flow (e.g., architectural decisions feeding into the coding task). - -## How It Works - -1. Using Orchestrator Mode, Kilo can analyze a complex task and suggest breaking it down into a subtask. -2. The parent task pauses, and the new subtask begins in a different mode. -3. When the subtask's goal is achieved, Kilo signals completion. -4. The parent task resumes with only the summary of the subtask. The parent uses this summary to continue the main workflow. - -## Key Considerations - -- **Approval Required:** By default, you must approve the creation and completion of each subtask. This can be automated via the [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions#subtasks) settings if desired. -- **Context Isolation and Transfer:** Each subtask operates in complete isolation with its own conversation history. It does not automatically inherit the parent's context. Information must be explicitly passed: - - **Down:** Via the initial instructions provided when the subtask is created. - - **Up:** Via the final summary provided when the subtask finishes. Be mindful that only this summary returns to the parent. -- **Navigation:** Kilo's interface helps you see the hierarchy of tasks (which task is the parent, which are children). You can typically navigate between active and paused tasks. - -Orchestrator Mode provides a powerful way to manage complex development workflows directly within Kilo Code, leveraging specialized modes for maximum efficiency. - -{% callout type="tip" title="Keep Tasks Focused" %} -Use subtasks to maintain clarity. If a request significantly shifts focus or requires a different expertise (mode), consider creating a subtask rather than overloading the current one. +{% callout type="warning" title="Deprecated — scheduled for removal" %} +Orchestrator mode is deprecated and will be removed in a future release. In the VSCode extension and CLI, **agents with full tool access (Code, Plan, Debug) can now delegate to subagents automatically**. You no longer need a dedicated orchestrator — just pick the agent for your task and it will coordinate subagents when helpful. (Read-only agents like Ask do not support delegation.) {% /callout %} -1. This context is passed via the `message` parameter of the [`new_task`](/docs/automate/tools/new-task) tool. -2. The mode for the subtask is specified via the `mode` parameter of the [`new_task`](/docs/automate/tools/new-task) tool during initiation. -3. This summary is passed via the `result` parameter of the [`attempt_completion`](/docs/automate/tools/attempt-completion) tool when the subtask finishes. +## What Changed + +Previously, orchestrator mode was the only way to break complex tasks into subtasks. You had to explicitly switch to orchestrator mode, which would then delegate work to other modes like Code or Architect. + +Now, **subagent support is built into agents that have full tool access** (Code, Plan, Debug). When one of these agents encounters a task that would benefit from delegation — like exploring a codebase, running a parallel search, or handling a subtask in isolation — it can launch a subagent directly using the `task` tool. There's no need to switch agents first. + +## What You Should Do + +- **Just pick the right agent for your task.** Use Code for implementation, Plan for architecture, Debug for troubleshooting. Each will orchestrate subagents where it makes sense. +- **Add custom subagents** if you want specialized delegation behavior. See [Custom Subagents](/docs/customize/custom-subagents) for details. +- **Stop switching to orchestrator mode** before complex tasks. Your current agent already has that capability. + +## How Subagents Work + +1. The agent analyzes a complex task and decides a subtask would benefit from isolation. +2. It launches a subagent session using the `task` tool (e.g., `general` for autonomous work, `explore` for codebase research). +3. The subagent runs in its own isolated context — separate conversation history, no shared state. +4. When done, the subagent returns a summary to the parent agent, which continues its work. + +Agents can launch multiple subagent sessions concurrently for parallel work. + +{% callout type="info" title="VSCode (Legacy)" collapsed=true %} +In the legacy extension, orchestrator mode uses two dedicated tools: + +1. [`new_task`](/docs/automate/tools/new-task) — Creates a subtask with context passed via the `message` parameter and a mode specified via `mode` (e.g., `code`, `architect`, `debug`). +2. [`attempt_completion`](/docs/automate/tools/attempt-completion) — Signals subtask completion and passes a summary back to the parent via the `result` parameter. + +{% youtube url="https://www.youtube.com/watch?v=20MmJNeOODo" caption="Orchestrator Mode in the legacy extension" /%} +{% /callout %} diff --git a/packages/kilo-docs/pages/code-with-ai/agents/using-agents.md b/packages/kilo-docs/pages/code-with-ai/agents/using-agents.md new file mode 100644 index 00000000000..c03728dfbb9 --- /dev/null +++ b/packages/kilo-docs/pages/code-with-ai/agents/using-agents.md @@ -0,0 +1,263 @@ +--- +title: "Using Agents" +description: "Understanding and using different agents in Kilo Code" +--- + +# Using Agents + +Agents in Kilo Code are specialized personas that tailor the assistant's behavior to your current task. Each agent offers different capabilities, expertise, and access levels to help you accomplish specific goals. + +{% callout type="info" %} +The **VSCode (Legacy)** extension calls these **modes** instead of agents. The concept is the same — specialized personas with distinct tool access and behavior. +{% /callout %} + +## Why Use Different Agents? + +- **Task specialization:** Get precisely the type of assistance you need for your current task +- **Safety controls:** Prevent unintended file modifications when focusing on planning or learning +- **Focused interactions:** Receive responses optimized for your current activity +- **Workflow optimization:** Seamlessly transition between planning, implementing, debugging, and learning + +## Switching Agents + +{% tabs %} +{% tab label="VSCode" %} + +There are several ways to switch agents: + +- **Dropdown menu:** Click the agent selector in the sidebar to switch between agents. +- **Slash commands:** Type `/agents` in the chat input to open the agent picker. +- **Keyboard shortcut:** Press `Cmd+.` (macOS) or `Ctrl+.` (Windows/Linux) to cycle through available agents. Add `Shift` to cycle in reverse. + +{% /tab %} +{% tab label="CLI" %} + +There are several ways to switch agents: + +- **Cycle agents:** Press `Tab` to cycle forward through agents, or `Shift+Tab` to cycle backward. +- **Agent picker:** Press `Ctrl+X a` (leader key + `a`) to open the full agent list. +- **Slash commands:** Type `/agents` in the chat input to open the agent picker. +- **Config file:** Set the `default_agent` key in your configuration to change the default agent on startup. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +{% youtube url="https://youtu.be/cS4vQfX528w" caption="Explaining the different modes in Kilo Code" /%} + +Four ways to switch modes: + +1. **Dropdown menu:** Click the selector to the left of the chat input + + {% image src="/docs/img/modes/modes.png" alt="Using the dropdown menu to switch modes" width="400" /%} + +2. **Slash command:** Type `/agents` or `/modes` to list modes and switch. Type `/newtask` to create a new task, or `/smol` to condense your context window. + + {% image src="/docs/img/modes/modes-1.png" alt="Using slash commands to switch modes" width="400" /%} + +### Understanding /newtask vs /smol + +Users often confuse `/newtask` and `/smol`. Here's the key difference: + +| Command | Purpose | When to Use | +| ---------- | ----------------------------------------------------- | ----------------------------------------------------------------------- | +| `/newtask` | Creates a new task with context from the current task | When you want to start something new while carrying over context | +| `/smol` | Condenses your current context window | When your conversation is getting too long and you want to summarize it | + +3. **Toggle command/Keyboard shortcut:** Use the keyboard shortcut below, applicable to your operating system. Each press cycles through the available modes in sequence, wrapping back to the first mode after reaching the end. + + | Operating System | Shortcut | + | ---------------- | -------- | + | macOS | ⌘ + . | + | Windows | Ctrl + . | + | Linux | Ctrl + . | + +You can hold `shift` to move backwards through the list of modes, for example ⌘ + shift + on macOS. + +4. **Accept suggestions:** Click on mode switch suggestions that Kilo Code offers when appropriate + + {% image src="/docs/img/modes/modes-2.png" alt="Accepting a mode switch suggestion from Kilo Code" width="400" /%} + +{% /tab %} +{% /tabs %} + +## Built-in Agents + +{% tabs %} +{% tab label="VSCode" %} + +### code (Default) + +| Aspect | Details | +| -------------------- | ----------------------------------------------------------------------------------------------------------------- | +| **Description** | A skilled software engineer with expertise in programming languages, design patterns, and best practices | +| **Tool Access** | Full access to all tools: `read`, `edit`, `glob`, `grep`, `bash`, `task`, `webfetch`, plus tools from MCP servers | +| **Ideal For** | Writing code, implementing features, debugging, and general development | +| **Special Features** | No tool restrictions — full flexibility for all coding tasks | + +### ask + +| Aspect | Details | +| -------------------- | ------------------------------------------------------------------------------------------------- | +| **Description** | A knowledgeable technical assistant focused on answering questions without changing your codebase | +| **Tool Access** | Read-only tools only (cannot edit files or run commands) | +| **Ideal For** | Code explanation, concept exploration, and technical learning | +| **Special Features** | Optimized for informative responses without modifying your project | + +### plan + +| Aspect | Details | +| -------------------- | ---------------------------------------------------------------------------------------------------- | +| **Description** | An experienced technical leader and planner who helps design systems and create implementation plans | +| **Tool Access** | Read-only tools plus restricted file editing (plan files in `.kilo/plans/` only) | +| **Ideal For** | System design, high-level planning, and architecture discussions | +| **Special Features** | Similar to the legacy extension's "Architect" mode, with a planning-focused approach | + +### debug + +| Aspect | Details | +| -------------------- | ----------------------------------------------------------------------------------- | +| **Description** | An expert problem solver specializing in systematic troubleshooting and diagnostics | +| **Tool Access** | Full access to all tools | +| **Ideal For** | Tracking down bugs, diagnosing errors, and resolving complex issues | +| **Special Features** | Uses a methodical approach of analyzing, narrowing possibilities, and fixing issues | + +### orchestrator (Deprecated) + +| Aspect | Details | +| -------------------- | -------------------------------------------------------------------------------------------------------------------- | +| **Description** | A strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized agents | +| **Tool Access** | Limited access to create new tasks and coordinate workflows | +| **Ideal For** | Breaking down complex projects into manageable subtasks assigned to specialized agents | +| **Special Features** | Delegates work to other agents; also has access to the **explore** subagent for codebase exploration | + +{% callout type="warning" %} +Orchestrator is deprecated and will be removed in a future release. Agents with full tool access (Code, Plan, Debug) now support subagents natively — there's no need for a dedicated orchestrator. See [Orchestrator Mode (Deprecated)](/docs/code-with-ai/agents/orchestrator-mode) for migration details. +{% /callout %} + +{% callout type="info" %} +The VSCode extension and CLI do not include a built-in Review agent. Code review workflows can be handled by the **code** agent or via custom agent configurations. +{% /callout %} + +{% /tab %} +{% tab label="CLI" %} + +### code (Default) + +| Aspect | Details | +| -------------------- | ----------------------------------------------------------------------------------------------------------------- | +| **Description** | A skilled software engineer with expertise in programming languages, design patterns, and best practices | +| **Tool Access** | Full access to all tools: `read`, `edit`, `glob`, `grep`, `bash`, `task`, `webfetch`, plus tools from MCP servers | +| **Ideal For** | Writing code, implementing features, debugging, and general development | +| **Special Features** | No tool restrictions — full flexibility for all coding tasks | + +### ask + +| Aspect | Details | +| -------------------- | ------------------------------------------------------------------------------------------------- | +| **Description** | A knowledgeable technical assistant focused on answering questions without changing your codebase | +| **Tool Access** | Read-only tools only (cannot edit files or run commands) | +| **Ideal For** | Code explanation, concept exploration, and technical learning | +| **Special Features** | Optimized for informative responses without modifying your project | + +### plan + +| Aspect | Details | +| -------------------- | ---------------------------------------------------------------------------------------------------- | +| **Description** | An experienced technical leader and planner who helps design systems and create implementation plans | +| **Tool Access** | Read-only tools plus restricted file editing (plan files in `.kilo/plans/` only) | +| **Ideal For** | System design, high-level planning, and architecture discussions | +| **Special Features** | Similar to the legacy extension's "Architect" mode, with a planning-focused approach | + +### debug + +| Aspect | Details | +| -------------------- | ----------------------------------------------------------------------------------- | +| **Description** | An expert problem solver specializing in systematic troubleshooting and diagnostics | +| **Tool Access** | Full access to all tools | +| **Ideal For** | Tracking down bugs, diagnosing errors, and resolving complex issues | +| **Special Features** | Uses a methodical approach of analyzing, narrowing possibilities, and fixing issues | + +### orchestrator (Deprecated) + +| Aspect | Details | +| -------------------- | -------------------------------------------------------------------------------------------------------------------- | +| **Description** | A strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized agents | +| **Tool Access** | Limited access to create new tasks and coordinate workflows | +| **Ideal For** | Breaking down complex projects into manageable subtasks assigned to specialized agents | +| **Special Features** | Delegates work to other agents; also has access to the **explore** subagent for codebase exploration | + +{% callout type="warning" %} +Orchestrator is deprecated and will be removed in a future release. Agents with full tool access (Code, Plan, Debug) now support subagents natively — there's no need for a dedicated orchestrator. See [Orchestrator Mode (Deprecated)](/docs/code-with-ai/agents/orchestrator-mode) for migration details. +{% /callout %} + +{% callout type="info" %} +The VSCode extension and CLI do not include a built-in Review agent. Code review workflows can be handled by the **code** agent or via custom agent configurations. +{% /callout %} + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +### Code Mode (Default) + +| Aspect | Details | +| -------------------- | -------------------------------------------------------------------------------------------------------- | +| **Description** | A skilled software engineer with expertise in programming languages, design patterns, and best practices | +| **Tool Access** | Full access to all tool groups: `read`, `edit`, `browser`, `command`, `mcp` | +| **Ideal For** | Writing code, implementing features, debugging, and general development | +| **Special Features** | No tool restrictions—full flexibility for all coding tasks | + +### Ask Mode + +| Aspect | Details | +| -------------------- | ------------------------------------------------------------------------------------------------- | +| **Description** | A knowledgeable technical assistant focused on answering questions without changing your codebase | +| **Tool Access** | Limited access: `read`, `browser`, `mcp` only (cannot edit files or run commands) | +| **Ideal For** | Code explanation, concept exploration, and technical learning | +| **Special Features** | Optimized for informative responses without modifying your project | + +### Architect Mode + +| Aspect | Details | +| -------------------- | ---------------------------------------------------------------------------------------------------- | +| **Description** | An experienced technical leader and planner who helps design systems and create implementation plans | +| **Tool Access** | Access to `read`, `browser`, `mcp`, and restricted `edit` (markdown files only) | +| **Ideal For** | System design, high-level planning, and architecture discussions | +| **Special Features** | Follows a structured approach from information gathering to detailed planning | + +### Debug Mode + +| Aspect | Details | +| -------------------- | ----------------------------------------------------------------------------------- | +| **Description** | An expert problem solver specializing in systematic troubleshooting and diagnostics | +| **Tool Access** | Full access to all tool groups: `read`, `edit`, `browser`, `command`, `mcp` | +| **Ideal For** | Tracking down bugs, diagnosing errors, and resolving complex issues | +| **Special Features** | Uses a methodical approach of analyzing, narrowing possibilities, and fixing issues | + +{% callout type="tip" %} +**Keep debugging separate from main tasks:** When using Debug mode, ask Kilo to "start a new task in Debug mode with all of the necessary context needed to figure out X" so that the debugging process uses its own context window and doesn't pollute the main task. +{% /callout %} + +### Orchestrator Mode + +| Aspect | Details | +| -------------------- | ------------------------------------------------------------------------------------------------------------------- | +| **Description** | A strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized modes | +| **Tool Access** | Limited access to create new tasks and coordinate workflows | +| **Ideal For** | Breaking down complex projects into manageable subtasks assigned to specialized modes | +| **Special Features** | Uses the new_task tool to delegate work to other modes | + +### Review Mode + +| Aspect | Details | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| **Description** | An expert code reviewer specializing in analyzing changes to provide structured feedback on quality, security, and best practices | +| **Tool Access** | Access to `read`, `browser`, `mcp`, and when permitted, `edit` | +| **Ideal For** | Catching issues early, enforcing code standards, accelerating PR turnaround | +| **Special Features** | Code review before committing, surfacing feedback across performance, security, style, and test coverage | + +{% /tab %} +{% /tabs %} + +## Custom Agents + +Create your own specialized assistants by defining tool access, file permissions, and behavior instructions. Custom agents help enforce team standards or create purpose-specific assistants. See [Custom Modes documentation](/docs/customize/custom-modes) for setup instructions. diff --git a/packages/kilo-docs/pages/code-with-ai/agents/using-modes.md b/packages/kilo-docs/pages/code-with-ai/agents/using-modes.md deleted file mode 100644 index 1f0c4f1e0d9..00000000000 --- a/packages/kilo-docs/pages/code-with-ai/agents/using-modes.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -title: "Using Modes" -description: "Understanding and using different modes in Kilo Code" ---- - -# Using Modes - -Modes in Kilo Code are specialized personas that tailor the assistant's behavior to your current task. Each mode offers different capabilities, expertise, and access levels to help you accomplish specific goals. - -## Why Use Different Modes? - -- **Task specialization:** Get precisely the type of assistance you need for your current task -- **Safety controls:** Prevent unintended file modifications when focusing on planning or learning -- **Focused interactions:** Receive responses optimized for your current activity -- **Workflow optimization:** Seamlessly transition between planning, implementing, debugging, and learning - -{% youtube url="https://youtu.be/cS4vQfX528w" caption="Explaining the different modes in Kilo Code" /%} - -## Switching Between Modes - -Four ways to switch modes: - -1. **Dropdown menu:** Click the selector to the left of the chat input - - {% image src="/docs/img/modes/modes.png" alt="Using the dropdown menu to switch modes" width="400" /%} - -2. **Slash command:** Type `/architect`, `/ask`, `/debug`, or `/code` in the chat input to switch modes. Type `/newtask` to create a new task, or `/smol` to condense your context window. - - {% image src="/docs/img/modes/modes-1.png" alt="Using slash commands to switch modes" width="400" /%} - -### Understanding /newtask vs /smol - -Users often confuse `/newtask` and `/smol`. Here's the key difference: - -| Command | Purpose | When to Use | -| ---------- | ----------------------------------------------------- | ----------------------------------------------------------------------- | -| `/newtask` | Creates a new task with context from the current task | When you want to start something new while carrying over context | -| `/smol` | Condenses your current context window | When your conversation is getting too long and you want to summarize it | - -3. **Toggle command/Keyboard shortcut:** Use the keyboard shortcut below, applicable to your operating system. Each press cycles through the available modes in sequence, wrapping back to the first mode after reaching the end. - - | Operating System | Shortcut | - | ---------------- | -------- | - | macOS | ⌘ + . | - | Windows | Ctrl + . | - | Linux | Ctrl + . | - -4. **Accept suggestions:** Click on mode switch suggestions that Kilo Code offers when appropriate - - {% image src="/docs/img/modes/modes-2.png" alt="Accepting a mode switch suggestion from Kilo Code" width="400" /%} - -## Built-in Modes - -### Code Mode (Default) - -| Aspect | Details | -| -------------------- | -------------------------------------------------------------------------------------------------------- | -| **Description** | A skilled software engineer with expertise in programming languages, design patterns, and best practices | -| **Tool Access** | Full access to all tool groups: `read`, `edit`, `browser`, `command`, `mcp` | -| **Ideal For** | Writing code, implementing features, debugging, and general development | -| **Special Features** | No tool restrictions—full flexibility for all coding tasks | - -### Ask Mode - -| Aspect | Details | -| -------------------- | ------------------------------------------------------------------------------------------------- | -| **Description** | A knowledgeable technical assistant focused on answering questions without changing your codebase | -| **Tool Access** | Limited access: `read`, `browser`, `mcp` only (cannot edit files or run commands) | -| **Ideal For** | Code explanation, concept exploration, and technical learning | -| **Special Features** | Optimized for informative responses without modifying your project | - -### Architect Mode - -| Aspect | Details | -| -------------------- | ---------------------------------------------------------------------------------------------------- | -| **Description** | An experienced technical leader and planner who helps design systems and create implementation plans | -| **Tool Access** | Access to `read`, `browser`, `mcp`, and restricted `edit` (markdown files only) | -| **Ideal For** | System design, high-level planning, and architecture discussions | -| **Special Features** | Follows a structured approach from information gathering to detailed planning | - -### Debug Mode - -| Aspect | Details | -| -------------------- | ----------------------------------------------------------------------------------- | -| **Description** | An expert problem solver specializing in systematic troubleshooting and diagnostics | -| **Tool Access** | Full access to all tool groups: `read`, `edit`, `browser`, `command`, `mcp` | -| **Ideal For** | Tracking down bugs, diagnosing errors, and resolving complex issues | -| **Special Features** | Uses a methodical approach of analyzing, narrowing possibilities, and fixing issues | - -{% callout type="tip" %} -**Keep debugging separate from main tasks:** When using Debug mode, ask Kilo to "start a new task in Debug mode with all of the necessary context needed to figure out X" so that the debugging process uses its own context window and doesn't pollute the main task. -{% /callout %} - -### Orchestrator Mode - -| Aspect | Details | -| -------------------- | ------------------------------------------------------------------------------------------------------------------- | -| **Description** | A strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized modes | -| **Tool Access** | Limited access to create new tasks and coordinate workflows | -| **Ideal For** | Breaking down complex projects into manageable subtasks assigned to specialized modes | -| **Special Features** | Uses the new_task tool to delegate work to other modes | - -### Review Mode - -| Aspect | Details | -| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| **Description** | An expert code reviewer specializing in analyzing changes to provide structured feedback on quality, security, and best practices | -| **Tool Access** | Access to `read`, `browser`, `mcp`, and when permitted, `edit` | -| **Ideal For** | Catching issues early, enforcing code standards, accelerating PR turnaround | -| **Special Features** | Code review before committing, surfacing feedback across performance, security, style, and test coverage | - -## Custom Modes - -Create your own specialized assistants by defining tool access, file permissions, and behavior instructions. Custom modes help enforce team standards or create purpose-specific assistants. See [Custom Modes documentation](/docs/customize/custom-modes) for setup instructions. - - diff --git a/packages/kilo-docs/pages/code-with-ai/features/autocomplete/index.md b/packages/kilo-docs/pages/code-with-ai/features/autocomplete/index.md index 311ae3e0b87..d26c1d6f086 100644 --- a/packages/kilo-docs/pages/code-with-ai/features/autocomplete/index.md +++ b/packages/kilo-docs/pages/code-with-ai/features/autocomplete/index.md @@ -7,6 +7,45 @@ description: "AI-powered code autocompletion in Kilo Code" Kilo Code's autocomplete feature provides intelligent code suggestions and completions while you're typing, helping you write code faster and more efficiently. It offers both automatic and manual triggering options. +{% tabs %} +{% tab label="VSCode" %} + +## How Autocomplete Works + +The extension uses **Fill-in-the-Middle (FIM)** completion powered by Codestral (`mistralai/codestral-2508`). It analyzes the code before and after your cursor to generate contextually accurate inline suggestions. + +## Triggering Options + +### Auto-trigger + +Autocomplete is **enabled by default** and automatically shows inline suggestions as you type. Suggestions appear as ghost text that you can accept with `Tab`. + +### Trigger on keybinding (Cmd+L) + +Press `Cmd+L` (Mac) or `Ctrl+L` (Windows/Linux) to manually request a completion at your cursor position. + +{% callout type="note" %} +This keybinding requires `kilo-code.new.autocomplete.enableSmartInlineTaskKeybinding` to be enabled in VS Code settings. It is **disabled by default**. +{% /callout %} + +## Status Bar + +The extension displays an **autocomplete status indicator** in the VS Code status bar, including: + +- Current autocomplete state (active/snoozed) +- Cumulative cost tracking for autocomplete requests + +### Snooze / Unsnooze + +You can temporarily disable autocomplete by clicking the status bar item to **snooze** it. Click again to **unsnooze** and re-enable suggestions. + +## Copilot Conflict Detection + +The extension automatically detects if **GitHub Copilot** inline suggestions are enabled and warns you about potential conflicts. Disable Copilot's inline completions for the best experience with Kilo Code autocomplete. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ## How Autocomplete Works Autocomplete analyzes your code context and provides: @@ -86,6 +125,9 @@ If using Cursor, go to **Settings** > **Cursor Settings** > **Tab**, and toggle {% image src="https://github.com/user-attachments/assets/fd2eeae2-f770-40ca-8a72-a9d5a1c17d47" alt="Disable Cursor autocomplete" width="800" caption="Disable Cursor autocomplete" /%} +{% /tab %} +{% /tabs %} + ## Best Practices 1. **Balance speed and quality**: Faster models provide quicker suggestions but may be less accurate diff --git a/packages/kilo-docs/pages/code-with-ai/features/browser-use.md b/packages/kilo-docs/pages/code-with-ai/features/browser-use.md index 8255cf275b6..40d29c18728 100644 --- a/packages/kilo-docs/pages/code-with-ai/features/browser-use.md +++ b/packages/kilo-docs/pages/code-with-ai/features/browser-use.md @@ -13,6 +13,32 @@ Browser Use requires an advanced agentic model. It is typically most reliable wi ## How Browser Use Works +{% tabs %} +{% tab label="VSCode" %} + +Browser automation is built into the extension and requires no manual setup. Enable it from **Settings → Browser** and Kilo handles the rest automatically. + +{% /tab %} +{% tab label="CLI" %} + +Kilo Code uses [Playwright](https://playwright.dev/) for browser automation. Add it to your `kilo.jsonc` configuration: + +```json +{ + "mcp": { + "playwright": { + "type": "local", + "command": ["npx", "-y", "@playwright/mcp@latest"] + } + } +} +``` + +Playwright downloads Chromium automatically on first use. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + By default, Kilo Code uses a built-in browser that: - Launches automatically when you ask Kilo to visit a website @@ -22,6 +48,9 @@ By default, Kilo Code uses a built-in browser that: All of this happens directly within VS Code, with no setup required. +{% /tab %} +{% /tabs %} + ## Using Browser Use A typical browser interaction follows this pattern: @@ -39,6 +68,38 @@ For example: ## How Browser Actions Work +{% tabs %} +{% tab label="VSCode" %} + +Kilo launches a browser automatically when asked and returns screenshots after each action so you can see what's happening. It can navigate to URLs, click elements, fill in forms, scroll, hover, select from dropdowns, and drag and drop — all driven by natural language instructions in chat. + +{% /tab %} +{% tab label="CLI" %} + +The Playwright MCP server provides a set of browser tools for interacting with web pages. These tools return screenshots and accessibility snapshots after each action. + +Key characteristics: + +- The browser launches automatically when a browser tool is invoked +- Multiple browser tools can be used in sequence +- Screenshots are captured after each action for visual feedback + +### Available Browser Tools + +| Tool | Description | When to Use | +| -------------------- | ----------------------------------- | ------------------------------------- | +| `browser_navigate` | Navigates to a URL | Opening a web page | +| `browser_click` | Clicks an element on the page | Interacting with buttons, links, etc. | +| `browser_type` | Types text into an input element | Filling forms, search boxes | +| `browser_screenshot` | Captures a screenshot of the page | Inspecting visual state | +| `browser_scroll` | Scrolls the page or a specific area | Viewing content above or below | +| `browser_hover` | Hovers over an element | Revealing tooltips or menus | +| `browser_select` | Selects an option from a dropdown | Choosing from select elements | +| `browser_drag` | Drags an element to a target | Drag-and-drop interactions | + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + The browser_action tool controls a browser instance that returns screenshots and console logs after each action, allowing you to see the results of interactions. Key characteristics: @@ -59,8 +120,28 @@ Key characteristics: | `scroll_up` | Scrolls up by one page | Returning to previous content | | `close` | Closes the browser | Ending a browser session | +{% /tab %} +{% /tabs %} + ## Browser Use Settings +{% tabs %} +{% tab label="VSCode" %} + +Browser automation settings are available under **Settings → Browser**: + +- **Enable browser automation**: Toggle to enable or disable browser automation +- **Headless mode**: Run the browser without a visible window (default: disabled) +- **Use system Chrome**: Enabled by default — uses your installed Chrome. Disable to have Playwright download and use Chromium instead. + +{% /tab %} +{% tab label="CLI" %} + +Browser automation is configured in your `kilo.jsonc` file. No additional settings are required — Playwright manages the browser lifecycle automatically. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + {% callout type="info" title="Default Browser Settings" %} - **Enable browser tool**: Enabled @@ -161,3 +242,6 @@ Connect to a visible Chrome window to observe Kilo's interactions in real-time: ```bash google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug --no-first-run ``` + +{% /tab %} +{% /tabs %} diff --git a/packages/kilo-docs/pages/code-with-ai/features/checkpoints.md b/packages/kilo-docs/pages/code-with-ai/features/checkpoints.md index 377f9dc108e..f928aef3899 100644 --- a/packages/kilo-docs/pages/code-with-ai/features/checkpoints.md +++ b/packages/kilo-docs/pages/code-with-ai/features/checkpoints.md @@ -14,6 +14,65 @@ Checkpoints let you: - Compare different implementation approaches - Revert to previous project states without losing work +## Configuration Options + +{% tabs %} +{% tab label="VSCode" %} + +Checkpoints (called **snapshots** in the new extension) are configured via the `snapshot` key in your config file (`kilo.jsonc` or `~/.config/kilo/kilo.jsonc`): + +```json +{ + "snapshot": true +} +``` + +You can also toggle this in Settings: + +1. Open Settings by clicking the gear icon {% codicon name="gear" /%} +2. Go to the `Checkpoints` tab +3. Toggle the snapshot setting + +{% /tab %} +{% tab label="CLI" %} + +Checkpoints are controlled by the `snapshot` boolean in your `kilo.jsonc` configuration file: + +```json +{ + "snapshot": true +} +``` + +When enabled, the system automatically captures snapshots at each step of a task. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +Access checkpoint settings in Kilo Code settings under the "Checkpoints" section: + +1. Open Settings by clicking the gear icon {% codicon name="gear" /%} → Checkpoints +2. Check or uncheck the "Enable automatic checkpoints" checkbox + + {% image src="/docs/img/checkpoints/checkpoints.png" alt="Checkpoint settings in Kilo Code configuration" width="500" /%} + +{% /tab %} +{% /tabs %} + +## How Checkpoints Work + +{% tabs %} +{% tab label="VSCode" %} + +The new extension uses **git-based snapshots** of your working directory. Snapshots are taken before and after agent edits. They integrate with your existing Git repository — no separate shadow repository is created. + +**Reverting changes:** + +You can revert any message's changes from the chat. A **Revert Banner** appears at the top of the chat when the session is in a reverted state, making it clear that you are viewing an earlier state. Use the banner to unrevert and return to the latest state. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + {% callout type="info" title="Important Notes" %} - **Checkpoints are enabled by default.** @@ -25,17 +84,6 @@ Checkpoints let you: {% /callout %} -## Configuration Options - -Access checkpoint settings in Kilo Code settings under the "Checkpoints" section: - -1. Open Settings by clicking the gear icon {% codicon name="gear" /%} → Checkpoints -2. Check or uncheck the "Enable automatic checkpoints" checkbox - - {% image src="/docs/img/checkpoints/checkpoints.png" alt="Checkpoint settings in Kilo Code configuration" width="500" /%} - -## How Checkpoints Work - Kilo Code captures snapshots of your project's state using a shadow Git repository, separate from your main version control system. These snapshots, called checkpoints, automatically record changes throughout your AI-assisted workflow—whenever tasks begin, files change, or commands run. Checkpoints are stored as Git commits in the shadow repository, capturing: @@ -46,8 +94,35 @@ Checkpoints are stored as Git commits in the shadow repository, capturing: - Renamed files - Binary file changes +{% /tab %} +{% /tabs %} + ## Working with Checkpoints +{% tabs %} +{% tab label="VSCode" %} + +Checkpoints are integrated directly into your workflow through the chat interface. Each message that caused file changes shows a diff summary. You can: + +- Click the diff badge on a message to open the **Diff Viewer** and review what changed +- Click **Revert** on any message to restore the workspace to its state before that message + +A **Revert Banner** is shown at the top of the chat whenever you are in a reverted state, with an option to return to the current state. + +{% /tab %} +{% tab label="CLI" %} + +Checkpoints are captured automatically at each step of a task. In the CLI terminal interface, checkpoints appear as revert points in the conversation. You can revert to any point by selecting the corresponding message. + +### Reverting Changes + +- **Full revert**: Revert your workspace to any point in the conversation +- **Undo a revert**: Restore the state before the last revert +- **Per-file revert**: Selectively undo changes to specific files while keeping others + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Checkpoints are integrated directly into your workflow through the chat interface. Checkpoints appear directly in your chat history in two forms: @@ -93,6 +168,9 @@ To restore a project to a previous checkpoint state: {% image src="/docs/img/checkpoints/checkpoints-9.png" alt="Confirmation dialog for restoring checkpoint with files & task" width="300" /%} +{% /tab %} +{% /tabs %} + ### Limitations and Considerations - **Scope**: Checkpoints only capture changes made during active Kilo Code tasks @@ -102,6 +180,14 @@ To restore a project to a previous checkpoint state: ## Technical Implementation +{% tabs %} +{% tab label="VSCode" %} + +The new extension uses the underlying Git repository in your workspace to create snapshot commits. No shadow repository is used. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ### Checkpoint Architecture The checkpoint system consists of: @@ -183,9 +269,12 @@ Checkpoints do not support nested Git repositories. The working directory must b Operations are queued to prevent concurrent Git operations that might corrupt repository state. This ensures that rapid checkpoint operations complete safely even when requested in quick succession. +{% /tab %} +{% /tabs %} + ## Git Installation -Checkpoints require Git to be installed on your system. The implementation uses the `simple-git` library, which relies on Git command-line tools to create and manage shadow repositories. +Checkpoints require Git to be installed on your system. ### macOS diff --git a/packages/kilo-docs/pages/code-with-ai/features/code-actions.md b/packages/kilo-docs/pages/code-with-ai/features/code-actions.md index c36e900e7f6..0cf1059a108 100644 --- a/packages/kilo-docs/pages/code-with-ai/features/code-actions.md +++ b/packages/kilo-docs/pages/code-with-ai/features/code-actions.md @@ -7,6 +7,37 @@ description: "Quick code actions and refactoring with Kilo Code" Code Actions are a powerful feature of VS Code that provide quick fixes, refactorings, and other code-related suggestions directly within the editor. Kilo Code integrates with this system to offer AI-powered assistance for common coding tasks. +{% callout type="info" %} +Code Actions are a **VS Code extension feature** and are not available in the CLI/TUI. +{% /callout %} + +{% tabs %} +{% tab label="VSCode" %} + +## Available Code Actions + +The extension provides code actions via the editor context menu and lightbulb: + +- **Add to Context:** Adds selected code (with file path and line numbers) to the active chat session. Keyboard shortcut: `Cmd+K Cmd+A` (Mac) or `Ctrl+K Ctrl+A` (Windows/Linux). +- **Explain Code:** Asks Kilo to explain the selected code. +- **Fix Code:** Asks Kilo to fix problems in the selected code. +- **Improve Code:** Asks Kilo to suggest improvements to the selected code. + +### Agent Manager Integration + +If the **Agent Manager** is active, code actions route to the current Agent Manager session rather than the sidebar chat. This allows code actions to work seamlessly within multi-session workflows. + +### Terminal Context Menu + +The extension also adds code actions to the **terminal context menu**: + +- **Add Terminal Content:** Adds selected terminal output to the chat context. +- **Fix Command:** Asks Kilo to fix a failed terminal command. +- **Explain Command:** Asks Kilo to explain a terminal command or its output. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ## What are Code Actions? Code Actions appear as a lightbulb icon (💡) in the editor gutter (the area to the left of the line numbers). They can also be accessed via the right-click context menu, or via keyboard shortcut. They are triggered when: @@ -90,4 +121,7 @@ You can customize the prompts used for each Code Action by modifying the "Suppor 3. **Edit the Prompts:** Modify the text in the text area for the prompt you want to customize. You can use placeholders like `${filePath}` and `${selectedText}` to include information about the current file and selection. 4. **Click "Done":** Save your changes. +{% /tab %} +{% /tabs %} + By using Kilo Code's Code Actions, you can quickly get AI-powered assistance directly within your coding workflow. This can save you time and help you write better code. diff --git a/packages/kilo-docs/pages/code-with-ai/features/git-commit-generation.md b/packages/kilo-docs/pages/code-with-ai/features/git-commit-generation.md index 31582101198..6485311aa07 100644 --- a/packages/kilo-docs/pages/code-with-ai/features/git-commit-generation.md +++ b/packages/kilo-docs/pages/code-with-ai/features/git-commit-generation.md @@ -53,6 +53,20 @@ Common types include: ## Configuration +{% tabs %} +{% tab label="VSCode" %} + +The extension provides the same **SCM button** in the VS Code Source Control panel. Clicking it generates a commit message using the CLI backend's commit message generation API. + +Configuration is handled through the extension's settings or the shared `kilo.jsonc` config file. + +{% callout type="info" %} +Git commit message generation is a **VS Code extension feature**. It is not available in the CLI/TUI. +{% /callout %} + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ### Customizing the Commit Template You can customize how commit messages are generated by modifying the prompt template: @@ -81,6 +95,9 @@ You can configure which API profile to use for commit message generation: Consider creating a dedicated [API configuration profile](/docs/ai-providers) with a faster, more cost-effective model specifically for commit message generation. {% /callout %} +{% /tab %} +{% /tabs %} + ## Best Practices ### Staging Strategy diff --git a/packages/kilo-docs/pages/code-with-ai/index.md b/packages/kilo-docs/pages/code-with-ai/index.md index 486ee35d3cc..2e229d00fdc 100644 --- a/packages/kilo-docs/pages/code-with-ai/index.md +++ b/packages/kilo-docs/pages/code-with-ai/index.md @@ -34,10 +34,10 @@ Use Kilo Code wherever you work: Kilo uses specialized agents to help with different tasks: - [**Chat Interface**](/docs/code-with-ai/agents/chat-interface) — Conversation-based coding -- [**Using Modes**](/docs/code-with-ai/agents/using-modes) — Switch between Code, Architect, Debug, and Ask modes +- [**Using Agents**](/docs/code-with-ai/agents/using-agents) — Switch between Code, Ask, Plan, Debug, and other agents - [**Model Selection**](/docs/code-with-ai/agents/model-selection) — Choose the right AI model for each task - [**Context Mentions**](/docs/code-with-ai/agents/context-mentions) — Reference files, functions, and symbols -- [**Orchestrator Mode**](/docs/code-with-ai/agents/orchestrator-mode) — Complex multi-step tasks +- [**Orchestrator Mode**](/docs/code-with-ai/agents/orchestrator-mode) — Legacy orchestration (now built into all agents) - [**Free & Budget Models**](/docs/code-with-ai/agents/free-and-budget-models) — Cost-effective AI options ## Features diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md index 04d6ff8e2a8..91b40871124 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md @@ -1,6 +1,7 @@ --- title: "Kilo CLI" description: "Using Kilo Code from the command line" +platform: new --- {% callout type="warning" title="Version Notice" %} @@ -167,7 +168,7 @@ Review your code locally before pushing — catch issues early without waiting f Configuration is managed through: - `/connect` command for provider setup (interactive) -- Config files in **`~/.config/kilo/`**: the CLI (Kilo CLI 1.0 from [Kilo-Org/kilocode](https://github.com/Kilo-Org/kilocode)) merges `config.json`, `opencode.json`, and `opencode.jsonc`. Use **`opencode.json`** (or `opencode.jsonc`) for provider, model, permission, and **MCP** settings. Restart the CLI after editing. See [Using MCP in the CLI](/docs/automate/mcp/using-in-cli) for MCP config format. +- Config files in **`~/.config/kilo/`**: use **`kilo.jsonc`** for provider, model, permission, and **MCP** settings. Restart the CLI after editing. See [Using MCP in Kilo Code](/docs/automate/mcp/using-in-kilo-code) for MCP config format. - `kilo auth` for credential management ## Slash Commands @@ -175,7 +176,7 @@ Configuration is managed through: The CLI's interactive mode supports slash commands for common operations. The main commands are documented above in the [Interactive Slash Commands](#interactive-slash-commands) section. {% callout type="tip" %} -**Confused about /newtask vs /smol in the IDE?** See the [Using Modes](/docs/code-with-ai/agents/using-modes#understanding-newtask-vs-smol) documentation for details. +**Confused about /newtask vs /smol in the IDE?** See the [Using Agents](/docs/code-with-ai/agents/using-agents#understanding-newtask-vs-smol) documentation for details. {% /callout %} ## Permissions diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/vscode.md b/packages/kilo-docs/pages/code-with-ai/platforms/vscode.md index a06044fe304..2db22d89a61 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/vscode.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/vscode.md @@ -5,6 +5,60 @@ description: "Using Kilo Code in Visual Studio Code" # VS Code Extension +Kilo Code is available as two VS Code extensions: the **VSCode (Legacy)** extension and the current **VSCode** version built on the Kilo CLI core. + +{% tabs %} +{% tab label="VSCode" %} + +## Installation + +1. Open VS Code +2. Go to Extensions (`Ctrl+Shift+X` / `Cmd+Shift+X`) +3. Search for "Kilo Code" +4. Click the dropdown arrow next to **Install** and select **Install Pre-Release Version** + +The extension bundles its own CLI binary and spawns `kilo serve` as a background process. All communication happens over HTTP + SSE. + +## Key Features + +Key features include: + +- **SolidJS-based UI** — Rebuilt sidebar with a modern component architecture +- **[JSONC config files](/docs/getting-started/settings)** — Portable settings in `kilo.jsonc` instead of VS Code settings +- **[Granular permissions](/docs/getting-started/settings/auto-approving-actions)** — Per-tool permission rules with glob patterns +- **[Agents](/docs/code-with-ai/agents/using-agents)** — Customizable agents (`.kilo/agents/*.md`) replacing the modes system +- **[Agent Manager](/docs/automate/agent-manager)** — Enhanced with diff panel, multi-model comparison, PR import, and code review annotations +- **[Autocomplete](/docs/code-with-ai/features/autocomplete)** — FIM-based with Codestral, status bar cost tracking +- **[Workflows](/docs/customize/workflows)** — Repeatable prompt templates as `.md` files +- **[Skills](/docs/customize/skills)** — Load specialized domain knowledge from SKILL.md files +- **[Custom Subagents](/docs/customize/custom-subagents)** — Define specialized sub-agents for the `task` tool +- **Open in Tab** — Pop the chat out into a full editor tab +- **Sub-Agent Viewer** — Read-only panels for viewing child agent sessions +- **Legacy Migration** — Automatic migration wizard for VSCode extension settings + +## Shared Settings + +The extension shares its configuration with the CLI. Settings in `~/.config/kilo/kilo.jsonc` (global) and `./kilo.jsonc` (project) apply to both the CLI and the extension. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ## Installation {% partial file="install-vscode.md" /%} + +## Key Features + +- **Sidebar chat** — AI-powered chat panel in the VS Code activity bar +- **[Autocomplete](/docs/code-with-ai/features/autocomplete)** — Inline code completions as you type +- **[Code Actions](/docs/code-with-ai/features/code-actions)** — Explain, fix, and improve code from the editor context menu +- **[Agents](/docs/code-with-ai/agents/using-agents)** — Code, Ask, Architect, Debug, Orchestrator, and Review modes +- **[Custom Modes](/docs/customize/custom-modes)** — Define custom modes with `.kilocodemodes` YAML files +- **[MCP](/docs/automate/mcp/overview)** — Connect to MCP servers for extended capabilities +- **[Agent Manager](/docs/automate/agent-manager)** — Multi-session orchestration with git worktree isolation +- **[Git Commit Generation](/docs/code-with-ai/features/git-commit-generation)** — AI-powered commit messages from the Source Control panel +- **[Context Mentions](/docs/code-with-ai/agents/context-mentions)** — Reference files, URLs, diagnostics, and git changes with `@` +- **[Checkpoints](/docs/code-with-ai/features/checkpoints)** — Git-based snapshots for undo/redo + +{% /tab %} +{% /tabs %} diff --git a/packages/kilo-docs/pages/collaborate/teams/custom-modes-org.md b/packages/kilo-docs/pages/collaborate/teams/custom-modes-org.md index e25615ab238..56211e329d6 100644 --- a/packages/kilo-docs/pages/collaborate/teams/custom-modes-org.md +++ b/packages/kilo-docs/pages/collaborate/teams/custom-modes-org.md @@ -5,7 +5,7 @@ description: "Create organization-wide custom modes" # Custom Modes (Org) -Custom Modes let you create tailored versions of Kilo’s built-in [modes](/docs/code-with-ai/agents/using-modes) for your organization. You can also adjust the settings for Kilo Code's original default modes. You can define a mode’s purpose, behavior, and tool access — helping Kilo adapt to your team’s unique workflows. +Custom Modes let you create tailored versions of Kilo's built-in [agents](/docs/code-with-ai/agents/using-agents) for your organization. You can also adjust the settings for Kilo Code's original default modes. You can define a mode's purpose, behavior, and tool access — helping Kilo adapt to your team's unique workflows. For example, Admins and Owners can extend these by creating **Custom Modes** with specialized roles or personalities (e.g. "Documentation Writer" or "Security Reviewer"). @@ -22,7 +22,7 @@ For example, Admins and Owners can extend these by creating **Custom Modes** wit | ---------------------------------- | ---------------------------------------------------------------------------------------------------- | | **Mode Name** | Display name for the new mode (e.g. _Security Reviewer_). | | **Mode Slug** | A short identifier used internally (e.g. `security-reviewer`). | -| **Role Definition** | Describe Kilo’s role and personality for this mode. Shapes how it reasons and responds. | +| **Role Definition** | Describe Kilo's role and personality for this mode. Shapes how it reasons and responds. | | **Short Description** | A brief summary shown in the mode selector. | | **When to Use (optional)** | Guidance for when this mode should be used. Helps the Orchestrator choose the right mode for a task. | | **Custom Instructions (optional)** | Add behavioral guidelines specific to this mode. | diff --git a/packages/kilo-docs/pages/contributing/cline-to-kilo-migration.md b/packages/kilo-docs/pages/contributing/cline-to-kilo-migration.md deleted file mode 100644 index 4e4d399fc7f..00000000000 --- a/packages/kilo-docs/pages/contributing/cline-to-kilo-migration.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -title: "Cline to Kilo: Contributor Migration Guide" -description: "A guide for Cline contributors who want to start contributing to Kilo Code" ---- - -# Cline to Kilo: Contributor Migration Guide - -If you've been contributing to Cline and you're ready to bring those skills over to Kilo Code, you're in the right place. This guide will walk you through what's different, what's the same, and how to get up and running as a Kilo contributor. - -The good news: if you've been contributing to Cline, you already have most of the skills you need. The workflows are similar, but there are some differences worth knowing about before you dive in. - -## The Quick Version - -| What You Know from Cline | What's Different in Kilo | -| -------------------------------------------- | ------------------------ | -| `npm run install:all` | `pnpm install` | -| `npm run protos` required before first build | Not required | -| F5 to launch dev extension | Same — F5 to launch | -| Changesets for versioning | Same — `pnpm changeset` | - -## Setting Up Your Environment - -### What Stays the Same - -- Git, Node.js (v20.18.1+), and VS Code are still your core tools -- F5 still launches the extension in debug mode -- The project structure follows similar patterns (`src/`, `webview-ui/`, `e2e/`) - -### What's Changed - -**Package Manager: pnpm instead of npm** - -Kilo uses pnpm for dependency management. If you don't have it installed: - -```bash -npm install -g pnpm -``` - -Then instead of: - -```bash -# Cline -npm run install:all -``` - -You'll run: - -```bash -# Kilo -pnpm install -``` - -This single command handles everything — the main extension, webview UI, and e2e tests. - -**No Protocol Buffer Generation** - -In Cline, you needed to run `npm run protos` before your first build. Kilo doesn't require this step. Just install dependencies and you're ready to go. - -**Building the Extension** - -```bash -pnpm build -``` - -This builds the webview UI, compiles TypeScript, bundles everything, and drops a `.vsix` file in `bin/`. - -## Development Workflow Differences - -### Hot Reloading - -Kilo has improved hot reloading in development mode: - -- **Webview UI changes:** Apply immediately without restart (same as Cline) -- **Core extension changes:** In dev mode (`NODE_ENV="development"`), Kilo automatically triggers `workbench.action.reloadWindow` — no manual debugger restarts needed - -In Cline, you had to manually stop debugging, kill background tasks, and restart. Kilo handles this for you during development. - -**Note:** Production builds still require the manual stop/restart cycle. - -### Git Hooks - -Kilo uses Husky for git hooks, which run automatically: - -**Pre-commit:** - -- Blocks commits directly to main -- Runs type generation (`pnpm generate-types`) -- Checks for type file changes -- Runs lint-staged - -**Pre-push:** - -- Blocks pushes directly to main -- Compiles the project -- Reminds you to create a changeset if needed - -These hooks catch issues early. If a commit or push fails, check the hook output for details. - -## Testing - -### Running Tests - -```bash -# All tests -pnpm test - -# Extension tests only -pnpm test:extension - -# Webview tests only -pnpm test:webview - -# E2E / Integration tests -pnpm test:integration -``` - -### E2E Test Setup - -For integration tests, create a `.env.local` file in the project root: - -``` -OPENROUTER_API_KEY=sk-or-v1-... -``` - -Check `e2e/VSCODE_INTEGRATION_TESTS.md` for full details. - -## Contributing Code - -### Creating a Pull Request - -The changeset workflow is identical to Cline: - -```bash -pnpm changeset -``` - -Choose your version bump: - -- **major** — breaking changes -- **minor** — new features -- **patch** — bug fixes - -Commit the generated `.changeset` file with your changes. - -### Code Quality Checks - -```bash -pnpm lint # ESLint -pnpm check-types # TypeScript type checking -``` - -## What's New in Kilo - -Beyond the workflow changes, Kilo has expanded significantly as a platform. As a contributor, you might find opportunities to work on: - -- **Multiple interfaces:** VS Code, JetBrains, CLI, and web (Cloud Agents, App Builder) -- **Specialized Agent modes:** Code, Ask, Debug, Architect, Orchestrator -- **Custom Modes:** A system for creating and sharing specialized agent configurations -- **Platform features:** Sessions, Parallel Agents, Deploy, Code Reviews, Managed Indexing -- **Kilo Marketplace:** A community-driven repository where you can contribute Skills (modular workflows), MCP Servers (tool integrations), and Modes (custom agent behaviors) - -Check the [Architecture Overview](architecture) to understand how these pieces fit together. - -## Getting Help - -- **Discord:** Real-time support from the community -- **GitHub Discussions:** For questions and feature ideas -- **Reddit:** Community discussions - -## TL;DR Checklist - -- ✅ Install pnpm globally -- ✅ Fork and clone the Kilo repo -- ✅ Run `pnpm install` (not `npm run install:all`) -- ✅ Skip the protos step — it's not needed -- ✅ Press F5 to launch the dev extension -- ✅ Create a changeset before your PR (`pnpm changeset`) -- ✅ Let the git hooks do their thing - -Welcome to Kilo. We're glad you're here. diff --git a/packages/kilo-docs/pages/contributing/index.md b/packages/kilo-docs/pages/contributing/index.md index ba8960f7be3..1b8fa9e8604 100644 --- a/packages/kilo-docs/pages/contributing/index.md +++ b/packages/kilo-docs/pages/contributing/index.md @@ -6,7 +6,7 @@ description: "Contribute to Kilo Code" # Contributing Overview {% callout type="info" %} -**New versions of the VS Code extension and CLI are being developed in [Kilo-Org/Kilo](https://github.com/Kilo-Org/Kilo)** (extension at `packages/kilo-vscode`, CLI at `packages/opencode`). If you're looking to contribute to the extension or CLI, please head over to that repository. +**New versions of the VS Code extension and CLI are being developed in [Kilo-Org/kilocode](https://github.com/Kilo-Org/kilocode)** (extension at `packages/kilo-vscode`, CLI at `packages/opencode`). If you're looking to contribute to the extension or CLI, please head over to that repository. {% /callout %} Kilo Code is an open-source project that welcomes contributions from developers of all skill levels. This guide will help you get started with contributing to Kilo Code, whether you're fixing bugs, adding features, improving documentation, or sharing custom modes. diff --git a/packages/kilo-docs/pages/customize/agents-md.md b/packages/kilo-docs/pages/customize/agents-md.md index d451b5e7a7d..a3d19b9b65a 100644 --- a/packages/kilo-docs/pages/customize/agents-md.md +++ b/packages/kilo-docs/pages/customize/agents-md.md @@ -18,7 +18,7 @@ If you'd like to migrate your memory bank content to AGENTS.md: 1. Examine the contents in `.kilocode/rules/memory-bank/` 2. Move that content into your project's `AGENTS.md` file (or ask Kilo to do it for you) -{% /callout %} + {% /callout %} ## What is AGENTS.md? @@ -138,6 +138,35 @@ When you start a task in Kilo Code: ### Interaction with Other Rules +{% tabs %} +{% tab label="VSCode" %} + +In the new platform, AGENTS.md is loaded alongside other instruction sources. The CLI also supports `.claude/` and `.agents/` directories for compatibility with other tools. + +| Source | Scope | Location | Priority | +| ------------------------------------------------ | --------- | ------------------------------------------ | ---------------- | +| **Agent prompt** | Per-agent | `agent..prompt` in config | 1 (Highest) | +| **[Instructions](/docs/customize/custom-rules)** | Project | `instructions` key in project `kilo.jsonc` | 2 | +| **AGENTS.md** | Project | `AGENTS.md` at project root | 3 | +| **[Instructions](/docs/customize/custom-rules)** | Global | `instructions` key in global `kilo.jsonc` | 4 | +| **[Skills](/docs/customize/skills)** | Both | `.kilo/skills/`, config `skills` key | Loaded on demand | + +{% /tab %} +{% tab label="CLI" %} + +In the new platform, AGENTS.md is loaded alongside other instruction sources. The CLI also supports `.claude/` and `.agents/` directories for compatibility with other tools. + +| Source | Scope | Location | Priority | +| ------------------------------------------------ | --------- | ------------------------------------------ | ---------------- | +| **Agent prompt** | Per-agent | `agent..prompt` in config | 1 (Highest) | +| **[Instructions](/docs/customize/custom-rules)** | Project | `instructions` key in project `kilo.jsonc` | 2 | +| **AGENTS.md** | Project | `AGENTS.md` at project root | 3 | +| **[Instructions](/docs/customize/custom-rules)** | Global | `instructions` key in global `kilo.jsonc` | 4 | +| **[Skills](/docs/customize/skills)** | Both | `.kilo/skills/`, config `skills` key | Loaded on demand | + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + AGENTS.md works alongside Kilo Code's other configuration systems: | Feature | Scope | Location | Purpose | Priority | @@ -148,9 +177,37 @@ AGENTS.md works alongside Kilo Code's other configuration systems: | **[Global Custom Rules](/docs/customize/custom-rules)** | Global | `~/.kilocode/rules/` | Global Kilo Code rules | 4 | | **[Custom Instructions](/docs/customize/custom-instructions)** | Global | IDE settings | Personal preferences across all projects | 5 (Lowest) | +{% /tab %} +{% /tabs %} + ### Enabling/Disabling AGENTS.md -AGENTS.md support is **enabled by default** in Kilo Code. To disable it, edit `settings.json`: +{% tabs %} +{% tab label="VSCode" %} + +AGENTS.md is loaded automatically. To disable external skill directories (`.claude/skills/`, `.agents/skills/`), set the environment variable: + +```bash +export KILO_DISABLE_EXTERNAL_SKILLS=true +``` + +AGENTS.md itself cannot be individually disabled — it is always loaded if present. To override its instructions, use higher-priority sources like the `instructions` config key or agent-specific prompts. + +{% /tab %} +{% tab label="CLI" %} + +AGENTS.md is loaded automatically. To disable external skill directories (`.claude/skills/`, `.agents/skills/`), set the environment variable: + +```bash +export KILO_DISABLE_EXTERNAL_SKILLS=true +``` + +AGENTS.md itself cannot be individually disabled — it is always loaded if present. To override its instructions, use higher-priority sources like the `instructions` config key or agent-specific prompts. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +AGENTS.md support is **enabled by default**. To disable it, edit `settings.json`: ```json { @@ -158,6 +215,9 @@ AGENTS.md support is **enabled by default** in Kilo Code. To disable it, edit `s } ``` +{% /tab %} +{% /tabs %} + ## Related Features - **[Custom Rules](/docs/customize/custom-rules)** - Kilo Code-specific rules with more control diff --git a/packages/kilo-docs/pages/customize/context/codebase-indexing.md b/packages/kilo-docs/pages/customize/context/codebase-indexing.md index ff6e1312f01..3f38b01a76d 100644 --- a/packages/kilo-docs/pages/customize/context/codebase-indexing.md +++ b/packages/kilo-docs/pages/customize/context/codebase-indexing.md @@ -1,6 +1,7 @@ --- title: "Codebase Indexing" description: "Index your codebase for improved AI understanding" +platform: legacy --- # Codebase Indexing diff --git a/packages/kilo-docs/pages/customize/context/context-condensing.md b/packages/kilo-docs/pages/customize/context/context-condensing.md index 9ab1a2d2a8b..3d010446f35 100644 --- a/packages/kilo-docs/pages/customize/context/context-condensing.md +++ b/packages/kilo-docs/pages/customize/context/context-condensing.md @@ -17,9 +17,142 @@ Every AI model has a maximum context window - a limit on how much text it can pr - Higher API costs due to increased token usage - Eventually hitting the context limit and being unable to continue +{% tabs %} +{% tab label="VSCode" %} + +## The Solution: Auto-Compaction + +The new platform uses a **Compaction** system to manage context automatically. When your conversation approaches the token limit, compaction kicks in and produces a structured summary that captures: + +- The overall goal of the session +- Key discoveries made along the way +- What has been accomplished so far +- Files that were modified + +This summary replaces the earlier conversation history, freeing up context window space while maintaining continuity in your work. + +## How Compaction Works + +### Automatic Compaction + +Compaction triggers automatically when the conversation reaches the `usableWindow` token threshold. The full conversation history is sent to a dedicated **compaction agent**, which produces a structured summary. This happens in the background without interrupting your workflow. + +### Context Pruning + +In addition to compaction, the system can **prune** old tool outputs to reclaim context space incrementally. Tool results older than a 40,000-token recency window are replaced with `"[Old tool result content cleared]"`. This is a lighter-weight mechanism that runs alongside full compaction. + +### Manual Compaction + +You can also trigger compaction manually: + +- **CLI TUI**: Press `c` to compact the current session +- **Extension Webview**: Send a `CompactRequest` message to trigger compaction + +{% callout type="info" %} +There is no `/condense` chat command on the new platform. Use the keybinding or message-based invocation instead. +{% /callout %} + +### The Compaction Process + +When compaction is triggered: + +1. **Threshold Check**: The system detects that context usage has reached the `usableWindow` limit +2. **Agent Summarization**: The full conversation history is sent to a dedicated compaction agent +3. **Structured Summary**: The agent produces a summary covering the goal, discoveries, accomplishments, and modified files +4. **Replacement**: The detailed history is replaced with the compacted summary +5. **Continuation**: You continue working with the freed-up context space + +## Configuration Options + +Compaction is configured in your `kilo.jsonc` file: + +```jsonc +{ + "compaction": { + "auto": true, // Enable or disable automatic compaction + "reserved": 4096, // Number of tokens to reserve (keep free) after compaction + "prune": true, // Enable pruning of old tool outputs beyond the recency window + }, +} +``` + +| Option | Type | Description | +| --------------------- | ------- | ------------------------------------------------------------------------ | +| `compaction.auto` | boolean | Enable or disable automatic compaction when the context threshold is hit | +| `compaction.reserved` | number | Number of tokens to reserve after compaction | +| `compaction.prune` | boolean | Enable pruning of old tool outputs outside the 40K token recency window | + +{% /tab %} +{% tab label="CLI" %} + +## The Solution: Auto-Compaction + +The new platform uses a **Compaction** system to manage context automatically. When your conversation approaches the token limit, compaction kicks in and produces a structured summary that captures: + +- The overall goal of the session +- Key discoveries made along the way +- What has been accomplished so far +- Files that were modified + +This summary replaces the earlier conversation history, freeing up context window space while maintaining continuity in your work. + +## How Compaction Works + +### Automatic Compaction + +Compaction triggers automatically when the conversation reaches the `usableWindow` token threshold. The full conversation history is sent to a dedicated **compaction agent**, which produces a structured summary. This happens in the background without interrupting your workflow. + +### Context Pruning + +In addition to compaction, the system can **prune** old tool outputs to reclaim context space incrementally. Tool results older than a 40,000-token recency window are replaced with `"[Old tool result content cleared]"`. This is a lighter-weight mechanism that runs alongside full compaction. + +### Manual Compaction + +You can also trigger compaction manually: + +- **CLI TUI**: Press `c` to compact the current session +- **Extension Webview**: Send a `CompactRequest` message to trigger compaction + +{% callout type="info" %} +There is no `/condense` chat command on the new platform. Use the keybinding or message-based invocation instead. +{% /callout %} + +### The Compaction Process + +When compaction is triggered: + +1. **Threshold Check**: The system detects that context usage has reached the `usableWindow` limit +2. **Agent Summarization**: The full conversation history is sent to a dedicated compaction agent +3. **Structured Summary**: The agent produces a summary covering the goal, discoveries, accomplishments, and modified files +4. **Replacement**: The detailed history is replaced with the compacted summary +5. **Continuation**: You continue working with the freed-up context space + +## Configuration Options + +Compaction is configured in your `kilo.jsonc` file: + +```jsonc +{ + "compaction": { + "auto": true, // Enable or disable automatic compaction + "reserved": 4096, // Number of tokens to reserve (keep free) after compaction + "prune": true, // Enable pruning of old tool outputs beyond the recency window + }, +} +``` + +| Option | Type | Description | +| --------------------- | ------- | ------------------------------------------------------------------------ | +| `compaction.auto` | boolean | Enable or disable automatic compaction when the context threshold is hit | +| `compaction.reserved` | number | Number of tokens to reserve after compaction | +| `compaction.prune` | boolean | Enable pruning of old tool outputs outside the 40K token recency window | + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ## The Solution: Intelligent Condensing -Context Condensing solves this problem by creating a concise summary of your conversation that captures: +**Context Condensing** solves this problem by creating a concise summary of your conversation that captures: - The original task or goal - Key decisions made during the session @@ -63,20 +196,6 @@ Context Condensing uses an AI model to generate summaries. You can configure whi You can configure context condensing thresholds and behavior on a per-profile basis, allowing different settings for different projects or use cases. -## Best Practices - -### When to Condense - -- **Long sessions**: If you've been working for an extended period on a complex task -- **Before major transitions**: When switching to a different aspect of your project -- **When prompted**: When Kilo Code suggests condensing due to context limits - -### Maintaining Context Quality - -- **Be specific in your initial task**: A clear task description helps create better summaries -- **Use AGENTS.md**: Combine with [AGENTS.md](/docs/customize/agents-md) for persistent project context that doesn't need to be condensed -- **Review the summary**: After condensing, the summary is visible in your chat history - ## Troubleshooting ### Context Condensing Error @@ -95,6 +214,23 @@ If the condensed summary doesn't capture important details: - Use clear, specific language when describing your tasks - Important context can be reinforced after condensing by reminding Kilo Code of key details +{% /tab %} +{% /tabs %} + +## Best Practices + +### When to Condense + +- **Long sessions**: If you've been working for an extended period on a complex task +- **Before major transitions**: When switching to a different aspect of your project +- **When prompted**: When Kilo Code suggests condensing or compaction due to context limits + +### Maintaining Context Quality + +- **Be specific in your initial task**: A clear task description helps create better summaries +- **Use AGENTS.md**: Combine with [AGENTS.md](/docs/customize/agents-md) for persistent project context that doesn't need to be condensed +- **Review the summary**: After condensing or compaction, the summary is visible in your chat history + ## Related Features - [AGENTS.md](/docs/customize/agents-md) - Persistent context storage across sessions diff --git a/packages/kilo-docs/pages/customize/context/kilocodeignore.md b/packages/kilo-docs/pages/customize/context/kilocodeignore.md index c929aab46ff..25e979374c2 100644 --- a/packages/kilo-docs/pages/customize/context/kilocodeignore.md +++ b/packages/kilo-docs/pages/customize/context/kilocodeignore.md @@ -13,6 +13,61 @@ If no `.kilocodeignore` file exists, Kilo Code can access all files in the works ## Quick Start +{% tabs %} +{% tab label="VSCode" %} + +The primary mechanism for controlling file access is the **permission system** in `kilo.jsonc`. You define tool-level permissions with glob patterns: + +```json +{ + "permission": { + "read": { "*.env": "deny", "*": "allow" }, + "edit": { "dist/**": "deny", "*": "allow" } + } +} +``` + +If you have an existing `.kilocodeignore` file, it is still supported. The **IgnoreMigrator** automatically converts `.kilocodeignore` patterns into permission `deny` rules on `read` and `edit` tools, so your existing rules continue to work without manual changes. + +You can also exclude paths from the file watcher separately using `watcher.ignore`: + +```json +{ + "watcher": { + "ignore": ["tmp/**", "logs/**"] + } +} +``` + +{% /tab %} +{% tab label="CLI" %} + +The primary mechanism for controlling file access is the **permission system** in `kilo.jsonc`. You define tool-level permissions with glob patterns: + +```json +{ + "permission": { + "read": { "*.env": "deny", "*": "allow" }, + "edit": { "dist/**": "deny", "*": "allow" } + } +} +``` + +If you have an existing `.kilocodeignore` file, it is still supported. The **IgnoreMigrator** automatically converts `.kilocodeignore` patterns into permission `deny` rules on `read` and `edit` tools, so your existing rules continue to work without manual changes. + +You can also exclude paths from the file watcher separately using `watcher.ignore`: + +```json +{ + "watcher": { + "ignore": ["tmp/**", "logs/**"] + } +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + 1. Create a `.kilocodeignore` file at the root of your project. 2. Add patterns for files or folders you want Kilo Code to avoid. 3. Save the file. Kilo Code will pick up the changes automatically. @@ -34,6 +89,9 @@ coverage/ !secrets/README.md ``` +{% /tab %} +{% /tabs %} + ## Pattern Rules `.kilocodeignore` follows the same rules as `.gitignore`: @@ -47,6 +105,35 @@ Patterns are evaluated relative to the workspace root. ## What It Affects +{% tabs %} +{% tab label="VSCode" %} + +File access is controlled through **permission-based access control**. Each tool (`read`, `edit`, `glob`, `grep`, `write`, `bash`, etc.) has its own permission rules evaluated against glob patterns. + +In addition to your explicit permission rules: + +- **Hardcoded directory ignores** — 27 directories are always skipped (e.g. `node_modules`, `.git`, `dist`, `build`, `.cache`, `__pycache__`, `vendor`, and others). +- **Hardcoded file pattern ignores** — 11 file patterns are always skipped (e.g. lock files, binary artifacts). +- **`.gitignore` and `.ignore` files** are also respected when listing and searching files. + +If a file is denied by a permission rule, the tool will report that access was blocked. + +{% /tab %} +{% tab label="CLI" %} + +File access is controlled through **permission-based access control**. Each tool (`read`, `edit`, `glob`, `grep`, `write`, `bash`, etc.) has its own permission rules evaluated against glob patterns. + +In addition to your explicit permission rules: + +- **Hardcoded directory ignores** — 27 directories are always skipped (e.g. `node_modules`, `.git`, `dist`, `build`, `.cache`, `__pycache__`, `vendor`, and others). +- **Hardcoded file pattern ignores** — 11 file patterns are always skipped (e.g. lock files, binary artifacts). +- **`.gitignore` and `.ignore` files** are also respected when listing and searching files. + +If a file is denied by a permission rule, the tool will report that access was blocked. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Kilo Code checks `.kilocodeignore` before accessing files in tools like: - [`read_file`](/docs/automate/tools/read-file) @@ -58,17 +145,109 @@ Kilo Code checks `.kilocodeignore` before accessing files in tools like: If a file is blocked, Kilo Code will return an "access denied" message and suggest updating your `.kilocodeignore` rules. -## Visibility in Lists +{% /tab %} +{% /tabs %} + +## Configuration Details + +{% tabs %} +{% tab label="VSCode" %} + +### Permission Rules + +Permission rules are defined per-tool in `kilo.jsonc`. Patterns are evaluated in order — the last matching rule wins: + +```json +{ + "permission": { + "read": { + "*.env": "deny", + "secrets/**": "deny", + "*": "allow" + }, + "edit": { + "dist/**": "deny", + "*.lock": "deny", + "*": "allow" + } + } +} +``` + +### Migrating from .kilocodeignore + +If you already have a `.kilocodeignore` file, you don't need to do anything — the IgnoreMigrator reads your existing patterns and applies them as `deny` rules on `read` and `edit` tools automatically. You can optionally move your rules into `kilo.jsonc` for more granular control (e.g. denying edits but allowing reads). + +### File Watcher Exclusions + +The `watcher.ignore` setting controls which paths the file watcher skips. This is separate from tool permissions and only affects change detection: + +```json +{ + "watcher": { + "ignore": ["tmp/**", "logs/**", ".build/**"] + } +} +``` + +{% /tab %} +{% tab label="CLI" %} + +### Permission Rules + +Permission rules are defined per-tool in `kilo.jsonc`. Patterns are evaluated in order — the last matching rule wins: + +```json +{ + "permission": { + "read": { + "*.env": "deny", + "secrets/**": "deny", + "*": "allow" + }, + "edit": { + "dist/**": "deny", + "*.lock": "deny", + "*": "allow" + } + } +} +``` + +### Migrating from .kilocodeignore + +If you already have a `.kilocodeignore` file, you don't need to do anything — the IgnoreMigrator reads your existing patterns and applies them as `deny` rules on `read` and `edit` tools automatically. You can optionally move your rules into `kilo.jsonc` for more granular control (e.g. denying edits but allowing reads). + +### File Watcher Exclusions + +The `watcher.ignore` setting controls which paths the file watcher skips. This is separate from tool permissions and only affects change detection: + +```json +{ + "watcher": { + "ignore": ["tmp/**", "logs/**", ".build/**"] + } +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +### Visibility in Lists By default, ignored files are hidden from file lists. You can show them with a lock icon by enabling: Settings -> Context -> **Show .kilocodeignore'd files in lists and searches** +{% /tab %} +{% /tabs %} + ## Checkpoints vs .kilocodeignore -Checkpoint tracking is separate from file access rules. Files blocked by `.kilocodeignore` can still be checkpointed if they are not excluded by `.gitignore`. See the [Checkpoints](/docs/code-with-ai/features/checkpoints) documentation for details. +Checkpoint tracking is separate from file access rules. Files blocked by `.kilocodeignore` or permission rules can still be checkpointed if they are not excluded by `.gitignore`. See the [Checkpoints](/docs/code-with-ai/features/checkpoints) documentation for details. ## Troubleshooting -- **Kilo can't access a file you want:** Remove or narrow the matching rule in `.kilocodeignore`. -- **A file still appears in lists:** Check the setting that shows ignored files in lists and searches. +- **Kilo can't access a file you want:** Remove or narrow the matching rule in `.kilocodeignore` (legacy) or adjust the permission rules in `kilo.jsonc` (VSCode extension & CLI). +- **A file still appears in lists:** In the legacy extension, check the setting that shows ignored files in lists and searches. In the extension & CLI, verify your permission and watcher ignore configuration. +- **`.kilocodeignore` patterns not working in the new platform:** Ensure the file is at the workspace root. The IgnoreMigrator reads it automatically — check that your patterns use valid `.gitignore` syntax. diff --git a/packages/kilo-docs/pages/customize/context/large-projects.md b/packages/kilo-docs/pages/customize/context/large-projects.md index a0aab196f0a..101fb1621b6 100644 --- a/packages/kilo-docs/pages/customize/context/large-projects.md +++ b/packages/kilo-docs/pages/customize/context/large-projects.md @@ -1,6 +1,7 @@ --- title: "Large Projects" description: "Best practices for using Kilo Code with large codebases" +platform: legacy --- # Working with Large Projects diff --git a/packages/kilo-docs/pages/customize/custom-instructions.md b/packages/kilo-docs/pages/customize/custom-instructions.md index b7ea7b7d8fb..c46dda3120e 100644 --- a/packages/kilo-docs/pages/customize/custom-instructions.md +++ b/packages/kilo-docs/pages/customize/custom-instructions.md @@ -5,18 +5,162 @@ description: "Provide custom instructions to guide Kilo Code" # Custom Instructions -Custom Instructions allow you to personalize how Kilo Code behaves, providing specific guidance that shapes responses, coding style, and decision-making processes. +Custom Instructions allow you to personalize how Kilo Code behaves, providing specific guidance that shapes responses, coding style, and decision-making processes. Both the **VSCode** and **CLI** versions support custom instructions, though the mechanisms differ. ## What Are Custom Instructions? Custom Instructions define specific Extension behaviors, preferences, and constraints beyond Kilo's basic role definition. Examples include coding style, documentation standards, testing requirements, and workflow guidelines. +{% tabs %} +{% tab label="VSCode" %} + +The extension provides multiple layers of instruction configuration — from per-agent prompts in the Settings UI to auto-discovered files in your project and global config. + +## Per-Agent Prompts + +Each agent can have its own custom prompt configured through the settings UI: + +1. Open **Settings → Agent Behaviour → Agents** subtab +2. Select the agent you want to customize +3. Enter your instructions in the markdown text area under the agent's `prompt` field +4. Save your changes + +These prompts are injected into the agent's system prompt and apply across all sessions using that agent. + +## Instruction Files + +Kilo automatically discovers instruction files at your project root and in parent directories (via `findUp`). The following filenames are recognized: + +- **`AGENTS.md`** — The primary instruction file for Kilo +- **`CLAUDE.md`** — Also supported for compatibility +- **`CONTEXT.md`** — Additional project context + +Place any of these files at your project root to provide project-wide instructions to the agent. + +### Global Instructions + +For instructions that apply across all your projects, place an `AGENTS.md` file in your global config directory: + +- **Kilo:** `~/.config/kilo/AGENTS.md` +- **Claude-compatible:** `~/.claude/CLAUDE.md` + +Project-level instructions are loaded before global instructions and apply to every session. + +### Per-Directory Instructions + +You can place `AGENTS.md` files in any subdirectory of your project. These are loaded dynamically — when the agent's Read tool accesses a file in that directory, the corresponding `AGENTS.md` is discovered and its contents are injected into the conversation as `` tags. + +This is useful for providing context-specific guidance for different parts of a monorepo or project. + +## Additional Instruction Sources + +The `instructions` key in `kilo.jsonc` accepts an array of paths, globs, or URLs pointing to additional instruction files. You can manage these in **Settings → Agent Behaviour → Rules** subtab. + +```yaml +# Examples of instruction sources +instructions: + - ./docs/coding-standards.md + - ./teams/frontend-rules.md + - https://example.com/team-instructions.md +``` + +{% callout type="info" title="URL-Based Instructions" %} +URL-based instruction sources are fetched at session start with a 5-second timeout. If the URL is unreachable, the instruction source is silently skipped. +{% /callout %} + +## Legacy `.kilocoderules` Support + +If your project contains `.kilocoderules` files from the VSCode extension, these are still loaded via auto-migration. However, migrating to `AGENTS.md` is recommended for new projects. + +{% /tab %} +{% tab label="CLI" %} + +The CLI provides multiple layers of instruction configuration — from per-agent prompts in agent definition files to auto-discovered files in your project and global config. + +## Per-Agent Prompts + +Each agent can have its own custom prompt defined in its `.md` file (the markdown body) or via the `agent..prompt` key in `kilo.jsonc`: + +```jsonc +// kilo.jsonc +{ + "agent": { + "code": { + "prompt": "You are a Python specialist. Follow PEP8 strictly.", + }, + }, +} +``` + +Or as the markdown body in `.kilo/agents/code.md`: + +```markdown +--- +description: Python specialist +--- + +You are a Python specialist. Follow PEP8 strictly. +``` + +These prompts are injected into the agent's system prompt and apply across all sessions using that agent. + +## Instruction Files + +Kilo automatically discovers instruction files at your project root and in parent directories (via `findUp`). The following filenames are recognized: + +- **`AGENTS.md`** — The primary instruction file for Kilo +- **`CLAUDE.md`** — Also supported for compatibility +- **`CONTEXT.md`** — Additional project context + +Place any of these files at your project root to provide project-wide instructions to the agent. + +### Global Instructions + +For instructions that apply across all your projects, place an `AGENTS.md` file in your global config directory: + +- **Kilo:** `~/.config/kilo/AGENTS.md` +- **Claude-compatible:** `~/.claude/CLAUDE.md` + +Project-level instructions are loaded before global instructions and apply to every session. + +### Per-Directory Instructions + +You can place `AGENTS.md` files in any subdirectory of your project. These are loaded dynamically — when the agent's Read tool accesses a file in that directory, the corresponding `AGENTS.md` is discovered and its contents are injected into the conversation as `` tags. + +This is useful for providing context-specific guidance for different parts of a monorepo or project. + +## Additional Instruction Sources + +The `instructions` key in `kilo.jsonc` accepts an array of paths, globs, or URLs pointing to additional instruction files. Configure these in your `kilo.jsonc`: + +```jsonc +// kilo.jsonc +{ + "instructions": [ + "./docs/coding-standards.md", + "./teams/frontend-rules.md", + "https://example.com/team-instructions.md", + ], +} +``` + +{% callout type="info" title="URL-Based Instructions" %} +URL-based instruction sources are fetched at session start with a 5-second timeout. If the URL is unreachable, the instruction source is silently skipped. +{% /callout %} + +## Legacy `.kilocoderules` Support + +If your project contains `.kilocoderules` files from the VSCode extension, these are still loaded via auto-migration. However, migrating to `AGENTS.md` is recommended for new projects. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +## Setting Custom Instructions + {% callout type="info" title="Custom Instructions vs Rules" %} Custom Instructions are IDE-wide and are applied across all workspaces and maintain your preferences regardless of which project you're working on. Unlike Instructions, [Custom Rules](/docs/customize/custom-rules) are project specific and allow you to setup workspace-based ruleset. {% /callout %} -## Setting Custom Instructions - **How to set them:** {% image src="/docs/img/custom-instructions/custom-instructions.png" alt="Kilo Code Modes tab showing global custom instructions interface" width="600" caption="Kilo Code Modes tab showing global custom instructions interface" /%} @@ -37,20 +181,24 @@ Mode-specific instructions can be set using the Modes Tab * **Save Changes:** Click "Done" to save your changes {% callout type="info" title="Global Mode Rules" %} - If the mode itself is global (not workspace-specific), any custom instructions you set for it will also apply globally for that mode across all workspaces. - {% /callout %} -## Mode-Specific Instructions from Files +If the mode itself is global (not workspace-specific), any custom instructions you set for it will also apply globally for that mode across all workspaces. +{% /callout %} + +#### Mode-Specific Instructions from Files For version-controlled mode instructions, use the mode rules file paths documented in [Custom Modes](/docs/customize/custom-modes#mode-specific-instructions-via-filesdirectories): - Preferred: `.kilo/rules-{mode-slug}/` (directory) -- Fallback: `.kilorules-{mode-slug}` (single file) +- Fallback: `.kilocoderules-{mode-slug}` (single file) {% callout type="info" title="Legacy Naming Note" %} -Older naming like `.clinerules-{mode-slug}` is not the recommended path for current Kilo mode-specific instructions. +Only `.kilocoderules-{mode-slug}` is recognized as the legacy fallback. Older naming like `.clinerules-{mode-slug}` is not supported. {% /callout %} +{% /tab %} +{% /tabs %} + ## Related Features - [Custom Modes](/docs/customize/custom-modes) diff --git a/packages/kilo-docs/pages/customize/custom-modes.md b/packages/kilo-docs/pages/customize/custom-modes.md index 86bcc17a2c4..f75a4a6b9c6 100644 --- a/packages/kilo-docs/pages/customize/custom-modes.md +++ b/packages/kilo-docs/pages/customize/custom-modes.md @@ -5,11 +5,11 @@ description: "Create and configure custom modes in Kilo Code" # Custom Modes -Kilo Code allows you to create **custom modes** to tailor Kilo's behavior to specific tasks or workflows. Custom modes can be either **global** (available across all projects) or **project-specific** (defined within a single project). +Kilo Code allows you to create **custom modes** (also called **agents**) to tailor Kilo's behavior to specific tasks or workflows. Custom modes can be either **global** (available across all projects) or **project-specific** (defined within a single project). -## Sticky Models for Efficient Workflow - -Each mode—including custom ones—features **Sticky Models**. This means Kilo Code automatically remembers and selects the last model you used with a particular mode. This lets you assign different preferred models to different tasks without constant reconfiguration, as Kilo switches between models when you change modes. +{% callout type="info" %} +The current VS Code extension (built on the Kilo CLI) uses **agent Markdown files** to define custom modes. The legacy extension used `custom_modes.yaml` / `.kilocodemodes`. See the tabs below for the relevant approach. +{% /callout %} ## Why Use Custom Modes? @@ -18,6 +18,443 @@ Each mode—including custom ones—features **Sticky Models**. This means Kilo - **Experimentation:** Safely experiment with different prompts and configurations without affecting other modes - **Team Collaboration:** Share custom modes with your team to standardize workflows +{% tabs %} +{% tab label="VSCode" %} + +In the VSCode extension and CLI, custom behavioral profiles are called **agents** instead of modes. Agents are defined as Markdown files with YAML frontmatter or as entries in the `agent` key of your config file. + +## What's Included in a Custom Agent? + +| Property | Description | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| **name** (filename) | The agent's identifier, derived from the `.md` filename (e.g., `docs-writer.md` creates an agent named `docs-writer`) | +| **description** | A short summary displayed in the agent picker and used by the orchestrator for delegation | +| **model** | Pin a specific model in `provider/model` format (e.g., `anthropic/claude-sonnet-4-20250514`) | +| **prompt** (markdown body) | The system prompt text — the markdown body of the file, injected into the agent's system prompt | +| **mode** | Role classification: `primary` (user-selectable), `subagent` (only invoked by other agents), or `all` (both) | +| **permission** | Per-agent permission overrides controlling which tools the agent can use (e.g., deny `edit`, `bash`) | +| **color** | Hex color (`#FF5733`) or theme keyword (`primary`, `accent`, `warning`, etc.) for the agent picker UI | +| **steps** | Maximum agentic iterations before forcing a text-only response | +| **temperature** / **top_p** | Sampling parameters for the agent's model | +| **variant** | Default model variant | +| **hidden** | If `true`, the agent is hidden from the UI (only meaningful for subagents) | +| **disable** | If `true`, removes the agent entirely | + +## Methods for Creating and Configuring Agents + +### 1. Ask Kilo! (Recommended) + +Ask Kilo to create an agent for you: + +``` +Create a new agent called "docs-writer" that can only read files and edit Markdown files. +``` + +Kilo will generate the agent definition and write it to `.kilo/agent/` in your project. + +### 2. Using the Settings UI + +You can manage agents through the **Settings → Agent Behaviour → Agents** subtab in the extension. This lets you view, create, and edit agent configurations — including the agent's prompt, model, permissions, and other properties. + +### 3. Markdown Files with YAML Frontmatter + +Create `.md` files in any of these directories: + +``` +.kilo/agents/my-agent.md +.kilo/agent/my-agent.md +.opencode/agents/my-agent.md +``` + +For global agents, place files in your global config directory: + +``` +~/.config/kilo/agent/my-agent.md +``` + +The **filename** (minus `.md`) becomes the agent name. Nested directories create namespaced names (e.g., `agents/backend/sql.md` becomes agent `backend/sql`). + +**Example agent file** (`.kilo/agents/docs-writer.md`): + +```markdown +--- +description: Specialized for writing and editing technical documentation +mode: primary +color: "#10B981" +permission: + edit: + "*.md": "allow" + "*": "deny" + bash: deny +--- + +You are a technical documentation specialist. Your expertise includes: + +- Writing clear, well-structured documentation +- Following markdown best practices +- Creating helpful code examples + +Focus on clarity and completeness. Only edit Markdown files. +``` + +### 4. Config File (`kilo.jsonc`) + +Define agents under the `agent` key in your project's `kilo.jsonc`: + +```jsonc +{ + "agent": { + "docs-writer": { + "description": "Specialized for writing and editing technical documentation", + "mode": "primary", + "color": "#10B981", + "prompt": "You are a technical documentation specialist...", + "permission": { + "edit": { + "*.md": "allow", + "*": "deny", + }, + "bash": "deny", + }, + }, + // Override a built-in agent + "code": { + "model": "anthropic/claude-sonnet-4-20250514", + "temperature": 0.3, + }, + }, +} +``` + +## Agent Property Reference + +### `mode` + +Controls where the agent appears: + +| Value | Behavior | +| ---------- | -------------------------------------------------------------------------------------- | +| `primary` | Shown in the agent picker — the user can select it directly | +| `subagent` | Only invokable by other agents via the `task` tool | +| `all` | Available both as a top-level pick and as a subagent (default for user-defined agents) | + +### `permission` + +An ordered set of rules controlling tool access. Permissions support three actions: `allow`, `deny`, and `ask` (prompt the user). You can use glob patterns to scope rules to specific files or commands: + +```yaml +permission: + edit: + "*.md": "allow" + "*": "deny" + bash: deny + read: allow +``` + +Known permission types include: `read`, `edit`, `bash`, `glob`, `grep`, `list`, `task`, `webfetch`, `websearch`, `codesearch`, `todowrite`, `todoread`, and more. + +### `model` + +Pin a specific model using the `provider/model` format: + +```yaml +model: anthropic/claude-sonnet-4-20250514 +``` + +### `steps` + +Limits the number of agentic iterations (tool call rounds) before the agent is forced to respond with text only. Useful for preventing runaway agents: + +```yaml +steps: 25 +``` + +## Configuration Precedence + +Agent configurations merge from lowest to highest priority: + +1. Built-in (native) agent defaults +2. Global config (`~/.config/kilo/kilo.jsonc`) +3. Project config (`kilo.jsonc` at project root) +4. `.kilo/` / `.opencode/` directory configs and agent `.md` files +5. Environment variable overrides (`KILO_CONFIG_CONTENT`) + +When the same agent name appears at multiple levels, properties are merged (not replaced wholesale), so you can override just a model or temperature without redefining the entire agent. + +## Overriding Built-in Agents + +Override any built-in agent (**code**, **plan**, **debug**, **ask**, **orchestrator**, **explore**, **general**) by defining an agent with the same name: + +```jsonc +// kilo.jsonc — override the built-in "code" agent +{ + "agent": { + "code": { + "model": "openai/gpt-4o", + "temperature": 0.2, + "permission": { + "edit": { + "*.py": "allow", + "*": "deny", + }, + }, + }, + }, +} +``` + +Or as a `.md` file (`.kilo/agents/code.md`): + +```markdown +--- +model: openai/gpt-4o +temperature: 0.2 +permission: + edit: + "*.py": "allow" + "*": "deny" +--- + +You are a Python specialist. Only edit Python files. +``` + +## Migration from VSCode Extension Modes + +If you have existing `.kilocodemodes` or `custom_modes.yaml` files from the VSCode extension, the extension automatically migrates them on startup. The migration converts: + +- `slug` to the agent name (key) +- `roleDefinition` + `customInstructions` to `prompt` +- `groups` (e.g., `["read", "edit", "browser"]`) to `permission` rules +- `whenToUse` / `description` to `description` +- Mode is set to `primary` + +Default legacy mode slugs (`code`, `build`, `architect`, `ask`, `debug`, `orchestrator`) are skipped during migration since they map to built-in agents (`build` → `code`, `architect` → `plan`). + +{% /tab %} +{% tab label="CLI" %} + +In the CLI, custom behavioral profiles are called **agents** instead of modes. Agents are defined as Markdown files with YAML frontmatter or as entries in the `agent` key of your config file. + +## What's Included in a Custom Agent? + +| Property | Description | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| **name** (filename) | The agent's identifier, derived from the `.md` filename (e.g., `docs-writer.md` creates an agent named `docs-writer`) | +| **description** | A short summary displayed in the agent picker and used by the orchestrator for delegation | +| **model** | Pin a specific model in `provider/model` format (e.g., `anthropic/claude-sonnet-4-20250514`) | +| **prompt** (markdown body) | The system prompt text — the markdown body of the file, injected into the agent's system prompt | +| **mode** | Role classification: `primary` (user-selectable), `subagent` (only invoked by other agents), or `all` (both) | +| **permission** | Per-agent permission overrides controlling which tools the agent can use (e.g., deny `edit`, `bash`) | +| **color** | Hex color (`#FF5733`) or theme keyword (`primary`, `accent`, `warning`, etc.) for the agent picker UI | +| **steps** | Maximum agentic iterations before forcing a text-only response | +| **temperature** / **top_p** | Sampling parameters for the agent's model | +| **variant** | Default model variant | +| **hidden** | If `true`, the agent is hidden from the UI (only meaningful for subagents) | +| **disable** | If `true`, removes the agent entirely | + +## Methods for Creating and Configuring Agents + +### 1. Ask Kilo! (Recommended) + +Ask Kilo to create an agent for you: + +``` +Create a new agent called "docs-writer" that can only read files and edit Markdown files. +``` + +Kilo will generate the agent definition and write it to `.kilo/agent/` in your project. + +### 2. Using `kilo agent create` + +The CLI provides an interactive command: + +```bash +kilo agent create +``` + +This walks you through selecting a description, mode, and tools, then uses an LLM to generate the agent's system prompt and writes a `.md` file with YAML frontmatter. + +### 3. Markdown Files with YAML Frontmatter + +Create `.md` files in any of these directories: + +``` +.kilo/agents/my-agent.md +.kilo/agent/my-agent.md +.opencode/agents/my-agent.md +``` + +For global agents, place files in your global config directory: + +``` +~/.config/kilo/agent/my-agent.md +``` + +The **filename** (minus `.md`) becomes the agent name. Nested directories create namespaced names (e.g., `agents/backend/sql.md` becomes agent `backend/sql`). + +**Example agent file** (`.kilo/agents/docs-writer.md`): + +```markdown +--- +description: Specialized for writing and editing technical documentation +mode: primary +color: "#10B981" +permission: + edit: + "*.md": "allow" + "*": "deny" + bash: deny +--- + +You are a technical documentation specialist. Your expertise includes: + +- Writing clear, well-structured documentation +- Following markdown best practices +- Creating helpful code examples + +Focus on clarity and completeness. Only edit Markdown files. +``` + +### 4. Config File (`kilo.jsonc`) + +Define agents under the `agent` key in your project's `kilo.jsonc`: + +```jsonc +{ + "agent": { + "docs-writer": { + "description": "Specialized for writing and editing technical documentation", + "mode": "primary", + "color": "#10B981", + "prompt": "You are a technical documentation specialist...", + "permission": { + "edit": { + "*.md": "allow", + "*": "deny", + }, + "bash": "deny", + }, + }, + // Override a built-in agent + "code": { + "model": "anthropic/claude-sonnet-4-20250514", + "temperature": 0.3, + }, + }, +} +``` + +## Agent Property Reference + +### `mode` + +Controls where the agent appears: + +| Value | Behavior | +| ---------- | -------------------------------------------------------------------------------------- | +| `primary` | Shown in the agent picker — the user can select it directly | +| `subagent` | Only invokable by other agents via the `task` tool | +| `all` | Available both as a top-level pick and as a subagent (default for user-defined agents) | + +### `permission` + +An ordered set of rules controlling tool access. Permissions support three actions: `allow`, `deny`, and `ask` (prompt the user). You can use glob patterns to scope rules to specific files or commands: + +```yaml +permission: + edit: + "*.md": "allow" + "*": "deny" + bash: deny + read: allow +``` + +Known permission types include: `read`, `edit`, `bash`, `glob`, `grep`, `list`, `task`, `webfetch`, `websearch`, `codesearch`, `todowrite`, `todoread`, and more. + +### `model` + +Pin a specific model using the `provider/model` format: + +```yaml +model: anthropic/claude-sonnet-4-20250514 +``` + +### `steps` + +Limits the number of agentic iterations (tool call rounds) before the agent is forced to respond with text only. Useful for preventing runaway agents: + +```yaml +steps: 25 +``` + +## Configuration Precedence + +Agent configurations merge from lowest to highest priority: + +1. Built-in (native) agent defaults +2. Global config (`~/.config/kilo/kilo.jsonc`) +3. Project config (`kilo.jsonc` at project root) +4. `.kilo/` / `.opencode/` directory configs and agent `.md` files +5. Environment variable overrides (`KILO_CONFIG_CONTENT`) + +When the same agent name appears at multiple levels, properties are merged (not replaced wholesale), so you can override just a model or temperature without redefining the entire agent. + +## Overriding Built-in Agents + +Override any built-in agent (**code**, **plan**, **debug**, **ask**, **orchestrator**, **explore**, **general**) by defining an agent with the same name: + +```jsonc +// kilo.jsonc — override the built-in "code" agent +{ + "agent": { + "code": { + "model": "openai/gpt-4o", + "temperature": 0.2, + "permission": { + "edit": { + "*.py": "allow", + "*": "deny", + }, + }, + }, + }, +} +``` + +Or as a `.md` file (`.kilo/agents/code.md`): + +```markdown +--- +model: openai/gpt-4o +temperature: 0.2 +permission: + edit: + "*.py": "allow" + "*": "deny" +--- + +You are a Python specialist. Only edit Python files. +``` + +## Migration from VSCode Extension Modes + +If you have existing `.kilocodemodes` or `custom_modes.yaml` files from the VSCode extension, the CLI automatically migrates them on startup. The migration converts: + +- `slug` to the agent name (key) +- `roleDefinition` + `customInstructions` to `prompt` +- `groups` (e.g., `["read", "edit", "browser"]`) to `permission` rules +- `whenToUse` / `description` to `description` +- Mode is set to `primary` + +Default legacy mode slugs (`code`, `build`, `architect`, `ask`, `debug`, `orchestrator`) are skipped during migration since they map to built-in agents (`build` → `code`, `architect` → `plan`). + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +## Sticky Models for Efficient Workflow + +Each mode—including custom ones—features **Sticky Models**. This means Kilo Code automatically remembers and selects the last model you used with a particular mode. This lets you assign different preferred models to different tasks without constant reconfiguration, as Kilo switches between models when you change modes. + {% callout type="tip" %} **Keep custom modes on track:** Limit the types of files that they're allowed to edit using the `fileRegex` option in the `groups` configuration. This prevents modes from accidentally modifying files outside their intended scope. {% /callout %} @@ -89,6 +526,57 @@ When importing modes, you can change the slug in the exported YAML file before i ## Methods for Creating and Configuring Custom Modes +{% tabs %} +{% tab label="VSCode" %} + +Custom agents are defined as Markdown files with optional YAML frontmatter. You can place them in: + +- **Project agents:** `.kilo/agents/*.md` (or `.opencode/agents/*.md`) +- **Global agents:** `~/.config/kilo/agents/*.md` + +### Agent File Format + +```markdown +--- +model: anthropic/claude-3-5-sonnet-20241022 +description: A specialized agent for writing documentation +mode: primary +--- + +You are a technical writer specializing in clear, concise documentation. +Focus on clarity, completeness, and consistent formatting. +``` + +**YAML frontmatter fields:** + +| Field | Description | +| ------------- | ----------------------------------------------------------------------------- | +| `model` | Override the default model for this agent | +| `description` | Short description shown in the agent selector | +| `mode` | `"primary"` (user-selectable), `"subagent"` (invoked by AI only), or `"all"` | +| `permission` | Tool permission overrides (same format as the global `permission` config key) | +| `temperature` | Model temperature override | +| `top_p` | Model top_p override | + +The filename (without `.md`) becomes the agent's slug and display name. + +### Installing via Marketplace + +You can also install community-contributed agents from the **Marketplace** tab in the extension sidebar. + +### Ask Kilo! (Recommended) + +You can also have Kilo create an agent file for you. For example: + +``` +Create a new agent called "Documentation Writer". It should only be able to read files and write Markdown files. +``` + +Kilo will create the appropriate `.kilo/agents/docs-writer.md` file with the right frontmatter. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + You can create and configure custom modes in several ways: ### 1. Ask Kilo! (Recommended) @@ -135,8 +623,6 @@ If you see both YAML and JSON mode files, this is usually from legacy configurat YAML is now the preferred format for defining custom modes due to better readability, comment support, and cleaner multi-line strings. -### YAML Example - ```yaml customModes: - slug: docs-writer @@ -388,9 +874,46 @@ customModes: - command ``` +{% /tab %} +{% /tabs %} + ## Understanding Regex in Custom Modes -Regular expressions (`fileRegex`) offer fine-grained control over file editing permissions. +{% tabs %} +{% tab label="VSCode" %} + +The extension uses **permission rules with glob patterns** instead of regex. Permissions are defined per-tool (e.g., `edit`, `bash`, `read`) and support `allow`, `deny`, and `ask` actions with glob matching: + +```yaml +permission: + edit: + "*.md": "allow" + "*": "deny" +``` + +The **VSCode (Legacy)** version's `fileRegex` approach is automatically converted to permission rules during migration. + +{% /tab %} +{% tab label="CLI" %} + +The CLI uses **permission rules with glob patterns** instead of regex. Permissions are defined per-tool (e.g., `edit`, `bash`, `read`) and support `allow`, `deny`, and `ask` actions with glob matching: + +```yaml +permission: + edit: + "*.md": "allow" + "*": "deny" +``` + +The **VSCode (Legacy)** version's `fileRegex` approach is automatically converted to permission rules during migration. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +Regular expressions (`fileRegex`) in the **VSCode** version offer fine-grained control over file editing permissions within tool groups. + +{% /tab %} +{% /tabs %} {% callout type="tip" %} @@ -445,6 +968,183 @@ When a mode attempts to edit a file that doesn't match its `fileRegex` pattern, ## Example Configurations +{% tabs %} +{% tab label="VSCode" %} + +### Basic Documentation Writer (`.kilo/agents/docs-writer.md`) + +```markdown +--- +description: Specialized for writing and editing technical documentation +mode: primary +color: "#10B981" +permission: + edit: + "*.md": "allow" + "*": "deny" + bash: deny +--- + +You are a technical writer specializing in clear documentation. +Focus on clear explanations and examples. +``` + +### Test Engineer (`.kilo/agents/test-engineer.md`) + +```markdown +--- +description: Focused on writing and maintaining test suites +mode: primary +color: "#F59E0B" +permission: + edit: + "*.{test,spec}.{js,ts}": "allow" + "*": "deny" +--- + +You are a test engineer focused on code quality. +Use for writing tests, debugging test failures, and improving test coverage. +``` + +### Security Reviewer (`.kilo/agents/security-review.md`) + +```markdown +--- +description: Read-only security analysis and vulnerability assessment +mode: primary +color: "#EF4444" +permission: + edit: deny + bash: deny +--- + +You are a security specialist reviewing code for vulnerabilities. + +Focus on: + +- Input validation issues +- Authentication and authorization flaws +- Data exposure risks +- Injection vulnerabilities +``` + +### Config File Example (`kilo.jsonc`) + +```jsonc +{ + "agent": { + "docs-writer": { + "description": "Specialized for writing and editing technical documentation", + "mode": "primary", + "color": "#10B981", + "prompt": "You are a technical writer specializing in clear documentation.", + "permission": { + "edit": { "*.md": "allow", "*": "deny" }, + "bash": "deny", + }, + }, + "test-engineer": { + "description": "Focused on writing and maintaining test suites", + "mode": "primary", + "prompt": "You are a test engineer focused on code quality.", + "permission": { + "edit": { "*.{test,spec}.{js,ts}": "allow", "*": "deny" }, + }, + }, + }, +} +``` + +{% /tab %} +{% tab label="CLI" %} + +### Basic Documentation Writer (`.kilo/agents/docs-writer.md`) + +```markdown +--- +description: Specialized for writing and editing technical documentation +mode: primary +color: "#10B981" +permission: + edit: + "*.md": "allow" + "*": "deny" + bash: deny +--- + +You are a technical writer specializing in clear documentation. +Focus on clear explanations and examples. +``` + +### Test Engineer (`.kilo/agents/test-engineer.md`) + +```markdown +--- +description: Focused on writing and maintaining test suites +mode: primary +color: "#F59E0B" +permission: + edit: + "*.{test,spec}.{js,ts}": "allow" + "*": "deny" +--- + +You are a test engineer focused on code quality. +Use for writing tests, debugging test failures, and improving test coverage. +``` + +### Security Reviewer (`.kilo/agents/security-review.md`) + +```markdown +--- +description: Read-only security analysis and vulnerability assessment +mode: primary +color: "#EF4444" +permission: + edit: deny + bash: deny +--- + +You are a security specialist reviewing code for vulnerabilities. + +Focus on: + +- Input validation issues +- Authentication and authorization flaws +- Data exposure risks +- Injection vulnerabilities +``` + +### Config File Example (`kilo.jsonc`) + +```jsonc +{ + "agent": { + "docs-writer": { + "description": "Specialized for writing and editing technical documentation", + "mode": "primary", + "color": "#10B981", + "prompt": "You are a technical writer specializing in clear documentation.", + "permission": { + "edit": { "*.md": "allow", "*": "deny" }, + "bash": "deny", + }, + }, + "test-engineer": { + "description": "Focused on writing and maintaining test suites", + "mode": "primary", + "prompt": "You are a test engineer focused on code quality.", + "permission": { + "edit": { "*.{test,spec}.{js,ts}": "allow", "*": "deny" }, + }, + }, + }, +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ### Basic Documentation Writer (YAML) ```yaml @@ -498,8 +1198,48 @@ customModes: - browser ``` +{% /tab %} +{% /tabs %} + ## Troubleshooting +{% tabs %} +{% tab label="VSCode" %} + +### Common Issues + +- **Agent not appearing:** Ensure the `.md` file is in a recognized directory (`.kilo/agents/`, `.kilo/agent/`, `.opencode/agents/`). Check that the `mode` property is `primary` or `all` if you expect it in the agent picker. +- **Permission errors:** Permission rules are evaluated last-match-wins. If an agent can't use a tool you expect, check that an `allow` rule appears after any `deny` rules for that permission. +- **YAML frontmatter parse errors:** Ensure the frontmatter block starts and ends with `---` on its own line. Validate that YAML keys match expected property names (e.g., `top_p` not `topP`). +- **Agent overrides not working:** Config merges from global to project level. If a global config sets a property, your project config can override it, but both must use the same agent name. + +### Tips for Agent Definitions + +- **Keep prompts focused:** The markdown body is your system prompt — write it as if briefing a colleague +- **Use `mode: subagent`** for helper agents that shouldn't be directly selectable by users +- **Use the Settings UI** to view and edit agents through the **Settings → Agent Behaviour → Agents** subtab +- **Legacy modes are auto-migrated:** If you have `.kilocodemodes` files, they'll be converted on startup — no manual migration needed + +{% /tab %} +{% tab label="CLI" %} + +### Common Issues + +- **Agent not appearing:** Ensure the `.md` file is in a recognized directory (`.kilo/agents/`, `.kilo/agent/`, `.opencode/agents/`). Check that the `mode` property is `primary` or `all` if you expect it in the agent picker. +- **Permission errors:** Permission rules are evaluated last-match-wins. If an agent can't use a tool you expect, check that an `allow` rule appears after any `deny` rules for that permission. +- **YAML frontmatter parse errors:** Ensure the frontmatter block starts and ends with `---` on its own line. Validate that YAML keys match expected property names (e.g., `top_p` not `topP`). +- **Agent overrides not working:** Config merges from global to project level. If a global config sets a property, your project config can override it, but both must use the same agent name. + +### Tips for Agent Definitions + +- **Keep prompts focused:** The markdown body is your system prompt — write it as if briefing a colleague +- **Use `mode: subagent`** for helper agents that shouldn't be directly selectable by users +- **Test with `kilo agent create`** to see how the CLI generates agent definitions, then customize from there +- **Legacy modes are auto-migrated:** If you have `.kilocodemodes` files, they'll be converted on startup — no manual migration needed + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ### Common Issues - **Mode not appearing:** After creating or importing a mode, you may need to reload the VS Code window @@ -514,6 +1254,9 @@ customModes: - **Hyphens for List Items:** List items start with a hyphen and a space (e.g., `- read`) - **Validate Your YAML:** Use online YAML validators or your editor's built-in validation +{% /tab %} +{% /tabs %} + ## Community Gallery -Ready to explore more? Check out the [Show and Tell](https://github.com/Kilo-Org/kilocode/discussions/categories/show-and-tell) to discover and share custom modes created by the community! +Ready to explore more? Check out the [Show and Tell](https://github.com/Kilo-Org/kilocode/discussions/categories/show-and-tell) to discover and share custom modes and agents created by the community! diff --git a/packages/kilo-docs/pages/customize/custom-rules.md b/packages/kilo-docs/pages/customize/custom-rules.md index ccecb0528ec..eb3c7fea120 100644 --- a/packages/kilo-docs/pages/customize/custom-rules.md +++ b/packages/kilo-docs/pages/customize/custom-rules.md @@ -26,11 +26,82 @@ Kilo Code supports two types of custom rules: - **Project Rules**: Apply only to the current project workspace - **Global Rules**: Apply across all projects and workspaces -{% callout type="note" title="UI Support" %} -The built-in rules management UI is available for general rules only. Mode-specific rules must be managed through the file system. +## Rule Location + +{% tabs %} +{% tab label="VSCode" %} + +### Project Rules + +Project rules are configured via the `instructions` key in your project's `kilo.jsonc` file. You can edit this file directly or use the **Settings** webview to manage the `instructions` configuration. Each entry points to a file path or glob pattern: + +```jsonc +// kilo.jsonc +{ + "instructions": [".kilo/rules/formatting.md", ".kilo/rules/*.md"], +} +``` + +You can also place rule files in the **`.kilo/`** directory structure: + +``` +project/ +├── .kilo/ +│ ├── rules/ +│ │ ├── formatting.md +│ │ ├── restricted_files.md +│ │ └── naming_conventions.md +├── kilo.json +├── src/ +└── ... +``` + +### Global Rules + +Global rules are configured via the `instructions` key in your global `kilo.jsonc` config file (typically at `~/.config/kilo/kilo.jsonc`). + +{% callout type="note" title="Migration" %} +The extension is backward compatible with `.kilocode/rules/` directories. Existing rules will continue to work, but migrating to `kilo.jsonc` is recommended. {% /callout %} -## Rule Location +{% /tab %} +{% tab label="CLI" %} + +### Project Rules + +Project rules are configured via the `instructions` key in your project's `kilo.jsonc` file. Each entry points to a file path or glob pattern: + +```jsonc +// kilo.jsonc +{ + "instructions": [".kilo/rules/formatting.md", ".kilo/rules/*.md"], +} +``` + +You can also place rule files in the **`.kilo/`** directory structure: + +``` +project/ +├── .kilo/ +│ ├── rules/ +│ │ ├── formatting.md +│ │ ├── restricted_files.md +│ │ └── naming_conventions.md +├── kilo.json +├── src/ +└── ... +``` + +### Global Rules + +Global rules are configured via the `instructions` key in your global `kilo.jsonc` config file (typically at `~/.config/kilo/kilo.jsonc`). + +{% callout type="note" title="Migration" %} +The CLI is backward compatible with `.kilocode/rules/` directories. Existing rules will continue to work, but migrating to `kilo.jsonc` is recommended. +{% /callout %} + +{% /tab %} +{% tab label="VSCode (Legacy)" %} ### Project Rules @@ -59,8 +130,54 @@ Global rules are stored in your home directory and apply to all projects: │ └── documentation_style.md ``` +{% /tab %} +{% /tabs %} + ## Managing Rules Through the UI +{% tabs %} +{% tab label="VSCode" %} + +Rules are managed by editing the `instructions` array in your `kilo.jsonc` config file. You can also use the **Settings** webview in VS Code to edit the configuration. + +- **Add a rule**: Add a file path or glob pattern to the `instructions` array +- **Remove a rule**: Remove the entry from the array +- **Disable a rule temporarily**: Comment out the line in `kilo.jsonc` (JSONC supports `//` comments) + +```jsonc +// kilo.jsonc +{ + "instructions": [ + ".kilo/rules/formatting.md", + // ".kilo/rules/experimental.md" -- temporarily disabled + ".kilo/rules/naming_conventions.md", + ], +} +``` + +{% /tab %} +{% tab label="CLI" %} + +Rules are managed by editing the `instructions` array in your `kilo.jsonc` config file directly. + +- **Add a rule**: Add a file path or glob pattern to the `instructions` array +- **Remove a rule**: Remove the entry from the array +- **Disable a rule temporarily**: Comment out the line in `kilo.jsonc` (JSONC supports `//` comments) + +```jsonc +// kilo.jsonc +{ + "instructions": [ + ".kilo/rules/formatting.md", + // ".kilo/rules/experimental.md" -- temporarily disabled + ".kilo/rules/naming_conventions.md", + ], +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Kilo Code provides a built-in interface for managing your custom rules without manually editing files in the `.kilocode/rules/` directories. To access the UI, click on the icon in the **bottom right corner** of the Kilo Code window. You can access the rules management UI to: @@ -70,8 +187,46 @@ You can access the rules management UI to: - Create and edit rules directly in the interface - Organize rules by category and priority +{% callout type="note" title="UI Support" %} +The built-in rules management UI is available for general rules only. Mode-specific rules must be managed through the file system. +{% /callout %} + +{% /tab %} +{% /tabs %} + ## Rule Loading Order +{% tabs %} +{% tab label="VSCode" %} + +Rules are loaded in the order they appear in the `instructions` array in `kilo.jsonc`: + +1. **Global instructions** from the global `kilo.jsonc` config +2. **Project instructions** from the project's `kilo.jsonc` + +Files matched by glob patterns are loaded in filesystem order. Project-level instructions take precedence over global instructions for conflicting directives. + +{% callout type="note" title="Backward Compatibility" %} +If `.kilocode/rules/` directories exist in your project, their contents are automatically included for backward compatibility. To fully migrate, move your rule files and reference them in `kilo.jsonc`. +{% /callout %} + +{% /tab %} +{% tab label="CLI" %} + +Rules are loaded in the order they appear in the `instructions` array in `kilo.jsonc`: + +1. **Global instructions** from the global `kilo.jsonc` config +2. **Project instructions** from the project's `kilo.jsonc` + +Files matched by glob patterns are loaded in filesystem order. Project-level instructions take precedence over global instructions for conflicting directives. + +{% callout type="note" title="Backward Compatibility" %} +If `.kilocode/rules/` directories exist in your project, their contents are automatically included for backward compatibility. To fully migrate, move your rule files and reference them in `kilo.jsonc`. +{% /callout %} + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ### General Rules (Any Mode) Rules are loaded in the following priority order: @@ -86,21 +241,64 @@ Rules are loaded in the following priority order: When both global and project rules exist, they are combined with project rules taking precedence over global rules for conflicting directives. {% callout type="note" %} -We strongly recommend keeping your rules in the `.kilocode/rules/` folder as it provides better organization and is the preferred approach for future versions. The folder-based structure allows for more granular rule organization and clearer separation of concerns. The legacy file-based approach is maintained for backward compatibility but may be subject to change in future releases. +We strongly recommend keeping your rules in the `.kilocode/rules/` folder as it provides better organization and is the preferred approach for future versions. The legacy file-based approach is maintained for backward compatibility but may be subject to change in future releases. {% /callout %} ### Mode-Specific Rules -Additionally, the system supports mode-specific rules, which are loaded separately and have their own priority order: +The system also supports mode-specific rules with their own priority order: 1. First, it checks for `.kilocode/rules-${mode}/` directory 2. If that doesn't exist or is empty, it falls back to `.kilocoderules-${mode}` file (deprecated) -Currently, mode-specific rules are only supported at the project level. -When both generic rules and mode-specific rules exist, the mode-specific rules are given priority in the final output. +Mode-specific rules are only supported at the project level. When both generic and mode-specific rules exist, mode-specific rules take priority. + +{% /tab %} +{% /tabs %} ## Creating Custom Rules +{% tabs %} +{% tab label="VSCode" %} + +### Using the Settings UI or Config File + +1. Create a `kilo.jsonc` file in your project root (if it doesn't exist) +2. Create a `.kilo/rules/` directory (or any directory you prefer) +3. Write your rule as a Markdown file in that directory +4. Add the file path or a glob pattern to the `instructions` array in `kilo.jsonc` + +```jsonc +// kilo.jsonc +{ + "instructions": [".kilo/rules/my-new-rule.md"], +} +``` + +Rules are applied on the next interaction. You can also edit `kilo.jsonc` through the **Settings** webview in VS Code. + +{% /tab %} +{% tab label="CLI" %} + +### Using the Config File + +1. Create a `kilo.jsonc` file in your project root (if it doesn't exist) +2. Create a `.kilo/rules/` directory (or any directory you prefer) +3. Write your rule as a Markdown file in that directory +4. Add the file path or a glob pattern to the `instructions` array in `kilo.jsonc` + +```jsonc +// kilo.jsonc +{ + "instructions": [".kilo/rules/my-new-rule.md"], +} +``` + +Rules are applied on the next interaction. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ### Using the UI Interface {% image src="/docs/img/custom-rules/rules-ui.png" alt="Rules tab in Kilo Code" width="400" /%} @@ -132,6 +330,9 @@ To create rules manually: Rules will be automatically applied to all future Kilo Code interactions. Any new changes will be applied immediately. +{% /tab %} +{% /tabs %} + ## Example Rules ### Example 1: Table Formatting @@ -192,10 +393,6 @@ Custom rules can be applied to a wide variety of scenarios: - **Keep It Simple**: Rules should be concise and easy to understand - **Update Regularly**: Review and update rules as project requirements change -{% callout type="tip" title="Pro Tip: File-Based Team Standards" %} -When working in team environments, placing `.kilocode/rules/codestyle.md` files under version control allows you to standardize Kilo's behavior across your entire development team. This ensures consistent code style, documentation practices, and development workflows for everyone on the project. -{% /callout %} - ## Limitations - Rules are applied on a best-effort basis by the AI models @@ -205,16 +402,41 @@ When working in team environments, placing `.kilocode/rules/codestyle.md` files ## Troubleshooting +{% tabs %} +{% tab label="VSCode" %} + +If your rules aren't being followed: + +1. **Check the `instructions` array** in your config to ensure the file path is correct. +2. **Verify Markdown formatting**: Ensure the file is valid Markdown. +3. **Restart the session**: Start a new chat session to pick up config changes. + +{% /tab %} +{% tab label="CLI" %} + +If your rules aren't being followed: + +1. **Check the `instructions` array** in your config to ensure the file path is correct. +2. **Verify Markdown formatting**: Ensure the file is valid Markdown. +3. **Restart the session**: Start a new chat session to pick up config changes. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + If your custom rules aren't being properly followed: -1. **Check rule status in the UI**: Use the rules management interface to verify that your rules are active and properly loaded 1. **Verify rule formatting**: Ensure that your rules are properly formatted with clear Markdown structure -1. **Check rule locations**: Ensure that your rules are located in supported locations: - - Global rules: `~/.kilocode/rules/` directory - - Project rules: `.kilocode/rules/` directory - - Legacy files: `.kilocoderules`, `.roorules`, or `.clinerules` -1. **Rule specificity**: Verify that the rules are specific and unambiguous -1. **Restart VS Code**: Restart VS Code to ensure the rules are properly loaded +2. **Rule specificity**: Verify that the rules are specific and unambiguous +3. **Check rule locations**: + - **Check rule status in the UI**: Use the rules management interface to verify that your rules are active and properly loaded + - Ensure rules are in supported locations: + - Global rules: `~/.kilocode/rules/` directory + - Project rules: `.kilocode/rules/` directory + - Legacy files: `.kilocoderules`, `.roorules`, or `.clinerules` + - **Restart VS Code** to ensure the rules are properly loaded + +{% /tab %} +{% /tabs %} ## Related Features diff --git a/packages/kilo-docs/pages/customize/custom-subagents.md b/packages/kilo-docs/pages/customize/custom-subagents.md index e97090c4a1e..ed4b3340a40 100644 --- a/packages/kilo-docs/pages/customize/custom-subagents.md +++ b/packages/kilo-docs/pages/customize/custom-subagents.md @@ -1,6 +1,7 @@ --- title: "Custom Subagents" description: "Create and configure custom subagents in Kilo Code's CLI" +platform: new --- # Custom Subagents @@ -8,7 +9,7 @@ description: "Create and configure custom subagents in Kilo Code's CLI" Kilo Code's CLI supports **custom subagents** — specialized AI assistants that can be invoked by primary agents or manually via `@` mentions. Subagents run in their own isolated sessions with tailored prompts, models, tool access, and permissions, enabling you to build purpose-built workflows for tasks like code review, documentation, security audits, and more. {% callout type="info" %} -Custom subagents are currently configured through the config file (`kilo.json`) or via markdown agent files. UI-based configuration is not yet available. +Custom subagents are currently configured through the config file (`kilo.jsonc`) or via markdown agent files. UI-based configuration is not yet available. {% /callout %} ## What Are Subagents? @@ -47,7 +48,7 @@ There are two ways to define custom subagents: through JSON configuration or mar ### Method 1: JSON Configuration -Add agents to the `agent` section of your `kilo.json` config file. Any key that doesn't match a built-in agent name creates a new custom agent. +Add agents to the `agent` section of your `kilo.jsonc` config file. Any key that doesn't match a built-in agent name creates a new custom agent. ```json { @@ -245,8 +246,8 @@ Agent configurations are merged from multiple sources. Later sources override ea 1. **Built-in agent defaults** (native agents defined in the codebase) 2. **Global config** (`~/.config/kilo/config.json`) -3. **Global agent markdown files** (`~/.config/kilo/agents/*.md`) -4. **Project config** (`kilo.json` in the project root) +3. **Project config** (`kilo.jsonc` in the project root) +4. **Global agent markdown files** (`~/.config/kilo/agents/*.md`) 5. **Project agent markdown files** (`.kilo/agents/*.md`) When overriding a built-in agent, properties are merged — only the fields you specify are overridden. When creating a new custom agent, unspecified fields use sensible defaults (`mode: "all"`, full permissions inherited from global config). @@ -373,5 +374,5 @@ To disable a built-in agent entirely: - [Custom Modes](/docs/customize/custom-modes) — Create specialized primary agents with tool restrictions - [Custom Rules](/docs/customize/custom-rules) — Define rules that apply to specific file types or situations -- [Orchestrator Mode](/docs/code-with-ai/agents/orchestrator-mode) — Coordinate complex tasks by delegating to subagents +- [Orchestrator Mode](/docs/code-with-ai/agents/orchestrator-mode) — Legacy mode for task delegation (now built into all agents) - [Task Tool](/docs/automate/tools/new-task) — The tool used to invoke subagents diff --git a/packages/kilo-docs/pages/customize/skills.md b/packages/kilo-docs/pages/customize/skills.md index 8f553d681ef..cc180e4df6a 100644 --- a/packages/kilo-docs/pages/customize/skills.md +++ b/packages/kilo-docs/pages/customize/skills.md @@ -47,6 +47,115 @@ This means: Skills are loaded from multiple locations, allowing both personal skills and project-specific instructions. +{% tabs %} +{% tab label="VSCode" %} + +### Global Skills (User-Level) + +Global skills are located in the `.kilo` directory within your Home directory: + +- Mac and Linux: `~/.kilo/skills/` +- Windows: `\Users\\.kilo\skills\` + +``` +~/.kilo/ +└── skills/ # Generic skills (all modes) + ├── my-skill/ + │ └── SKILL.md + └── another-skill/ + └── SKILL.md +``` + +### Project Skills (Workspace-Level) + +Located in `.kilo/skills/` within your project: + +``` +your-project/ +└── .kilo/ + └── skills/ # Generic skills for this project + └── project-conventions/ + └── SKILL.md +``` + +### Compatibility Directories + +For interoperability with other tools, the CLI also loads skills from: + +- `.claude/skills/` — Claude Code compatibility +- `.agents/skills/` — Open agent standard + +### Additional Skill Paths and Remote URLs + +You can configure extra skill locations and remote skill URLs in your `kilo.jsonc` config (project or global): + +```jsonc +{ + "skills": { + "paths": ["/path/to/shared/skills", "~/my-skills", "relative/skills"], + "urls": ["https://example.com/skills/my-skill/SKILL.md"], + }, +} +``` + +The `skills.paths` key accepts absolute paths, `~/` home-relative paths, or paths relative to the project root. The `skills.urls` key accepts URLs pointing to remote `SKILL.md` files that are fetched on demand. + +{% /tab %} +{% tab label="CLI" %} + +### Global Skills (User-Level) + +Global skills are located in the `.kilo` directory within your Home directory: + +- Mac and Linux: `~/.kilo/skills/` +- Windows: `\Users\\.kilo\skills\` + +``` +~/.kilo/ +└── skills/ # Generic skills (all modes) + ├── my-skill/ + │ └── SKILL.md + └── another-skill/ + └── SKILL.md +``` + +### Project Skills (Workspace-Level) + +Located in `.kilo/skills/` within your project: + +``` +your-project/ +└── .kilo/ + └── skills/ # Generic skills for this project + └── project-conventions/ + └── SKILL.md +``` + +### Compatibility Directories + +For interoperability with other tools, the CLI also loads skills from: + +- `.claude/skills/` — Claude Code compatibility +- `.agents/skills/` — Open agent standard + +### Additional Skill Paths and Remote URLs + +You can configure extra skill locations and remote skill URLs in your `kilo.jsonc` config (project or global): + +```jsonc +{ + "skills": { + "paths": ["/path/to/shared/skills", "~/my-skills", "relative/skills"], + "urls": ["https://example.com/skills/my-skill/SKILL.md"], + }, +} +``` + +The `skills.paths` key accepts absolute paths, `~/` home-relative paths, or paths relative to the project root. The `skills.urls` key accepts URLs pointing to remote `SKILL.md` files that are fetched on demand. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ### Global Skills (User-Level) Global skills are located in the `.kilocode` directory within your Home directory. @@ -84,9 +193,29 @@ your-project/ └── SKILL.md ``` +{% /tab %} +{% /tabs %} + ## Mode-Specific Skills -To create a skill that only appears in a specific mode: +{% tabs %} +{% tab label="VSCode" %} + +The new platform does not use mode-specific skill directories. All skills are loaded into a shared pool and the agent decides which skill to invoke based on the skill's `description` field and the current task context. + +If you need a skill to only apply in certain situations, write a clear and specific `description` in the SKILL.md frontmatter so the agent knows when to use it. + +{% /tab %} +{% tab label="CLI" %} + +The new platform does not use mode-specific skill directories. All skills are loaded into a shared pool and the agent decides which skill to invoke based on the skill's `description` field and the current task context. + +If you need a skill to only apply in certain situations, write a clear and specific `description` in the SKILL.md frontmatter so the agent knows when to use it. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +To create a skill that only appears in a specific mode, place it in a `skills-{mode-slug}` directory: ```bash # For Code mode only @@ -98,8 +227,24 @@ mkdir -p ~/.kilocode/skills-architect/microservices The directory naming pattern is `skills-{mode-slug}` where `{mode-slug}` matches the mode's identifier (e.g., `code`, `architect`, `ask`, `debug`). +{% /tab %} +{% /tabs %} + ## Priority and Overrides +{% tabs %} +{% tab label="VSCode" %} + +When multiple skills share the same name, project-level skills (`.kilo/skills/`) take precedence over global skills (`~/.kilo/skills/`). Skills from compatibility directories (`.claude/skills/`, `.agents/skills/`) and additional configured paths are loaded alongside project and global skills. + +{% /tab %} +{% tab label="CLI" %} + +When multiple skills share the same name, project-level skills (`.kilo/skills/`) take precedence over global skills (`~/.kilo/skills/`). Skills from compatibility directories (`.claude/skills/`, `.agents/skills/`) and additional configured paths are loaded alongside project and global skills. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + When multiple skills share the same name, Kilo Code uses these priority rules: 1. **Project skills override global skills** - A project skill with the same name takes precedence @@ -111,8 +256,34 @@ This allows you to: - Override them per-project when needed - Customize behavior for specific modes +{% /tab %} +{% /tabs %} + ## When Skills Are Loaded +{% tabs %} +{% tab label="VSCode" %} + +Skills are discovered when a session starts. The CLI scans all configured skill directories and reads metadata (name, description, file path) for each skill. + +- In the **CLI**: Skills are loaded when you start a new session or run `kilo run` +- In the **VS Code extension**: Skills are loaded when the extension connects to the CLI server + +Skills are re-scanned at the start of each new session. To pick up newly added or modified skills, start a new session. + +{% /tab %} +{% tab label="CLI" %} + +Skills are discovered when a session starts. The CLI scans all configured skill directories and reads metadata (name, description, file path) for each skill. + +- In the **CLI**: Skills are loaded when you start a new session or run `kilo run` +- In the **VS Code extension**: Skills are loaded when the extension connects to the CLI server + +Skills are re-scanned at the start of each new session. To pick up newly added or modified skills, start a new session. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Skills are discovered when Kilo Code initializes: - When VSCode starts @@ -126,6 +297,9 @@ Skills directories are monitored for changes to `SKILL.md` files. However, the m You can symlink skills directories to share skills across machines or from a central repository. When using symlinks, the skill's `name` field must match the **symlink name**, not the target directory name. +{% /tab %} +{% /tabs %} + ## SKILL.md Format The `SKILL.md` file uses YAML frontmatter followed by Markdown content containing the instructions: @@ -213,62 +387,145 @@ These additional files can be referenced from your skill's instructions, allowin ## Example: Creating a Skill +{% tabs %} +{% tab label="VSCode" %} + +1. Create the skill directory: + + ```bash + mkdir -p ~/.kilo/skills/api-design + ``` + +2. Create `SKILL.md` (see content below) + +3. Start a new session to pick up the skill + +{% /tab %} +{% tab label="CLI" %} + +1. Create the skill directory: + + ```bash + mkdir -p ~/.kilo/skills/api-design + ``` + +2. Create `SKILL.md` (see content below) + +3. Start a new session to pick up the skill + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + 1. Create the skill directory: ```bash mkdir -p ~/.kilocode/skills/api-design ``` -2. Create `SKILL.md`: - - ```markdown - --- - name: api-design - description: REST API design best practices and conventions - --- - - # API Design Guidelines - - When designing REST APIs, follow these conventions: - - ## URL Structure - - - Use plural nouns for resources: `/users`, `/orders` - - Use kebab-case for multi-word resources: `/order-items` - - Nest related resources: `/users/{id}/orders` - - ## HTTP Methods - - - GET: Retrieve resources - - POST: Create new resources - - PUT: Replace entire resource - - PATCH: Partial update - - DELETE: Remove resource - - ## Response Codes - - - 200: Success - - 201: Created - - 400: Bad Request - - 404: Not Found - - 500: Server Error - ``` +2. Create `SKILL.md` (see content below) 3. Reload VSCode to load the skill 4. The skill will now be available in all modes +{% /tab %} +{% /tabs %} + +Example `SKILL.md`: + +```markdown +--- +name: api-design +description: REST API design best practices and conventions +--- + +# API Design Guidelines + +When designing REST APIs, follow these conventions: + +## URL Structure + +- Use plural nouns for resources: `/users`, `/orders` +- Use kebab-case for multi-word resources: `/order-items` +- Nest related resources: `/users/{id}/orders` + +## HTTP Methods + +- GET: Retrieve resources +- POST: Create new resources +- PUT: Replace entire resource +- PATCH: Partial update +- DELETE: Remove resource + +## Response Codes + +- 200: Success +- 201: Created +- 400: Bad Request +- 404: Not Found +- 500: Server Error +``` + ## Finding Skills +{% tabs %} +{% tab label="VSCode" %} + +The new platform does not have a marketplace UI yet. You can find and share skills through: + +- **[Kilo Marketplace repository](https://github.com/Kilo-Org/kilo-marketplace)** — Browse community skills on GitHub and manually download them into your skills directory +- **[Agent Skills Specification](https://agentskills.io/home)** — The open specification that skills follow, enabling interoperability across different AI agents +- **Remote URLs** — Use the `skills.urls` config key to load skills directly from URLs without manually downloading them + +{% /tab %} +{% tab label="CLI" %} + +The new platform does not have a marketplace UI yet. You can find and share skills through: + +- **[Kilo Marketplace repository](https://github.com/Kilo-Org/kilo-marketplace)** — Browse community skills on GitHub and manually download them into your skills directory +- **[Agent Skills Specification](https://agentskills.io/home)** — The open specification that skills follow, enabling interoperability across different AI agents +- **Remote URLs** — Use the `skills.urls` config key to load skills directly from URLs without manually downloading them + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + You can discover and install community-created skills through: -- **Kilo Marketplace** - Browse skills directly in the Kilo Code extension via the Marketplace tab, or explore the [Kilo Marketplace repository](https://github.com/Kilo-Org/kilo-marketplace) on GitHub -- [Agent Skills Specification](https://agentskills.io/home) - The open specification that skills follow, enabling interoperability across different AI agents +- **Kilo Marketplace** — Browse skills directly in the Kilo Code extension via the Marketplace tab, or explore the [Kilo Marketplace repository](https://github.com/Kilo-Org/kilo-marketplace) on GitHub +- [Agent Skills Specification](https://agentskills.io/home) — The open specification that skills follow, enabling interoperability across different AI agents + +{% /tab %} +{% /tabs %} ## Troubleshooting ### Skill Not Loading? +{% tabs %} +{% tab label="VSCode" %} + +1. **Verify frontmatter**: Ensure `name` and `description` are present in the YAML frontmatter. The `name` does not need to match the directory name but should be unique across all loaded skills. + +2. **Start a new session**: Skills are scanned at session start. Begin a new session to pick up changes. + +3. **Check file location**: Ensure `SKILL.md` is directly inside the skill directory (e.g., `.kilo/skills/my-skill/SKILL.md`), not nested further. + +4. **Check config paths**: If using `skills.paths` or `skills.urls`, verify the paths and URLs are correct in your `kilo.jsonc`. + +{% /tab %} +{% tab label="CLI" %} + +1. **Verify frontmatter**: Ensure `name` and `description` are present in the YAML frontmatter. The `name` does not need to match the directory name but should be unique across all loaded skills. + +2. **Start a new session**: Skills are scanned at session start. Begin a new session to pick up changes. + +3. **Check file location**: Ensure `SKILL.md` is directly inside the skill directory (e.g., `.kilo/skills/my-skill/SKILL.md`), not nested further. + +4. **Check config paths**: If using `skills.paths` or `skills.urls`, verify the paths and URLs are correct in your `kilo.jsonc`. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + 1. **Check the Output panel**: Open `View` → `Output` → Select "Kilo Code" from dropdown. Look for skill-related errors. 2. **Verify frontmatter**: Ensure `name` exactly matches the directory name and `description` is present. @@ -277,6 +534,9 @@ You can discover and install community-created skills through: 4. **Check file location**: Ensure `SKILL.md` is directly inside the skill directory, not nested further. +{% /tab %} +{% /tabs %} + ### Verifying a Skill is Available To confirm a skill is properly loaded and available to the agent, you can ask the agent directly. Simply send a message like: @@ -291,10 +551,26 @@ If the agent confirms the skill is available, you're ready to use it. If not, ch ### Checking if a Skill Was Used +{% tabs %} +{% tab label="VSCode" %} + +When the agent uses a skill, it invokes the `skill` tool with the skill's name. Look for a `skill` tool call in the conversation to confirm a skill was loaded. The tool output includes the full skill content injected into context. + +{% /tab %} +{% tab label="CLI" %} + +When the agent uses a skill, it invokes the `skill` tool with the skill's name. Look for a `skill` tool call in the conversation to confirm a skill was loaded. The tool output includes the full skill content injected into context. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + To see if a skill was actually used during a conversation, look for a `read_file` tool call in the chat that targets a `SKILL.md` file. When the agent decides to use a skill, it reads the full skill file into context—this appears as a file read operation in the conversation. There's currently no dedicated UI indicator showing "Skill X was activated." The `read_file` call is the most reliable way to confirm a skill was used. +{% /tab %} +{% /tabs %} + ### Common Errors | Error | Cause | Solution | @@ -307,6 +583,24 @@ There's currently no dedicated UI indicator showing "Skill X was activated." The Have you created a skill that others might find useful? Share it with the community by contributing to the [Kilo Marketplace](https://github.com/Kilo-Org/kilo-marketplace)! +{% tabs %} +{% tab label="VSCode" %} + +While the new platform does not yet have a built-in marketplace UI, skills from the [Kilo Marketplace repository](https://github.com/Kilo-Org/kilo-marketplace) can be manually downloaded into your `.kilo/skills/` directory or loaded via `skills.urls` in config. + +{% /tab %} +{% tab label="CLI" %} + +While the new platform does not yet have a built-in marketplace UI, skills from the [Kilo Marketplace repository](https://github.com/Kilo-Org/kilo-marketplace) can be manually downloaded into your `.kilo/skills/` directory or loaded via `skills.urls` in config. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +Skills submitted to the marketplace are browsable and installable directly from the Marketplace tab in the **VSCode** version. + +{% /tab %} +{% /tabs %} + ### How to Submit Your Skill 1. **Prepare your skill**: Ensure your skill directory contains a valid `SKILL.md` file with proper frontmatter diff --git a/packages/kilo-docs/pages/customize/workflows.md b/packages/kilo-docs/pages/customize/workflows.md index c18a0030fb8..50ea192b3b6 100644 --- a/packages/kilo-docs/pages/customize/workflows.md +++ b/packages/kilo-docs/pages/customize/workflows.md @@ -1,16 +1,64 @@ --- title: "Workflows" description: "Create automated workflows with Kilo Code" +platform: new --- # Workflows -Workflows automate repetitive tasks by defining step-by-step instructions for Kilo Code to execute. Invoke any workflow by typing `/[workflow-name.md]` in the chat. +Workflows (also called **slash commands** in the new extension) automate repetitive tasks by defining step-by-step instructions for Kilo Code to execute. {% image src="/docs/img/slash-commands/workflows.png" alt="Workflows tab in Kilo Code" width="600" caption="Workflows tab in Kilo Code" /%} ## Creating Workflows +{% tabs %} +{% tab label="VSCode" %} + +Workflows are Markdown files stored as **slash commands** in `.kilo/commands/`: + +- **Global commands**: `~/.config/kilo/commands/` (available in all projects) +- **Project commands**: `[project]/.kilo/commands/` (project-specific) + +### Basic Setup + +1. Create a `.md` file with step-by-step instructions +2. Save it in your commands directory +3. Type `/command-name` in the chat (just the filename without `.md` extension) to execute + +For example, a file at `.kilo/commands/submit-pr.md` is invoked with `/submit-pr`. + +### Optional Frontmatter + +Command files can include YAML frontmatter: + +```markdown +--- +description: Submit a pull request with checks +agent: code +--- + +You are helping submit a pull request... +``` + +| Field | Description | +| ------------- | --------------------------------------------- | +| `description` | Shown in the command picker | +| `agent` | Which agent to use when invoking this command | +| `model` | Model override for this command | +| `subtask` | When `true`, runs as a sub-agent session | + +### Workflow Capabilities + +Workflows can leverage all built-in tools: `read`, `glob`, `grep`, `edit`, `write`, `bash`, `webfetch`, and MCP server tools. + +### Migration from Legacy Workflows + +The new extension automatically migrates legacy workflows from `.kilocode/workflows/` to the new command format on startup. You can also manually move files and remove the `.md` extension from invocations. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Workflows are markdown files stored in `.kilocode/workflows/`: - **Global workflows**: `~/.kilocode/workflows/` (available in all projects) @@ -29,7 +77,10 @@ Workflows can leverage: - [Built-in tools](/docs/automate/tools): [`read_file()`](/docs/automate/tools/read-file), [`search_files()`](/docs/automate/tools/search-files), [`execute_command()`](/docs/automate/tools/execute-command) - CLI tools: `gh`, `docker`, `npm`, custom scripts - [MCP integrations](/docs/automate/mcp/overview): Slack, databases, APIs -- [Mode switching](/docs/code-with-ai/agents/using-modes): [`new_task()`](/docs/automate/tools/new-task) for specialized contexts +- [Agent switching](/docs/code-with-ai/agents/using-agents): [`new_task()`](/docs/automate/tools/new-task) for specialized contexts + +{% /tab %} +{% /tabs %} ## Common Workflow Patterns @@ -64,7 +115,38 @@ Workflows can leverage: ## Example: PR Submission Workflow -Let's walk through creating a workflow for submitting a pull request. This workflow handles the entire process from code review to deployment notification. +Let's walk through creating a workflow for submitting a pull request. + +{% tabs %} +{% tab label="VSCode" %} + +Create a file called `submit-pr.md` in your `.kilo/commands` directory: + +```markdown +--- +description: Submit a pull request with full checks +--- + +# Submit PR Workflow + +You are helping submit a pull request. Follow these steps: + +1. First, use `grep` to check for any TODO comments or console.log statements that shouldn't be committed +2. Run tests using `bash` with `npm test` or the appropriate test command +3. If tests pass, stage and commit changes with a descriptive commit message +4. Push the branch and create a pull request using `bash` with `gh pr create` +5. Use `question` to get the PR title and description from the user + +Parameters needed (ask if not provided): + +- Branch name +- Reviewers to assign +``` + +Trigger this workflow by typing `/submit-pr` in the chat. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} Create a file called `submit-pr.md` in your `.kilocode/workflows` directory: @@ -85,12 +167,16 @@ Parameters needed (ask if not provided): - Reviewers to assign ``` -Now you can trigger this workflow by typing `/submit-pr.md` in the chat. Kilo Code will: +Trigger this workflow by typing `/submit-pr.md` in the chat. + +{% /tab %} +{% /tabs %} + +Kilo Code will: - Scan your code for common issues before committing - Run your test suite to catch problems early - Handle the Git operations and PR creation -- Notify your team automatically - Set up follow-up tasks for deployment -This saves you from manually running the same 7-step process every time you want to submit code for review. +This saves you from manually running the same steps every time you want to submit code for review. diff --git a/packages/kilo-docs/pages/getting-started/byok.md b/packages/kilo-docs/pages/getting-started/byok.md index 3df9aff13e1..24ed48bf863 100644 --- a/packages/kilo-docs/pages/getting-started/byok.md +++ b/packages/kilo-docs/pages/getting-started/byok.md @@ -20,10 +20,11 @@ Kilo Gateway currently supports BYOK keys for these providers: - Anthropic - AWS Bedrock -- OpenAI - Google AI Studio +- Inception - Minimax - Mistral AI +- OpenAI - xAI - Z.AI diff --git a/packages/kilo-docs/pages/getting-started/installing.md b/packages/kilo-docs/pages/getting-started/installing.md index f3693d1b7c6..e63c864adb5 100644 --- a/packages/kilo-docs/pages/getting-started/installing.md +++ b/packages/kilo-docs/pages/getting-started/installing.md @@ -14,54 +14,16 @@ Get started with Kilo Code by installing it on your preferred platform. Choose y ## VS Code Extension -{% partial file="install-vscode.md" /%} - -{% /tab %} -{% tab label="VS Code (Preview)" %} - -## VS Code Preview Extension - -{% callout type="info" %} -We're rebuilding Kilo Code from the ground up on the new [Kilo CLI](https://github.com/Kilo-Org/kilocode). The pre-release extension is available for users who want to try the latest architecture and provide feedback, and don't mind some missing features and rough edges. -{% /callout %} - -The pre-release extension is a complete rebuild featuring: - -- A new Solid.js-based UI -- Deep integration with the Kilo CLI backend -- Improved session management and model switching - -### Current Status - -This is an early pre-release. Core features like chat, markdown rendering, authentication, and model/mode switching are working. Some features from the stable extension are still being implemented. - -For the full feature status, see the [feature parity tracking document](https://github.com/Kilo-Org/kilocode/blob/main/packages/kilo-vscode/docs/opencode-migration-plan.md). - -### Installing the Pre-Release +The current Kilo Code extension is built on the [Kilo CLI](https://github.com/Kilo-Org/kilocode) and is distributed as the **pre-release version** on the VS Code Marketplace. 1. Open VS Code 2. Go to Extensions (`Ctrl+Shift+X` / `Cmd+Shift+X`) 3. Search for "Kilo Code" 4. Click the dropdown arrow next to **Install** and select **Install Pre-Release Version** -### Switching Back to Stable - -If you need to return to the stable version: - -1. Open Extensions in VS Code -2. Find Kilo Code -3. Click the dropdown and select **Switch to Release Version** - -### Feedback and Issues - -Report issues or provide feedback in the [Kilo-Org/kilocode repository](https://github.com/Kilo-Org/kilocode/issues). - -{% /tab %} -{% tab label="JetBrains" %} - -## JetBrains IDEs - -{% partial file="install-jetbrains.md" /%} +{% callout type="info" %} +The "pre-release" label is a VS Code Marketplace distribution channel — the extension is stable and recommended for all users. +{% /callout %} {% /tab %} {% tab label="CLI" %} @@ -70,6 +32,27 @@ Report issues or provide feedback in the [Kilo-Org/kilocode repository](https:// {% partial file="install-cli.md" /%} +{% /tab %} +{% tab label="VS Code (Legacy)" %} + +## VS Code Legacy Extension + +The legacy extension is the previous version of Kilo Code for VS Code. It is still available but is no longer actively developed. We recommend installing the current extension (see the **VS Code** tab). + +To install or switch back to the legacy version: + +1. Open VS Code +2. Go to Extensions (`Ctrl+Shift+X` / `Cmd+Shift+X`) +3. Search for "Kilo Code" +4. Click the dropdown arrow next to **Install** and select **Switch to Release Version** + +{% /tab %} +{% tab label="JetBrains" %} + +## JetBrains IDEs + +{% partial file="install-jetbrains.md" /%} + {% /tab %} {% tab label="Slack" %} diff --git a/packages/kilo-docs/pages/getting-started/quickstart.md b/packages/kilo-docs/pages/getting-started/quickstart.md index 3258a77a4d8..03e3206ab1c 100644 --- a/packages/kilo-docs/pages/getting-started/quickstart.md +++ b/packages/kilo-docs/pages/getting-started/quickstart.md @@ -5,12 +5,112 @@ description: "Get up and running with Kilo Code in minutes" # Quickstart +After you [set up Kilo Code](/docs/getting-started/setup-authentication), follow the guide for your platform below. + +{% tabs %} +{% tab label="VSCode" %} + +## Step by Step Guide + +### Step 1: Open Kilo Code + +Click the Kilo Code icon in the VS Code Primary Side Bar to open the chat panel. If you don't see the icon, verify the [extension is installed](/docs/getting-started/installing). + +### Step 2: Type Your Task + +Type a clear, concise description of what you want Kilo Code to do in the chat box. The same examples work here: + +- "Create a file named `hello.txt` containing 'Hello, world!'." +- "Write a Python function that adds two numbers." +- "Create an HTML file for a simple website with the title 'Kilo test'" + +No special commands or syntax needed—just use plain English. + +### Step 3: Send Your Task + +Press **Enter** to send. + +### Step 4: Review & Approve Actions + +Kilo Code analyzes your request and proposes actions. By default, most tools are auto-approved — only shell commands, external directory access, and sensitive file reads will prompt for confirmation. You'll see the tool name, arguments, and can approve or reject each action. + +To change which actions require approval, open **Settings** (gear icon) and go to the **Auto-Approve** tab. You can set each tool to Allow, Ask, or Deny. See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions) for details. + +### Step 5: Iterate + +Kilo Code works iteratively. Continue giving feedback or follow-up instructions until your task is complete. + +### Key Differences from Legacy + +- **Settings** are managed via `kilo.jsonc` config files (the Settings webview reads and writes the same files) +- **Permissions** use a granular per-tool system instead of broad approval categories +- **Modes** are called "agents" and configured as `.md` files or via the `agent` config key +- **Autocomplete** uses FIM (Fill-in-the-Middle) with Codestral + +{% /tab %} +{% tab label="CLI" %} + +## CLI Quickstart + +### Step 1: Open a Terminal + +Navigate to your project directory: + +```bash +cd /path/to/your/project +``` + +### Step 2: Launch Kilo + +Run the `kilo` command to start the interactive TUI (terminal user interface): + +```bash +kilo +``` + +If this is your first time, run `kilo auth login` first to authenticate (see [Setup & Authentication](/docs/getting-started/setup-authentication)). + +### Step 3: Type Your Task + +Type your request in natural language at the prompt. The same examples work here: + +- "Create a file named `hello.txt` containing 'Hello, world!'." +- "Write a Python function that adds two numbers." +- "Create an HTML file for a simple website with the title 'Kilo test'" + +Press **Enter** to send. + +### Step 4: Review & Approve Actions + +Kilo analyzes your request and proposes actions. By default, most tools are auto-approved — only shell commands, external directory access, and sensitive file reads will prompt for confirmation. You'll see the tool name, arguments, and can approve or reject each action. + +To change permission defaults, configure the `permission` key in your `kilo.jsonc` config file. See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions) for details. + +### Step 5: Iterate + +Kilo works iteratively. Continue giving feedback or follow-up instructions until your task is complete. + +### One-Shot Mode + +For quick, non-interactive tasks, use `kilo run`: + +```bash +kilo run "add error handling to src/api.ts" +``` + +Add `--auto` to auto-approve all permissions (use carefully): + +```bash +kilo run --auto "fix the failing tests in test/auth.test.ts" +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + ## Video Tour {% youtube url="https://www.youtube.com/watch?v=pO7zRLQS-p0" caption="This quick tour shows how Kilo Code handles a simple request from start to finish" /%} -After you [set up Kilo Code](/docs/getting-started/setup-authentication), follow these steps: - ## Step by Step Guide ### Step 1: Open Kilo Code @@ -57,7 +157,7 @@ Kilo Code analyzes your request and proposes specific actions. These may include {% callout type="tip" %} The level of autonomy is configurable, allowing you to make the agent more or less autonomous. -You can learn more about [using modes](/docs/code-with-ai/agents/using-modes) and [auto-approving actions](/docs/getting-started/settings/auto-approving-actions). +You can learn more about [using agents](/docs/code-with-ai/agents/using-agents) and [auto-approving actions](/docs/getting-started/settings/auto-approving-actions). {% /callout %} ### Step 5: Iterate @@ -66,6 +166,9 @@ Kilo Code works iteratively. After each action, it waits for your feedback befor {% image src="/docs/img/your-first-task/your-first-task-8.png" alt="Final result of a completed task showing the iteration process" width="500" caption="After completing the task, Kilo Code shows the final result and awaits your next instruction." /%} +{% /tab %} +{% /tabs %} + ## Conclusion You've completed your first task. Along the way you learned: @@ -77,7 +180,7 @@ You've completed your first task. Along the way you learned: Ready for more? Here are some next steps: - **[Autocomplete](/docs/code-with-ai/features/autocomplete)** — Get inline code suggestions as you type -- **[Modes](/docs/code-with-ai/agents/using-modes)** — Explore different modes for different tasks +- **[Agents](/docs/code-with-ai/agents/using-agents)** — Explore different agents for different tasks - **[Git commit generation](/docs/code-with-ai/features/git-commit-generation)** — Automatically generate commit messages {% callout type="tip" %} diff --git a/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md b/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md index c071327ebc1..10e4a9d0044 100644 --- a/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md +++ b/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md @@ -9,7 +9,243 @@ description: "Configure automatic approval settings for Kilo Code operations" **Security Warning:** Auto-approve settings bypass confirmation prompts, giving Kilo Code direct access to your system. This can result in data loss, file corruption, or worse. Command line access is particularly dangerous, as it can potentially execute harmful operations that could damage your system or compromise security. Only enable auto-approval for actions you fully trust. {% /callout %} -Auto-approve settings speed up your workflow by eliminating repetitive confirmation prompts, but they significantly increase security risks. +Auto-approve settings speed up your workflow by eliminating repetitive confirmation prompts, but they significantly increase security risks. The **VSCode (Legacy)**, **VSCode**, and **CLI** versions each handle permissions differently — choose the tab that matches your setup. + +{% tabs %} +{% tab label="VSCode" %} + +## Overview + +The extension uses a granular, per-tool permission system. You can configure permissions through the **Settings → Auto Approve** tab, which provides a UI with per-tool **Allow / Ask / Deny** dropdowns. + +The UI reads and writes to the same `kilo.jsonc` config files used by the CLI, so changes made in either place are reflected in both. + +## Permission Levels + +Each tool permission can be set to one of three values: + +| Value | Behavior | +| --------- | --------------------------------------------------------- | +| `"allow"` | The tool runs automatically without prompting | +| `"ask"` | Kilo pauses and asks for approval before running the tool | +| `"deny"` | The tool is blocked entirely | + +When no rule matches a permission check, the default action is `ask`. + +## Available Tool Permissions + +The Auto Approve tab lists the following tool-specific permissions. Some tools are grouped together in the UI and share a single permission level: + +| Permission | Controls | +| -------------------------- | ------------------------------------------------------ | +| `external_directory` | Accessing files outside the project directory | +| `bash` | Executing shell commands | +| `read` | Reading file contents | +| `edit` | Editing existing files | +| `glob` | File pattern matching / searching by name | +| `grep` | Searching file contents by regex | +| `list` | Listing directory contents | +| `task` | Launching sub-agents | +| `skill` | Loading specialized skills | +| `lsp` | Language server protocol operations | +| `todoread` / `todowrite` | Reading and updating the todo list | +| `websearch` / `codesearch` | Performing web or code searches | +| `webfetch` | Fetching content from URLs | +| `doom_loop` | Allowing the agent to continue after repeated failures | + +## Runtime Permission Requests + +When a tool is set to `"ask"`, Kilo pauses and displays a permission prompt with two options: + +| Option | Behavior | +| -------- | ------------------------------ | +| **Run** | Allow this specific invocation | +| **Deny** | Block this specific invocation | + +Expand **Manage Auto-Approve Rules** to add commands or patterns to your allowed or denied lists. These rules are then appended to the bottom of the approval rules in settings and the config file. + +## Defaults + +Most tools default to `"*": "allow"` for a smooth out-of-the-box experience. Notable exceptions that prompt by default: + +- **`.env` files** — reading `.env` files prompts for approval. Files matching `*.env.*` (e.g., `.env.local`, `.env.production`) also trigger an ask, while `*.env.example` is explicitly allowed. +- **`external_directory`** — accessing files outside the project prompts for approval +- **`doom_loop`** — prompts when the agent enters a repeated failure cycle + +{% /tab %} +{% tab label="CLI" %} + +## Overview + +The CLI uses a granular, per-tool permission system configured in `kilo.jsonc`. Instead of broad categories like "read" or "write," each tool has its own permission level with glob-pattern rules for fine-grained control. + +## Permission Levels + +Each tool permission can be set to one of three values: + +| Value | Behavior | +| --------- | --------------------------------------------------------- | +| `"allow"` | The tool runs automatically without prompting | +| `"ask"` | Kilo pauses and asks for approval before running the tool | +| `"deny"` | The tool is blocked entirely | + +When no rule matches a permission check, the default action is `ask`. + +## Available Tool Permissions + +Permissions are configured under the `permission` key in `kilo.jsonc`. The following tool-specific permission levels are available: + +| Permission | Controls | +| -------------------------- | ------------------------------------------------------ | +| `external_directory` | Accessing files outside the project directory | +| `bash` | Executing shell commands | +| `read` | Reading file contents | +| `edit` | Editing existing files | +| `glob` | File pattern matching / searching by name | +| `grep` | Searching file contents by regex | +| `list` | Listing directory contents | +| `task` | Launching sub-agents | +| `skill` | Loading specialized skills | +| `lsp` | Language server protocol operations | +| `todoread` / `todowrite` | Reading and updating the todo list | +| `websearch` / `codesearch` | Performing web or code searches | +| `webfetch` | Fetching content from URLs | +| `doom_loop` | Allowing the agent to continue after repeated failures | + +## Glob-Pattern Rules + +Instead of a simple `"allow"` or `"deny"`, each tool can use glob-pattern rules for granular control. Patterns are matched against the tool's arguments (command strings, file paths, etc.), and the last matching rule wins. + +### Example: Shell Commands + +Allow git commands automatically, but prompt for everything else: + +```json +{ + "permission": { + "bash": { + "git *": "allow", + "*": "ask" + } + } +} +``` + +### Example: File Reading + +Prompt before reading `.env` files, but allow all other reads: + +```json +{ + "permission": { + "read": { + "*.env": "ask", + "*": "allow" + } + } +} +``` + +### Example: Blocking Dangerous Commands + +Deny `rm -rf` commands, allow common dev commands, and ask for anything else: + +```json +{ + "permission": { + "bash": { + "rm -rf *": "deny", + "npm *": "allow", + "bun *": "allow", + "git *": "allow", + "*": "ask" + } + } +} +``` + +## Per-Agent Permission Overrides + +Different agents can have different permission levels. Override the default permissions for a specific agent under the `agent..permission` key: + +```json +{ + "permission": { + "bash": { "*": "ask" } + }, + "agent": { + "code": { + "permission": { + "bash": { "git *": "allow", "*": "ask" } + } + }, + "plan": { + "permission": { + "bash": { "*": "deny" } + } + } + } +} +``` + +In this example, the `code` agent can run `git` commands automatically and asks for other shell commands, while the `plan` agent cannot run shell commands at all. + +## Runtime Permission Requests + +When a tool is set to `"ask"`, Kilo pauses and displays a permission prompt. You have three options: + +| Option | Behavior | +| ---------------- | -------------------------------------------------------- | +| **Allow once** | Allow this specific invocation only | +| **Allow always** | Allow this tool (or pattern) for the rest of the session | +| **Reject** | Block this specific invocation | + +## Defaults + +Most tools default to `"*": "allow"` for a smooth out-of-the-box experience. Notable exceptions that prompt by default: + +- **`.env` files** — reading `.env` files prompts for approval. Files matching `*.env.*` (e.g., `.env.local`, `.env.production`) also trigger an ask, while `*.env.example` is explicitly allowed. +- **`external_directory`** — accessing files outside the project prompts for approval +- **`doom_loop`** — prompts when the agent enters a repeated failure cycle + +## Full Configuration Example + +{% callout type="info" %} +This is a custom example showing the available configuration options — it does not represent the shipped defaults. +{% /callout %} + +```json +{ + "permission": { + "read": { "*.env": "ask", "*": "allow" }, + "edit": { "*.env": "ask", "*": "allow" }, + "glob": { "*": "allow" }, + "grep": { "*": "allow" }, + "list": { "*": "allow" }, + "bash": { "git *": "allow", "npm *": "allow", "*": "ask" }, + "task": { "*": "allow" }, + "skill": { "*": "allow" }, + "lsp": { "*": "allow" }, + "todoread": { "*": "allow" }, + "todowrite": { "*": "allow" }, + "webfetch": { "*": "allow" }, + "websearch": { "*": "allow" }, + "codesearch": { "*": "allow" }, + "external_directory": { "*": "ask" }, + "doom_loop": { "*": "ask" } + }, + "agent": { + "code": { + "permission": { + "bash": { "git *": "allow", "npm *": "allow", "*": "ask" } + } + } + } +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} ## Quick Start Guide @@ -343,3 +579,6 @@ You can optionally enable an AI Safety Gatekeeper, which reviews every intended This is the fastest way to work with Kilo Code, but also the riskiest. Use it only when you fully trust the AI and are prepared for the consequences. {% /callout %} + +{% /tab %} +{% /tabs %} diff --git a/packages/kilo-docs/pages/getting-started/settings/auto-cleanup.md b/packages/kilo-docs/pages/getting-started/settings/auto-cleanup.md index 2dabb1c2c5e..c011f2c937e 100644 --- a/packages/kilo-docs/pages/getting-started/settings/auto-cleanup.md +++ b/packages/kilo-docs/pages/getting-started/settings/auto-cleanup.md @@ -1,3 +1,7 @@ +--- +platform: legacy +--- + # Auto Cleanup Auto Cleanup automatically manages your task history by removing old tasks to free up disk space and improve performance. Tasks are intelligently classified and retained based on their type and age, ensuring important work is preserved while temporary or experimental tasks are cleaned up. diff --git a/packages/kilo-docs/pages/getting-started/settings/index.md b/packages/kilo-docs/pages/getting-started/settings/index.md index 294831ca62e..acd1bd43ff6 100644 --- a/packages/kilo-docs/pages/getting-started/settings/index.md +++ b/packages/kilo-docs/pages/getting-started/settings/index.md @@ -5,13 +5,88 @@ description: "Configure Kilo Code settings and preferences" # Settings +The VS Code extension can be configured through the Settings window, opened by pressing the gear icon. Both the CLI and the extension can also be configured through interactions with the agent. The current VS Code extension and CLI share the same underlying settings, so changes in one are reflected in the other. + +## Managing Settings + +{% tabs %} +{% tab label="VSCode" %} + +The VS Code extension provides a **Settings webview UI** accessible from the extension sidebar by clicking the gear icon ({% codicon name="gear" /%}). The UI is organized into tabs including Providers, Auto-Approve, Models, and more. + +This UI reads and writes to the same underlying JSONC config files used by the CLI, so changes made in either place are reflected in both. + +### Config File Locations + +There are two primary config files: + +- **Global config:** `~/.config/kilo/kilo.jsonc` — applies to all projects. On Windows, this is `C:\Users\\.config\kilo\kilo.jsonc`. +- **Project config:** `kilo.jsonc` in your project root, or `.kilo/kilo.jsonc` for a cleaner setup. The `.kilo/` version takes priority if both exist. + +{% callout type="warning" %} +If you check config files into version control, make sure they do not contain API keys or other secrets (e.g., `provider.*.options.apiKey`). Use environment variables for credentials instead. +{% /callout %} + +### Export and Import + +Config files are plain-text and portable — copy them between machines and you're done. + +{% /tab %} +{% tab label="CLI" %} + +In the CLI, settings are managed via **JSONC config files** directly. Config files are plain-text and portable -- you can copy them between machines. + +{% callout type="warning" %} +If you check `kilo.jsonc` into version control, make sure it does not contain API keys or other secrets (e.g., `provider.*.options.apiKey`). Use environment variables for credentials instead. +{% /callout %} + +### Config File Locations + +There are two primary config files: + +- **Global config:** `~/.config/kilo/kilo.jsonc` -- applies to all projects. On Windows, this is `C:\Users\\.config\kilo\kilo.jsonc`. +- **Project config:** `kilo.jsonc` in the root of your project -- overrides global settings for that project. + +Both files use the [JSONC](https://code.visualstudio.com/docs/languages/json#_json-with-comments) format (JSON with comments). + +### Config File Precedence + +Settings are resolved through an 8-level precedence system (lowest to highest priority): + +1. **Legacy Kilocode** -- migrated settings from the VSCode extension +2. **Remote well-known** -- remotely fetched defaults +3. **Global** -- `~/.config/kilo/kilo.jsonc` +4. **Custom** -- additional custom config paths +5. **Project** -- `kilo.jsonc` in the project root +6. **`.kilo` directory** -- config from a `.kilo/` directory in the project +7. **Inline environment** -- environment variable overrides +8. **Managed / Enterprise** -- enterprise-managed configuration (highest priority) + +Higher-priority levels override lower ones. This allows organizations to enforce settings at the enterprise level while still letting individual developers customize their local environment. + +### Schema Auto-Injection + +When you create or open a `kilo.jsonc` file, the CLI automatically injects a `$schema` property pointing to the config JSON schema. This gives you **autocompletion and validation** in any editor that supports JSON Schema (VS Code, JetBrains, etc.). + +### Export and Import + +There is no traditional export/import of settings -- the JSONC config files themselves are portable. Copy `~/.config/kilo/kilo.jsonc` or `kilo.jsonc` to another machine and you're done. + +For **session** export and import, use the CLI commands: + +- `kilo export` -- export session data +- `kilo import` -- import session data + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + Kilo Code allows you to manage your configuration settings effectively through export, import, and reset options. These features are useful for backing up your setup, sharing configurations with others, or restoring default settings if needed. You can find these options at the bottom of the Kilo Code settings page, accessible via the gear icon ({% codicon name="gear" /%}) in the Kilo Code chat view. {% image src="/docs/img/settings-management/settings-management.png" alt="Export, Import, and Reset buttons in Kilo Code settings" width="800" caption="Export, Import, and Reset buttons" /%} -## Export Settings +### Export Settings Clicking the **Export** button saves your current Kilo Code settings to a JSON file. @@ -24,7 +99,7 @@ Clicking the **Export** button saves your current Kilo Code settings to a JSON f This creates a backup of your configuration or a file you can share. -## Import Settings +### Import Settings Clicking the **Import** button allows you to load settings from a previously exported JSON file. @@ -35,7 +110,7 @@ Clicking the **Import** button allows you to load settings from a previously exp - **Merging:** Importing settings **merges** the configurations. It adds new API profiles and updates existing ones and global settings based on the file content. It does **not** delete configurations present in your current setup but missing from the imported file. - **Validation:** Only valid settings matching the internal schema can be imported, preventing configuration errors. A success notification appears upon completion. -## Reset Settings +### Reset Settings Clicking the **Reset** button completely clears all Kilo Code configuration data and returns the extension to its default state. This is a destructive action intended for troubleshooting or starting fresh. @@ -57,8 +132,46 @@ Clicking the **Reset** button completely clears all Kilo Code configuration data Use this option only if you are certain you want to remove all Kilo Code data or if instructed during troubleshooting. Consider exporting your settings first if you might want to restore them later. +{% /tab %} +{% /tabs %} + ## Experimental Features +{% tabs %} +{% tab label="VSCode" %} + +The new extension exposes experimental features via the **Experimental** tab in Settings (click the gear icon {% codicon name="gear" /%} → Experimental). + +Available experimental toggles include: + +- **Share mode** — `manual`, `auto`, or `disabled` session sharing +- **LSP integration** — expose language server diagnostics to the agent +- **Paste summary** — summarize large clipboard pastes before including them +- **Batch tool** — allow the agent to batch multiple tool calls in one step + +Advanced options not exposed in the UI can be configured via the `experimental` key in `kilo.jsonc`: + +```json +{ + "experimental": { + "codebase_search": true, + "batch_tool": false, + "disable_paste_summary": false, + "mcp_timeout": 30000 + } +} +``` + +Refer to the auto-generated `$schema` in your `kilo.jsonc` for the full list of available options. + +{% /tab %} +{% tab label="CLI" %} + +The CLI does not currently expose the same experimental feature toggles as the **VSCode (Legacy)** version. Configuration of model behavior, file editing strategies, and other advanced options is handled directly in the JSONC config files. Refer to the auto-generated `$schema` in your `kilo.jsonc` for the full list of available options. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + {% callout type="info" %} These features are experimental and may change in future releases. They provide advanced control over Kilo Code's behavior for specific use cases. {% /callout %} @@ -106,3 +219,6 @@ This setting controls the number of lines read from a file in one batch. To mana **Default:** Set in Advanced Settings You can find this setting in the Kilo Code settings under 'Advanced Settings'. + +{% /tab %} +{% /tabs %} diff --git a/packages/kilo-docs/pages/getting-started/settings/system-notifications.md b/packages/kilo-docs/pages/getting-started/settings/system-notifications.md index 0593577a0f6..477f88043d6 100644 --- a/packages/kilo-docs/pages/getting-started/settings/system-notifications.md +++ b/packages/kilo-docs/pages/getting-started/settings/system-notifications.md @@ -1,6 +1,7 @@ --- title: "System Notifications" description: "Configure native OS notifications for Kilo Code" +platform: legacy --- # System Notifications diff --git a/packages/kilo-docs/pages/getting-started/setup-authentication.md b/packages/kilo-docs/pages/getting-started/setup-authentication.md index 8697970b0bd..d6390faa5f8 100644 --- a/packages/kilo-docs/pages/getting-started/setup-authentication.md +++ b/packages/kilo-docs/pages/getting-started/setup-authentication.md @@ -9,6 +9,31 @@ When you install Kilo Code, you'll be prompted to sign in or create a free accou ## Quick Start with Kilo Account +{% tabs %} +{% tab label="VSCode" %} + +The extension prompts you to sign in when you first open the sidebar. Click **Sign In** and complete the browser-based flow. The extension communicates with the CLI backend, so authentication is shared between the CLI and extension. + +{% /tab %} +{% tab label="CLI" %} + +Run the auth command and follow the browser-based sign-in flow: + +```bash +kilo auth login +``` + +This may open your browser to complete authentication. Once signed in, your credentials are stored locally and used for all future sessions. + +To verify your auth status: + +```bash +kilo auth list +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + 1. Click **"Try Kilo Code for Free"** in the extension 2. Sign in with your Google account 3. Allow VS Code to open the authorization URL @@ -17,6 +42,9 @@ When you install Kilo Code, you'll be prompted to sign in or create a free accou That's it! You're ready to [start your first task](/docs/getting-started/quickstart). +{% /tab %} +{% /tabs %} + {% callout type="tip" title="Bonus Credits" %} [Add credits to your account](https://app.kilo.ai/profile) and get $20 bonus credits, or sign up for [Kilo Pass](https://kilo.ai/features/kilo-pass). {% /callout %} @@ -75,12 +103,48 @@ Already have a ChatGPT subscription? You can use it with Kilo Code through the [ ### Configuring Your Provider +{% tabs %} +{% tab label="VSCode" %} + +1. Open the Kilo Code sidebar in VS Code +2. Click the gear icon ({% codicon name="gear" /%}) to open **Settings** +3. Go to the **Providers** tab +4. Select your provider and enter your API key +5. Choose your model + +You can also use `kilo auth login` for providers that support OAuth (like GitHub Copilot). The extension reads from the same underlying config files as the CLI, so provider settings are shared. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable: + +```bash +export ANTHROPIC_API_KEY="sk-ant-..." +``` + +Or use `kilo auth login` for providers that support OAuth (like GitHub Copilot). + +To set a default model: + +```jsonc +{ + "model": "anthropic/claude-sonnet-4-20250514", +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + 1. Click the {% kilo-code-icon /%} icon in the VS Code sidebar 2. Select your API provider from the dropdown 3. Paste your API key -4. Choose your model: +4. Choose your model 5. Click **"Let's go!"** +{% /tab %} +{% /tabs %} + {% callout type="info" title="Need Help?" %} Reach out to our [support team](mailto:hi@kilo.ai) or join our [Discord community](https://kilo.ai/discord). {% /callout %} diff --git a/packages/kilo-docs/pages/kiloclaw/chat-platforms.md b/packages/kilo-docs/pages/kiloclaw/chat-platforms.md deleted file mode 100644 index 6767956eb36..00000000000 --- a/packages/kilo-docs/pages/kiloclaw/chat-platforms.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: "Connecting Chat Platforms" -description: "Connect your KiloClaw agent to Telegram, Discord, Slack, and more" ---- - -# Connecting Chat Platforms - -KiloClaw supports connecting your AI agent to Telegram, Discord, and Slack. You can configure channels from the **Settings** tab on your [KiloClaw dashboard](/docs/kiloclaw/dashboard#channels), or from the OpenClaw Control UI after accessing your instance. - -While the exact steps vary for configuring a chat platform (called a _channel_ by OpenClaw), the steps are to: - -1. Configure the channel -2. Redeploy the KiloClaw instance -3. Initiate the pairing in the chat app -4. Accept the pairing request in the [KiloClaw UI](https://app.kilo.ai/claw) - -Detailed instructions for supported chat apps are below. - -## Chat Apps (Channels) - -### Telegram - -1. Open Telegram and search for [@BotFather](https://t.me/BotFather) -2. Send `/newbot` and follow the prompts to create your bot -3. Copy the **Bot Token** that BotFather gives you -4. Go to the **Settings** tab on your [KiloClaw dashboard](/docs/kiloclaw/dashboard) -5. Paste the token into the **Telegram Bot Token** field -6. Click **Save** -7. Redeploy your KiloClaw instance -8. Send a direct message to your bot in Telegram: `/start` - -{% image src="/docs/img/kiloclaw/telegram.png" alt="Connect account screen" width="800" caption="Telegram bot token entry" /%} - -You can remove or replace a configured token at any time. - -> ℹ️ **Info** -> Advanced settings such as DM policy, allow lists, and groups can be configured in the OpenClaw Control UI after connecting. - -### Discord - -To connect Discord, you need a **Bot Token** from the [Discord Developer Portal](https://discord.com/developers/applications). - -#### Create an Application and Bot - -1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and log in -2. Click **New Application**, give it a name, and click **Create** -3. Click **Bot** on the left sidebar -4. Click **Add Bot** and confirm - -#### Enable Privileged Intents - -On the **Bot** page, scroll down to **Privileged Gateway Intents** and enable: - -- **Message Content Intent** (required) -- **Server Members Intent** (recommended — needed for role allowlists and name matching) -- **Presence Intent** (optional) - -#### Copy Your Bot Token - -1. Scroll back up on the **Bot** page and click **Reset Token** - -> 📝 **Note** -> Despite the name, this generates your first token — nothing is being "reset." - -2. Copy the token that appears and paste it into the **Discord Bot Token** field in your KiloClaw dashboard. - -{% image src="/docs/img/kiloclaw/discord.png" alt="Connect account screen" width="800" caption="Discord bot token entry" /%} - -Enter the token in the Settings tab and click **Save**. You can remove or replace a configured token at any time. - -#### Generate an Invite URL and Add the Bot to Your Server - -1. Click **OAuth2** on the sidebar -2. Scroll down to **OAuth2 URL Generator** and enable: - - `bot` - - `applications.commands` -3. A **Bot Permissions** section will appear below. Enable: - - View Channels - - Send Messages - - Read Message History - - Embed Links - - Attach Files - - Add Reactions (optional) -4. Copy the generated URL at the bottom -5. Paste it into your browser, select your server, and click **Continue** -6. You should now see your bot in the Discord server - -#### Start Chatting with the Bot - -1. Right-click on the Bot in Discord and click **Message** -2. DM the bot `/pair` -3. You should get a response back with a pairing code -4. Return to [app.kilocode.ai/claw](https://app.kilocode.ai/claw) and confirm the pairing code and approve -5. You should now be able to chat with the bot from Discord - -### Slack - -#### Step 1: Create a Slack App from the OpenClaw Manifest - -1. Go to [Slack App Management](https://api.slack.com/apps) and click **Create New App** → **From a Manifest** -2. Copy the manifest from the [OpenClaw docs](https://docs.openclaw.ai/channels/slack#manifest-and-scope-checklist) -3. Paste the manifest JSON into Slack's manifest editor -4. Customize the manifest before creating: - - Rename the app to your preferred name wherever it appears - - Update the slash command if desired (e.g., `/kiloclaw`) -5. Click **Create** - -#### Step 2: Generate Tokens - -You need two tokens from Slack: - -**App-Level Token** - -1. In your Slack app settings, scroll down to **App-Level Tokens** -2. Click **Generate Token** -3. Add the `connections:write` scope -4. Generate and copy the token (starts with `xapp-`) - -**Bot User OAuth Token** - -1. In the left sidebar, click **Install App** -2. Install the app to your workspace -3. Copy the **Bot User OAuth Token** (starts with `xoxb-`) - -#### Step 3: Connect Slack to KiloClaw - -1. In the [KiloClaw UI](https://app.kilo.ai/claw), find the Slack integration section (may show "not configured") -2. Enter both tokens: - - The `xapp-` app-level token - - The `xoxb-` bot user OAuth token -3. Click **Save** -4. Scroll to the top of the KiloClaw UI and click **Redeploy**. Wait for the instance to come back up - -#### Step 4: Pair Slack with KiloClaw - -1. In Slack, DM the app and type your slash command (e.g., `/claw`) followed by anything — this triggers the pairing flow - -> 📝 **Note** -> The slash command is whatever you defined in the manifest. Any text after the command will work to trigger pairing. - -2. The app will return a pairing code -3. Return to [app.kilocode.ai/claw](https://app.kilocode.ai/claw) and confirm the pairing code and approve -4. You should now be able to chat with the bot from Slack - -## Future Support - -Additional platforms (such as WhatsApp) are planned for future releases. For the latest on supported platforms, refer to the [OpenClaw documentation](https://docs.openclaw.ai). - -## Related - -- [KiloClaw Overview](/docs/kiloclaw/overview) -- [Dashboard Reference](/docs/kiloclaw/dashboard) -- [Troubleshooting](/docs/kiloclaw/troubleshooting) -- [KiloClaw Pricing](/docs/kiloclaw/pricing) -- [OpenClaw Documentation](https://docs.openclaw.ai) diff --git a/packages/kilo-docs/pages/kiloclaw/chat-platforms/discord.md b/packages/kilo-docs/pages/kiloclaw/chat-platforms/discord.md new file mode 100644 index 00000000000..219e6799c68 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/chat-platforms/discord.md @@ -0,0 +1,67 @@ +--- +title: "Discord" +description: "Connect your KiloClaw agent to Discord" +--- + +# Discord + +Connect your KiloClaw agent to Discord by creating a bot in the Discord Developer Portal and linking it to your KiloClaw dashboard. + +## Prerequisites + +Make sure you have a Discord server ready to add the bot to. If you don't have one, open Discord, scroll to the bottom of your server list, click **+**, choose **Create My Own**, then **For me and my friends**, and give it a name. + +## Create an Application and Bot + +1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and log in +2. Click **New Application**, give it a name, and click **Create** + +## Enable Privileged Intents + +On the **Bot** page, scroll down to **Privileged Gateway Intents** and enable: + +- **Message Content Intent** (required) +- **Server Members Intent** (recommended — needed for role allowlists and name matching) +- **Presence Intent** (optional) + +## Generate an Invite URL and Add the Bot to Your Server + +1. Click **OAuth2** on the sidebar +2. Scroll down to **OAuth2 URL Generator** and enable: + - `bot` + - `applications.commands` +3. A **Bot Permissions** section will appear below. Enable: + - View Channels + - Send Messages + - Read Message History + - Embed Links + - Attach Files + - Add Reactions (optional) +4. Copy the generated URL at the bottom +5. Paste it into your browser, select your server, and click **Continue** +6. You should now see your bot in the Discord server + +## Copy Your Bot Token + +1. Go back to the **Bot** page on the left sidebar and click **Reset Token** + +> 📝 **Note** +> Despite the name, this generates your first token — nothing is being "reset." + +2. Copy the token that appears and paste it into the **Discord Bot Token** field in your KiloClaw dashboard. + +{% image src="/docs/img/kiloclaw/discord.png" alt="Connect account screen" width="800" caption="Discord bot token entry" /%} + +Enter the token in the Settings tab and click **Save**. You can remove or replace a configured token at any time. + +## Redeploy to Apply Changes + +After saving your token, click **Redeploy** (the yellow button at the top of the KiloClaw dashboard) to apply the changes. The server will restart in about 30–45 seconds. Wait for the redeploy to complete before pairing. + +## Start Chatting with the Bot + +1. Right-click on the Bot in Discord and click **Message** +2. DM the bot `/pair` +3. You should get a response back with a pairing code +4. Return to [app.kilo.ai/claw](https://app.kilo.ai/claw) and confirm the pairing code and approve +5. You should now be able to chat with the bot from Discord diff --git a/packages/kilo-docs/pages/kiloclaw/chat-platforms/index.md b/packages/kilo-docs/pages/kiloclaw/chat-platforms/index.md new file mode 100644 index 00000000000..69482f76a5c --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/chat-platforms/index.md @@ -0,0 +1,21 @@ +--- +title: "Chat Platforms" +description: "Connect your KiloClaw agent to Telegram, Discord, and Slack" +--- + +# Chat Platforms + +KiloClaw supports connecting your AI agent to messaging platforms so it can receive instructions and send responses directly in your chat apps. You can configure channels from the **Settings** tab on your [KiloClaw dashboard](/docs/kiloclaw/dashboard#channels), or from the OpenClaw Control UI after accessing your instance. + +The general steps to connect any chat platform are: + +1. Configure the channel token in Settings +2. Redeploy the KiloClaw instance +3. Initiate the pairing in the chat app +4. Accept the pairing request in the [KiloClaw UI](https://app.kilo.ai/claw) + +## Supported Platforms + +- [**Telegram**](/docs/kiloclaw/chat-platforms/telegram) — Connect via a BotFather bot token. +- [**Discord**](/docs/kiloclaw/chat-platforms/discord) — Connect via a Discord Developer Portal bot token. +- [**Slack**](/docs/kiloclaw/chat-platforms/slack) — Connect via a Slack app manifest with app-level and bot tokens. diff --git a/packages/kilo-docs/pages/kiloclaw/chat-platforms/slack.md b/packages/kilo-docs/pages/kiloclaw/chat-platforms/slack.md new file mode 100644 index 00000000000..08fbb66f7b6 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/chat-platforms/slack.md @@ -0,0 +1,55 @@ +--- +title: "Slack" +description: "Connect your KiloClaw agent to Slack" +--- + +# Slack + +Connect your KiloClaw agent to Slack by creating a Slack app from the OpenClaw manifest and linking it to your KiloClaw dashboard. + +## Step 1: Create a Slack App from the OpenClaw Manifest + +1. Go to [Slack App Management](https://api.slack.com/apps) and click **Create New App** → **From a Manifest** +2. Copy the manifest from the [OpenClaw docs](https://docs.openclaw.ai/channels/slack#manifest-and-scope-checklist) +3. Paste the manifest JSON into Slack's manifest editor +4. Customize the manifest before creating: + - Rename the app to your preferred name wherever it appears + - Update the slash command if desired (e.g., `/kiloclaw`) +5. Click **Create** + +## Step 2: Generate Tokens + +You need two tokens from Slack: + +**App-Level Token** + +1. In your Slack app settings, scroll down to **App-Level Tokens** +2. Click **Generate Token** +3. Add the `connections:write` scope +4. Generate and copy the token (starts with `xapp-`) + +**Bot User OAuth Token** + +1. In the left sidebar, click **Install App** +2. Install the app to your workspace +3. Copy the **Bot User OAuth Token** (starts with `xoxb-`) + +## Step 3: Connect Slack to KiloClaw + +1. In the [KiloClaw UI](https://app.kilo.ai/claw), find the Slack integration section (may show "not configured") +2. Enter both tokens: + - The `xapp-` app-level token + - The `xoxb-` bot user OAuth token +3. Click **Save** +4. Scroll to the top of the KiloClaw UI and click **Redeploy**. Wait for the instance to come back up + +## Step 4: Pair Slack with KiloClaw + +1. In Slack, DM the app and type your slash command (e.g., `/claw`) followed by anything — this triggers the pairing flow + +> 📝 **Note** +> The slash command is whatever you defined in the manifest. Any text after the command will work to trigger pairing. + +2. The app will return a pairing code +3. Return to [app.kilocode.ai/claw](https://app.kilocode.ai/claw) and confirm the pairing code and approve +4. You should now be able to chat with the bot from Slack diff --git a/packages/kilo-docs/pages/kiloclaw/chat-platforms/telegram.md b/packages/kilo-docs/pages/kiloclaw/chat-platforms/telegram.md new file mode 100644 index 00000000000..8fd8d7b524d --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/chat-platforms/telegram.md @@ -0,0 +1,24 @@ +--- +title: "Telegram" +description: "Connect your KiloClaw agent to Telegram" +--- + +# Telegram + +Connect your KiloClaw agent to Telegram by creating a bot via BotFather and linking it to your KiloClaw dashboard. + +1. Open Telegram and search for [@BotFather](https://t.me/BotFather) +2. Send `/newbot` and follow the prompts to create your bot +3. Copy the **Bot Token** that BotFather gives you +4. Go to the **Settings** tab on your [KiloClaw dashboard](/docs/kiloclaw/dashboard) +5. Paste the token into the **Telegram Bot Token** field +6. Click **Save** +7. Redeploy your KiloClaw instance +8. Send a direct message to your bot in Telegram: `/start` + +{% image src="/docs/img/kiloclaw/telegram.png" alt="Connect account screen" width="800" caption="Telegram bot token entry" /%} + +You can remove or replace a configured token at any time. + +> ℹ️ **Info** +> Advanced settings such as DM policy, allow lists, and groups can be configured in the OpenClaw Control UI after connecting. diff --git a/packages/kilo-docs/pages/kiloclaw/control-ui.md b/packages/kilo-docs/pages/kiloclaw/control-ui.md deleted file mode 100644 index 03aec4885ca..00000000000 --- a/packages/kilo-docs/pages/kiloclaw/control-ui.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: "OpenClaw Control UI" -description: "Browser-based dashboard for managing your OpenClaw instance" ---- - -# OpenClaw Control UI - -The Control UI is a browser-based dashboard (built with Vite + Lit) served by the OpenClaw Gateway on the same port as the gateway itself (default: `http://localhost:18789/`). It connects via WebSocket and gives you real-time control over your agent, channels, sessions, and system configuration. For KiloClaw users, see [Accessing the Control UI](/docs/kiloclaw/dashboard#accessing-the-control-ui) to get started. - -## Features - -- **Chat** — Send messages, stream responses with live tool-call output, view history, and abort runs. -- **Channels** — View the status of connected messaging platforms, scan QR codes for login, and edit per-channel config. -- **Sessions** — List active sessions with thinking and verbose overrides. -- **Cron Jobs** — Create, edit, enable/disable, run, and view history of scheduled tasks. -- **Skills** — View status, enable/disable, install, and manage API keys for skills. -- **Nodes** — List paired devices and their capabilities. -- **Exec Approvals** — Edit gateway or node command allowlists. See [Exec Approvals](#exec-approvals) below. -- **Config** — View and edit `openclaw.json` with schema-based form rendering and a raw JSON editor. -- **Logs** — Live tail of gateway logs with filtering and export. -- **Debug** — Status, health, model snapshots, event log, and manual RPC calls. -- **Update** — Run package updates and restart the gateway. - -For more details, please see the official [OpenClaw documentation](https://docs.openclaw.ai/web/control-ui). - -{% callout type="warning" %} -Do not use the **Update** feature in the Control UI to update KiloClaw. Use **Redeploy** from the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#redeploy) instead. Updating via the Control UI will not apply the correct KiloClaw platform image and may break your instance. -{% /callout %} - -## Changing Models - -The Control UI Chat tab doubles as a command line for model management. KiloClaw exposes 335+ models through the `kilocode` provider and you can browse and switch between them without leaving the chat. - -| Command | Description | -| ------------------------------------ | ------------------------------------------------------------------------------- | -| `/model status` | View the currently active model and provider | -| `/models kilocode` | Browse available models (paginated, 20 per page) | -| `/models kilocode ` | Jump to a specific page (e.g. `/models kilocode 2`) | -| `/model kilocode//` | Switch to a specific model (e.g. `/model kilocode/anthropic/claude-sonnet-4.6`) | -| `/models kilocode all` | List every available model at once | - -Each `/models` response includes helper text at the bottom with shortcuts for switching, paging, and listing all models. - -To change the default model for all new sessions, edit `agents.defaults.model.primary` in your `openclaw.json` via **Config** in the Control UI (or the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#changing-the-model) for a quick dropdown pick). - -For the full list of providers, advanced configuration, and CLI commands, see the [OpenClaw Model Providers documentation](https://docs.openclaw.ai/providers). - -## Authentication - -Auth is handled via token or password on the WebSocket handshake. Remote connections require one-time device pairing — the pairing request appears on the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#pairing-requests) or in the Control UI itself. - -## Exec Approvals - -Exec approvals are the safety interlock that controls which commands your agent can run on the host machine (gateway or node). By default, **all host exec requests are denied** — you must explicitly allowlist the commands you want your agent to run independently. This prevents accidental execution of destructive commands. - -{% callout type="warning" %} -The default security policy is `deny`. You must configure an allowlist before your agent can execute any host commands. -{% /callout %} - -### How It Works - -Approvals are enforced locally on the execution host and sit on top of tool policy and elevated gating. The effective policy is always the **stricter** of `tools.exec.*` and the approvals defaults. Settings are stored in `~/.openclaw/exec-approvals.json` on the host. - -### Security Policies - -| Policy | Behavior | -| ----------- | ---------------------------------------------- | -| `deny` | Block all host exec requests (default) | -| `allowlist` | Allow only commands matching the allowlist | -| `full` | Allow everything (equivalent to elevated mode) | - -### Ask Behavior - -The `ask` setting controls when the user is prompted for approval: - -| Setting | Behavior | -| --------- | ------------------------------------------------------- | -| `off` | Never prompt | -| `on-miss` | Prompt only when the allowlist does not match (default) | -| `always` | Prompt on every command | - -If a prompt is required but no UI is reachable, the `askFallback` setting decides the outcome (`deny` by default). - -### Allowlists - -Allowlists are **per agent** — each agent has its own set of allowed command patterns. Patterns are case-insensitive globs that must resolve to binary paths (basename-only entries are ignored). - -Example patterns: - -``` -~/Projects/**/bin/rg -~/.local/bin/* -/opt/homebrew/bin/rg -``` - -Each entry tracks last-used metadata (timestamp, command, resolved path) so you can audit and keep the list tidy. - -### Approval Flow - -When a command requires approval, the gateway broadcasts the request to connected operator clients. The approval dialog shows the command, arguments, working directory, agent ID, and resolved path. You can: - -- **Allow once** — run the command now -- **Allow always** — add to the allowlist and run -- **Deny** — block the request - -Approval prompts can also be forwarded to chat channels (Slack, Telegram, Discord, etc.) and resolved with `/approve`. - -### Editing in the Control UI - -Navigate to **Nodes > Exec Approvals** in the Control UI to edit defaults, per-agent overrides, and allowlists. Select a scope (Defaults or a specific agent), adjust the policy, add or remove allowlist patterns, then save. - -## Related - -- [KiloClaw Dashboard](/docs/kiloclaw/dashboard) -- [KiloClaw Overview](/docs/kiloclaw/overview) -- [Connecting Chat Platforms](/docs/kiloclaw/chat-platforms) diff --git a/packages/kilo-docs/pages/kiloclaw/control-ui/changing-models.md b/packages/kilo-docs/pages/kiloclaw/control-ui/changing-models.md new file mode 100644 index 00000000000..63c7fb77c62 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/control-ui/changing-models.md @@ -0,0 +1,22 @@ +--- +title: "Changing Models" +description: "Browse and switch models from the Control UI chat" +--- + +# Changing Models + +The Control UI Chat tab doubles as a command line for model management. KiloClaw exposes 335+ models through the `kilocode` provider and you can browse and switch between them without leaving the chat. + +| Command | Description | +| ------------------------------------ | ------------------------------------------------------------------------------- | +| `/model status` | View the currently active model and provider | +| `/models kilocode` | Browse available models (paginated, 20 per page) | +| `/models kilocode ` | Jump to a specific page (e.g. `/models kilocode 2`) | +| `/model kilocode//` | Switch to a specific model (e.g. `/model kilocode/anthropic/claude-sonnet-4.6`) | +| `/models kilocode all` | List every available model at once | + +Each `/models` response includes helper text at the bottom with shortcuts for switching, paging, and listing all models. + +To change the default model for all new sessions, edit `agents.defaults.model.primary` in your `openclaw.json` via **Config** in the Control UI (or the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#changing-the-model) for a quick dropdown pick). + +For the full list of providers, advanced configuration, and CLI commands, see the [OpenClaw Model Providers documentation](https://docs.openclaw.ai/providers). diff --git a/packages/kilo-docs/pages/kiloclaw/control-ui/exec-approvals.md b/packages/kilo-docs/pages/kiloclaw/control-ui/exec-approvals.md new file mode 100644 index 00000000000..1188f545c43 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/control-ui/exec-approvals.md @@ -0,0 +1,64 @@ +--- +title: "Exec Approvals" +description: "Control which commands your KiloClaw agent can run on the host machine" +--- + +# Exec Approvals + +Exec approvals are the safety interlock that controls which commands your agent can run on the host machine (gateway or node). By default, **all host exec requests are denied** — you must explicitly allowlist the commands you want your agent to run independently. This prevents accidental execution of destructive commands. + +{% callout type="warning" %} +The default security policy is `deny`. You must configure an allowlist before your agent can execute any host commands. +{% /callout %} + +## How It Works + +Approvals are enforced locally on the execution host and sit on top of tool policy and elevated gating. The effective policy is always the **stricter** of `tools.exec.*` and the approvals defaults. Settings are stored in `~/.openclaw/exec-approvals.json` on the host. + +## Security Policies + +| Policy | Behavior | +| ----------- | ---------------------------------------------- | +| `deny` | Block all host exec requests (default) | +| `allowlist` | Allow only commands matching the allowlist | +| `full` | Allow everything (equivalent to elevated mode) | + +## Ask Behavior + +The `ask` setting controls when the user is prompted for approval: + +| Setting | Behavior | +| --------- | ------------------------------------------------------- | +| `off` | Never prompt | +| `on-miss` | Prompt only when the allowlist does not match (default) | +| `always` | Prompt on every command | + +If a prompt is required but no UI is reachable, the `askFallback` setting decides the outcome (`deny` by default). + +## Allowlists + +Allowlists are **per agent** — each agent has its own set of allowed command patterns. Patterns are case-insensitive globs that must resolve to binary paths (basename-only entries are ignored). + +Example patterns: + +``` +~/Projects/**/bin/rg +~/.local/bin/* +/opt/homebrew/bin/rg +``` + +Each entry tracks last-used metadata (timestamp, command, resolved path) so you can audit and keep the list tidy. + +## Approval Flow + +When a command requires approval, the gateway broadcasts the request to connected operator clients. The approval dialog shows the command, arguments, working directory, agent ID, and resolved path. You can: + +- **Allow once** — run the command now +- **Allow always** — add to the allowlist and run +- **Deny** — block the request + +Approval prompts can also be forwarded to chat channels (Slack, Telegram, Discord, etc.) and resolved with `/approve`. + +## Editing in the Control UI + +Navigate to **Nodes > Exec Approvals** in the Control UI to edit defaults, per-agent overrides, and allowlists. Select a scope (Defaults or a specific agent), adjust the policy, add or remove allowlist patterns, then save. diff --git a/packages/kilo-docs/pages/kiloclaw/control-ui/overview.md b/packages/kilo-docs/pages/kiloclaw/control-ui/overview.md new file mode 100644 index 00000000000..564e11364eb --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/control-ui/overview.md @@ -0,0 +1,32 @@ +--- +title: "Control UI Overview" +description: "Browser-based dashboard for managing your OpenClaw instance" +--- + +# OpenClaw Control UI + +The Control UI is a browser-based dashboard (built with Vite + Lit) served by the OpenClaw Gateway on the same port as the gateway itself (default: `http://localhost:18789/`). It connects via WebSocket and gives you real-time control over your agent, channels, sessions, and system configuration. For KiloClaw users, see [Accessing the Control UI](/docs/kiloclaw/dashboard#accessing-the-control-ui) to get started. + +## Features + +- **Chat** — Send messages, stream responses with live tool-call output, view history, and abort runs. +- **Channels** — View the status of connected messaging platforms, scan QR codes for login, and edit per-channel config. +- **Sessions** — List active sessions with thinking and verbose overrides. +- **Cron Jobs** — Create, edit, enable/disable, run, and view history of scheduled tasks. +- **Skills** — View status, enable/disable, install, and manage API keys for skills. +- **Nodes** — List paired devices and their capabilities. +- **Exec Approvals** — Edit gateway or node command allowlists. See [Exec Approvals](/docs/kiloclaw/control-ui/exec-approvals). +- **Config** — View and edit `openclaw.json` with schema-based form rendering and a raw JSON editor. +- **Logs** — Live tail of gateway logs with filtering and export. +- **Debug** — Status, health, model snapshots, event log, and manual RPC calls. +- **Update** — Run package updates and restart the gateway. + +For more details, please see the official [OpenClaw documentation](https://docs.openclaw.ai/web/control-ui). + +{% callout type="warning" %} +Do not use the **Update** feature in the Control UI to update KiloClaw. Use **Redeploy** from the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#redeploy) instead. Updating via the Control UI will not apply the correct KiloClaw platform image and may break your instance. +{% /callout %} + +## Authentication + +Auth is handled via token or password on the WebSocket handshake. Remote connections require one-time device pairing — the pairing request appears on the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#pairing-requests) or in the Control UI itself. diff --git a/packages/kilo-docs/pages/kiloclaw/version-pinning.md b/packages/kilo-docs/pages/kiloclaw/control-ui/version-pinning.md similarity index 91% rename from packages/kilo-docs/pages/kiloclaw/version-pinning.md rename to packages/kilo-docs/pages/kiloclaw/control-ui/version-pinning.md index 5f2157e7629..9905620b83a 100644 --- a/packages/kilo-docs/pages/kiloclaw/version-pinning.md +++ b/packages/kilo-docs/pages/kiloclaw/control-ui/version-pinning.md @@ -45,9 +45,3 @@ To return to automatic updates: {% callout type="warning" %} Clearing a pin and running **Upgrade & Redeploy** will update your instance to the latest supported platform version. Review the changelog before upgrading to check for breaking changes. {% /callout %} - -## Related - -- [Dashboard Reference](/docs/kiloclaw/dashboard) -- [KiloClaw Overview](/docs/kiloclaw/overview) -- [Troubleshooting](/docs/kiloclaw/troubleshooting) diff --git a/packages/kilo-docs/pages/kiloclaw/dashboard.md b/packages/kilo-docs/pages/kiloclaw/dashboard.md index 9904f07bc74..ae04957539f 100644 --- a/packages/kilo-docs/pages/kiloclaw/dashboard.md +++ b/packages/kilo-docs/pages/kiloclaw/dashboard.md @@ -74,6 +74,10 @@ If the gateway crashes, it's automatically restarted. The machine itself can be Gateway process info is only available when the machine is running. {% /callout %} +## Instance Specs + +The specs of your instance, including number of CPUs, memory, and storage, are visible at the top right of the instance controls section. + ## Settings ### Changing the Model @@ -96,7 +100,7 @@ You can pin your instance to a specific OpenClaw version and variant from the Se Select a version and variant from the dropdowns and click **Save**. To return to automatic updates, clear the version pin and save. -See [Version Pinning](/docs/kiloclaw/version-pinning) for details. +See [Version Pinning](/docs/kiloclaw/control-ui/version-pinning) for details. ### Version Status Indicators @@ -195,4 +199,4 @@ These are the beta specifications for machines and subject to change without not - [OpenClaw Control UI](/docs/kiloclaw/control-ui) - [Connecting Chat Platforms](/docs/kiloclaw/chat-platforms) - [Troubleshooting](/docs/kiloclaw/troubleshooting) -- [KiloClaw Pricing](/docs/kiloclaw/pricing) +- [KiloClaw Pricing](/docs/kiloclaw/faq/pricing) diff --git a/packages/kilo-docs/pages/kiloclaw/development-tools/index.md b/packages/kilo-docs/pages/kiloclaw/development-tools/index.md new file mode 100644 index 00000000000..a99a3d705de --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/development-tools/index.md @@ -0,0 +1,13 @@ +--- +title: "Development Tools" +description: "Connect your KiloClaw agent to development platforms like GitHub and Google Workspace" +--- + +# Development Tools + +KiloClaw supports integrations with popular development platforms, allowing your agent to interact with repositories, code reviews, calendars, documents, and more — all autonomously. + +## Available Integrations + +- [**GitHub**](/docs/kiloclaw/development-tools/github) — Clone repositories, push commits, open pull requests, and leave code reviews. +- [**Google Workspace**](/docs/kiloclaw/development-tools/google) — Access Gmail, Calendar, Drive, Docs, Sheets, Slides, Tasks, and more. diff --git a/packages/kilo-docs/pages/kiloclaw/faq/general.md b/packages/kilo-docs/pages/kiloclaw/faq/general.md new file mode 100644 index 00000000000..6a63a54d016 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/faq/general.md @@ -0,0 +1,27 @@ +--- +title: "FAQ" +description: "Frequently asked questions about KiloClaw" +--- + +# FAQ + +## How can I change my model? + +You can change the model in two ways: + +- **From chat** — Type `/model` in the Chat window within the OpenClaw Control UI to switch models directly. +- **From the dashboard** — Go to [https://app.kilo.ai/claw](https://app.kilo.ai/claw), select the model you want, and click **Save**. No redeploy is needed. + +## Can I access the filesystem? + +You can access instance files in `/root/.openclaw/` directly from the [KiloClaw Dashboard](https://app.kilo.ai/claw). This is useful for examining or restoring config files. You can also interact with files through your OpenClaw agent using its built-in file tools. + +## Can I access my KiloClaw via SSH? + +For security reasons, SSH access is currently disabled for all KiloClaw instances. Our primary goal is to provide a secure environment for all users, and restricting direct SSH access is one of the many measures we take to ensure the platform remains safe and protected for everyone. + +## How can I update my OpenClaw? + +Do **not** click **Update Now** inside the OpenClaw Control UI — this is not supported for KiloClaw instances and may break your setup. + +Updates are managed by the KiloClaw platform team to ensure stability. When a new version is available, it will be announced in the **Changelog** on your dashboard. To apply the update, click **Upgrade & Redeploy** from the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#redeploy). diff --git a/packages/kilo-docs/pages/kiloclaw/pricing.md b/packages/kilo-docs/pages/kiloclaw/faq/pricing.md similarity index 81% rename from packages/kilo-docs/pages/kiloclaw/pricing.md rename to packages/kilo-docs/pages/kiloclaw/faq/pricing.md index 2d4be00d332..e63d9aa99b2 100644 --- a/packages/kilo-docs/pages/kiloclaw/pricing.md +++ b/packages/kilo-docs/pages/kiloclaw/faq/pricing.md @@ -1,37 +1,31 @@ --- -title: "KiloClaw Pricing" +title: "Pricing" description: "Pricing details for KiloClaw instances and model inference" --- -# KiloClaw Pricing +# Pricing KiloClaw uses Kilo Gateway credits by default — if you route requests through BYOK, model usage is billed directly by your provider instead. -### Instance Hosting +## Instance Hosting KiloClaw hosting is **free during the beta period**. Each user gets a dedicated machine (2 shared vCPUs, 3 GB RAM, 10 GB SSD) at no cost. > ℹ️ **Info** > Beta pricing is subject to change. Paid hosting tiers may be introduced after the beta period ends. Any changes will be announced in advance. -### Model Inference +## Model Inference Model usage is charged against your [Gateway credit balance](/docs/gateway/usage-and-billing). Costs vary by model — premium models like Claude Opus or GPT-5.4-pro cost more per token than smaller models. -### Free Models +## Free Models Several models are available at **no additional cost** to your Gateway balance. These are great for getting started or for tasks that don't need the most powerful models. To see which models are currently free, check the [Kilo Leaderboard](https://kilo.ai/leaderboard#all-models) — free models are marked accordingly. -### Adding Credits +## Adding Credits You can add Gateway credits from your [Kilo account](https://app.kilo.ai). Credits are shared across all Kilo products (VSCode extension, CLI, Cloud Agents, and KiloClaw). See [Adding Credits](/docs/getting-started/adding-credits) and [Gateway Usage and Billing](/docs/gateway/usage-and-billing) for details. - -## Related - -- [KiloClaw Overview](/docs/kiloclaw/overview) -- [Connecting Chat Platforms](/docs/kiloclaw/chat-platforms) -- [Gateway Usage and Billing](/docs/gateway/usage-and-billing) diff --git a/packages/kilo-docs/pages/kiloclaw/overview.md b/packages/kilo-docs/pages/kiloclaw/overview.md index 713fa0e6cb3..40624dd2f6c 100644 --- a/packages/kilo-docs/pages/kiloclaw/overview.md +++ b/packages/kilo-docs/pages/kiloclaw/overview.md @@ -96,7 +96,7 @@ For more information on use cases: - [Dashboard Reference](/docs/kiloclaw/dashboard) - [Connecting Chat Platforms](/docs/kiloclaw/chat-platforms) - [Troubleshooting](/docs/kiloclaw/troubleshooting) -- [KiloClaw Pricing](/docs/kiloclaw/pricing) +- [KiloClaw Pricing](/docs/kiloclaw/faq/pricing) - [Gateway Usage and Billing](/docs/gateway/usage-and-billing) - [Agent Manager](/docs/automate/agent-manager) - [OpenClaw Documentation](https://docs.openclaw.ai) diff --git a/packages/kilo-docs/pages/kiloclaw/tools/1password.md b/packages/kilo-docs/pages/kiloclaw/tools/1password.md new file mode 100644 index 00000000000..10868c41ef4 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/tools/1password.md @@ -0,0 +1,37 @@ +--- +title: "1Password Integration" +description: "Connect your KiloClaw agent to 1Password to securely manage credentials" +--- + +# 1Password Integration Guide + +Connect your KiloClaw agent to 1Password to securely manage credentials. This allows your agent to fetch API keys or passwords without ever seeing them in plain text. + +## Step 1: Create a Dedicated Vault + +For maximum security, do not give the bot access to your personal vault. + +1. Log in to your 1Password account. +2. Create a **New Vault** (e.g., name it `Kilo-Agent-Vault`). +3. Move only the specific items/keys you want the bot to use into this vault. + +## Step 2: Generate a Service Account Token + +1. Go to the [1Password Developer Portal](https://developer.1password.com/). +2. Select **Service Accounts** and click **Create a Service Account**. +3. **Important:** When prompted for permissions, select only the dedicated vault you created in Step 1. +4. Copy the generated token (it will begin with `ops_`). + +## Step 3: Configure KiloClaw + +1. Navigate to your KiloClaw dashboard: [app.kilo.ai/claw](https://app.kilo.ai/claw). +2. Go to **Settings > Tools** (or **Edit Files**). +3. Paste your `ops_` token into the **1Password Setup** field. +4. Click **Save**. + +## Step 4: Activate the Integration + +To apply the changes and inject the 1Password CLI into your environment: + +1. Select **Upgrade to latest**. +2. Perform a **Redeploy** to restart the agent with the new permissions active. diff --git a/packages/kilo-docs/pages/kiloclaw/tools/agentcard.md b/packages/kilo-docs/pages/kiloclaw/tools/agentcard.md new file mode 100644 index 00000000000..79a23bf4622 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/tools/agentcard.md @@ -0,0 +1,44 @@ +--- +title: "AgentCard Integration" +description: "Enable your KiloClaw agents to perform financial transactions with virtual debit cards" +--- + +# AgentCard Integration + +Enable your KiloClaw agents to perform financial transactions by creating and managing virtual debit cards. This integration allows for automated purchasing and expense management within set limits. + +## AgentCard Setup + +### 1. Create an AgentCard Account + +Install the AgentCard CLI and sign up via your terminal: + +```bash +agent-cards signup +``` + +### 2. Add a Payment Method + +Link your funding source (via Stripe) to enable the creation of virtual cards: + +```bash +agent-cards payment-method +``` + +### 3. Retrieve Your API Key + +Open your local configuration file located at `~/.agent-cards/config.json`. Copy the value assigned to the `jwt` key. + +### 4. Configure KiloClaw + +1. Paste the **JWT** into the AgentCard setup field in your KiloClaw settings. +2. Click **Save**. +3. Use **Redeploy** to apply the new secret. Only use **Upgrade & Redeploy** if you also need the latest platform version. + +## Available Tools + +Once activated, your agent will have access to: + +- `create_card`: Generate a new virtual debit card. +- `list_cards`: View existing cards and their statuses. +- `check_balance`: Monitor available funds. diff --git a/packages/kilo-docs/pages/kiloclaw/tools/brave-search.md b/packages/kilo-docs/pages/kiloclaw/tools/brave-search.md new file mode 100644 index 00000000000..6d448c3dada --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/tools/brave-search.md @@ -0,0 +1,28 @@ +--- +title: "Brave Search Integration" +description: "Equip your KiloClaw agent with real-time web browsing via the Brave Search API" +--- + +# Brave Search Integration + +Equip your KiloClaw agent with real-time web browsing capabilities by integrating the Brave Search API. This allows the agent to fetch up-to-date information, perform market research, and verify facts beyond its training data. + +## How to Generate a Brave Search API Key + +To get started, you will need to obtain a "BSA" (Brave Search API) key from the Brave developer portal. + +### 1. Access the Brave Search Dashboard + +Go to [api.search.brave.com](https://api.search.brave.com) and sign in or create a developer account. + +### 2. Choose a Subscription Plan + +Brave Search API requires a paid subscription. Select the plan that fits your usage volume. + +### 3. Create an API Key + +Once your account is active, navigate to the **API Keys** section and click **"Create New Key."** + +### 4. Copy the Key + +Your key will typically begin with the prefix `BSA`. Copy this key immediately, as it may not be displayed again for security reasons. diff --git a/packages/kilo-docs/pages/kiloclaw/tools/index.md b/packages/kilo-docs/pages/kiloclaw/tools/index.md new file mode 100644 index 00000000000..4a17e3289e7 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/tools/index.md @@ -0,0 +1,14 @@ +--- +title: "Tools" +description: "Third-party tool integrations for your KiloClaw agent" +--- + +# Tools + +KiloClaw supports integrations with third-party tools that extend your agent's capabilities — from secure credential management to web search and financial transactions. + +## Available Integrations + +- [**1Password**](/docs/kiloclaw/tools/1password) — Securely manage credentials and let your agent fetch API keys or passwords without ever seeing them in plain text. +- [**Brave Search**](/docs/kiloclaw/tools/brave-search) — Equip your agent with real-time web browsing via the Brave Search API. +- [**AgentCard**](/docs/kiloclaw/tools/agentcard) — Enable your agent to perform financial transactions using virtual debit cards. diff --git a/packages/kilo-docs/pages/kiloclaw/troubleshooting.md b/packages/kilo-docs/pages/kiloclaw/troubleshooting.md deleted file mode 100644 index c3f07a9f0a0..00000000000 --- a/packages/kilo-docs/pages/kiloclaw/troubleshooting.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: "Troubleshooting" -description: "Common issues, diagnostics, and FAQ for KiloClaw instances" ---- - -# Troubleshooting - -## OpenClaw Doctor - -OpenClaw Doctor is the recommended first step when something isn't working. It runs diagnostics on your instance and automatically fixes common configuration issues. - -To use it: - -1. Make sure your instance is running -2. Click **OpenClaw Doctor** on your [dashboard](/docs/kiloclaw/dashboard) -3. Watch the output as it runs — results appear in real time - -## Common Questions - -### Does Redeploy reset my instance? - -No. Redeploy does **not** delete your files, git repos, or cron jobs. It stops the machine, applies the latest platform image and your current configuration, and starts it again with the same persistent storage. Think of it as "update and restart." - -### When should I use Restart OpenClaw vs Redeploy? - -- **Restart OpenClaw** — Restarts just the OpenClaw process. The machine stays up. Use this for quick recovery from a process-level issue or when you want to apply openclaw config changes. -- **Redeploy** — Stops and restarts the entire machine with the latest image and config. Use this when the changelog shows a redeploy hint, or after changing channel tokens or secrets. - -### My bot isn't responding on Telegram/Discord/Slack - -1. Check that the channel token is configured in [Settings](/docs/kiloclaw/dashboard#channels) -2. Make sure you **Redeployed** or **Restarted OpenClaw** after saving tokens -3. Check for pending [pairing requests](/docs/kiloclaw/chat-platforms#pairing-requests) — the user may need to be approved -4. Try running **OpenClaw Doctor** - -### Accessing and Restoring Config Files - -You can directly access the files in /root/.openclaw/ on the [KiloClaw Dashboard](https://app.kilo.ai/claw) using the file browser of the edit files dialog. This can be a useful way to examine or update the config files (especially `openclaw.json`) if you run into an issue. There may also be backups in the form of `openclaw.bak` files that you can manually restore from if needed. - -### The gateway shows "Crashed" - -The OpenClaw process is automatically restarted when it crashes. Check the Gateway Process tab on your dashboard for the exit code and restart count. If it keeps crashing: - -1. Run **OpenClaw Doctor** -2. Try a **Redeploy** to apply the latest platform image -3. If the issue persists, join the [Kilo Discord](https://kilo.ai/discord) and share details in the KiloClaw channel - -### I changed the model but the agent is still using the old one - -After selecting a new model, click **Save & Provision** to apply it. This refreshes the API key and saves the new model. You may also need to **Restart OpenClaw** for the change to take full effect. - -## Gateway Process States - -The Gateway Process tab shows the current state of the OpenClaw process inside your machine: - -- **Running** — The process is up and handling requests -- **Stopped** — The process is not running -- **Starting** — The process is booting up -- **Stopping** — The process is shutting down gracefully -- **Crashed** — The process exited unexpectedly and will be automatically restarted -- **Shutting Down** — The process is stopping as part of a machine stop or redeploy - -## FAQ - -### How can I change my model? - -You can change the model in two ways: - -- **From chat** — Type `/model` in the Chat window within the OpenClaw Control UI to switch models directly. -- **From the dashboard** — Go to [https://app.kilo.ai/claw](https://app.kilo.ai/claw), select the model you want, and click **Save**. No redeploy is needed. - -### Can I access the filesystem? - -You can access instance files in `/root/.openclaw/` directly from the [KiloClaw Dashboard](https://app.kilo.ai/claw). This is useful for examining or restoring config files — see [Accessing and Restoring Config Files](#accessing-and-restoring-config-files) above. You can also interact with files through your OpenClaw agent using its built-in file tools. - -### Can I access my KiloClaw via SSH? - -For security reasons, SSH access is currently disabled for all KiloClaw instances. Our primary goal is to provide a secure environment for all users, and restricting direct SSH access is one of the many measures we take to ensure the platform remains safe and protected for everyone. - -### How can I update my OpenClaw? - -Do **not** click **Update Now** inside the OpenClaw Control UI — this is not supported for KiloClaw instances and may break your setup. - -Updates are managed by the KiloClaw platform team to ensure stability. When a new version is available, it will be announced in the **Changelog** on your dashboard. To apply the update, click **Upgrade & Redeploy** from the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#redeploy). - -## Architecture Notes - -For advanced users — how KiloClaw instances are structured: - -- **Dedicated machine** — Each user gets their own machine and persistent volume. There is no shared infrastructure between users. -- **Region-pinned storage** — Your persistent volume stays in the region where your instance was originally created. -- **Network isolation** — OpenClaw binds to loopback only; external traffic is proxied through a Kilo controller. -- **Per-user authentication** — The gateway token is derived per-user for authenticating requests to your machine. -- **Encryption at rest** — Sensitive data (API keys, channel tokens) is encrypted at rest in the machine configuration. - -## Related - -- [KiloClaw Overview](/docs/kiloclaw/overview) -- [Dashboard Reference](/docs/kiloclaw/dashboard) -- [Connecting Chat Platforms](/docs/kiloclaw/chat-platforms) -- [KiloClaw Pricing](/docs/kiloclaw/pricing) diff --git a/packages/kilo-docs/pages/kiloclaw/troubleshooting/architecture.md b/packages/kilo-docs/pages/kiloclaw/troubleshooting/architecture.md new file mode 100644 index 00000000000..2290fb1b9c2 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/troubleshooting/architecture.md @@ -0,0 +1,14 @@ +--- +title: "Architecture Notes" +description: "How KiloClaw instances are structured" +--- + +# Architecture Notes + +For advanced users — how KiloClaw instances are structured: + +- **Dedicated machine** — Each user gets their own machine and persistent volume. There is no shared infrastructure between users. +- **Region-pinned storage** — Your persistent volume stays in the region where your instance was originally created. +- **Network isolation** — OpenClaw binds to loopback only; external traffic is proxied through a Kilo controller. +- **Per-user authentication** — The gateway token is derived per-user for authenticating requests to your machine. +- **Encryption at rest** — Sensitive data (API keys, channel tokens) is encrypted at rest in the machine configuration. diff --git a/packages/kilo-docs/pages/kiloclaw/troubleshooting/common-questions.md b/packages/kilo-docs/pages/kiloclaw/troubleshooting/common-questions.md new file mode 100644 index 00000000000..1c49d2a39df --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/troubleshooting/common-questions.md @@ -0,0 +1,48 @@ +--- +title: "Common Questions" +description: "Answers to common KiloClaw troubleshooting questions" +--- + +# Common Questions + +## OpenClaw Doctor + +OpenClaw Doctor is the recommended first step when something isn't working. It runs diagnostics on your instance and automatically fixes common configuration issues. + +To use it: + +1. Make sure your instance is running +2. Click **OpenClaw Doctor** on your [dashboard](/docs/kiloclaw/dashboard) +3. Watch the output as it runs — results appear in real time + +## Does Redeploy reset my instance? + +No. Redeploy does **not** delete your files, git repos, or cron jobs. It stops the machine, applies the latest platform image and your current configuration, and starts it again with the same persistent storage. Think of it as "update and restart." + +## When should I use Restart OpenClaw vs Redeploy? + +- **Restart OpenClaw** — Restarts just the OpenClaw process. The machine stays up. Use this for quick recovery from a process-level issue or when you want to apply openclaw config changes. +- **Redeploy** — Stops and restarts the entire machine with the latest image and config. Use this when the changelog shows a redeploy hint, or after changing channel tokens or secrets. + +## My bot isn't responding on Telegram/Discord/Slack + +1. Check that the channel token is configured in [Settings](/docs/kiloclaw/dashboard#channels) +2. Make sure you **Redeployed** or **Restarted OpenClaw** after saving tokens +3. Check for pending pairing requests — the user may need to be approved +4. Try running **OpenClaw Doctor** + +## Accessing and Restoring Config Files + +You can directly access the files in `/root/.openclaw/` on the [KiloClaw Dashboard](https://app.kilo.ai/claw) using the file browser of the edit files dialog. This can be a useful way to examine or update the config files (especially `openclaw.json`) if you run into an issue. There may also be backups in the form of `openclaw.bak` files that you can manually restore from if needed. + +## The gateway shows "Crashed" + +The OpenClaw process is automatically restarted when it crashes. Check the Gateway Process tab on your dashboard for the exit code and restart count. If it keeps crashing: + +1. Run **OpenClaw Doctor** +2. Try a **Redeploy** to apply the latest platform image +3. If the issue persists, join the [Kilo Discord](https://kilo.ai/discord) and share details in the KiloClaw channel + +## I changed the model but the agent is still using the old one + +After selecting a new model, click **Save & Provision** to apply it. This refreshes the API key and saves the new model. You may also need to **Restart OpenClaw** for the change to take full effect. diff --git a/packages/kilo-docs/pages/kiloclaw/troubleshooting/faq.md b/packages/kilo-docs/pages/kiloclaw/troubleshooting/faq.md new file mode 100644 index 00000000000..6a63a54d016 --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/troubleshooting/faq.md @@ -0,0 +1,27 @@ +--- +title: "FAQ" +description: "Frequently asked questions about KiloClaw" +--- + +# FAQ + +## How can I change my model? + +You can change the model in two ways: + +- **From chat** — Type `/model` in the Chat window within the OpenClaw Control UI to switch models directly. +- **From the dashboard** — Go to [https://app.kilo.ai/claw](https://app.kilo.ai/claw), select the model you want, and click **Save**. No redeploy is needed. + +## Can I access the filesystem? + +You can access instance files in `/root/.openclaw/` directly from the [KiloClaw Dashboard](https://app.kilo.ai/claw). This is useful for examining or restoring config files. You can also interact with files through your OpenClaw agent using its built-in file tools. + +## Can I access my KiloClaw via SSH? + +For security reasons, SSH access is currently disabled for all KiloClaw instances. Our primary goal is to provide a secure environment for all users, and restricting direct SSH access is one of the many measures we take to ensure the platform remains safe and protected for everyone. + +## How can I update my OpenClaw? + +Do **not** click **Update Now** inside the OpenClaw Control UI — this is not supported for KiloClaw instances and may break your setup. + +Updates are managed by the KiloClaw platform team to ensure stability. When a new version is available, it will be announced in the **Changelog** on your dashboard. To apply the update, click **Upgrade & Redeploy** from the [KiloClaw Dashboard](/docs/kiloclaw/dashboard#redeploy). diff --git a/packages/kilo-docs/pages/kiloclaw/troubleshooting/gateway-process.md b/packages/kilo-docs/pages/kiloclaw/troubleshooting/gateway-process.md new file mode 100644 index 00000000000..045139fa11c --- /dev/null +++ b/packages/kilo-docs/pages/kiloclaw/troubleshooting/gateway-process.md @@ -0,0 +1,15 @@ +--- +title: "Gateway Process States" +description: "Understanding KiloClaw gateway process states" +--- + +# Gateway Process States + +The Gateway Process tab shows the current state of the OpenClaw process inside your machine: + +- **Running** — The process is up and handling requests +- **Stopped** — The process is not running +- **Starting** — The process is booting up +- **Stopping** — The process is shutting down gracefully +- **Crashed** — The process exited unexpectedly and will be automatically restarted +- **Shutting Down** — The process is stopping as part of a machine stop or redeploy diff --git a/packages/kilo-docs/previous-docs-redirects.js b/packages/kilo-docs/previous-docs-redirects.js index 60b42f13cf2..c6d3804f430 100644 --- a/packages/kilo-docs/previous-docs-redirects.js +++ b/packages/kilo-docs/previous-docs-redirects.js @@ -1,4 +1,10 @@ module.exports = [ + { + source: "/docs/contributing/cline-to-kilo-migration", + destination: "/docs/contributing", + basePath: false, + permanent: true, + }, { source: "/docs/contributing/architecture/model-provider-blocklist", destination: "/docs/collaborate/enterprise/model-access-controls", @@ -138,7 +144,7 @@ module.exports = [ }, { source: "/docs/basic-usage/using-modes", - destination: "/docs/code-with-ai/agents/using-modes", + destination: "/docs/code-with-ai/agents/using-agents", basePath: false, permanent: true, }, @@ -807,4 +813,10 @@ module.exports = [ basePath: false, permanent: true, }, + { + source: "/docs/code-with-ai/agents/using-modes", + destination: "/docs/code-with-ai/agents/using-agents", + basePath: false, + permanent: true, + }, ] diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index 0ff621242af..dc9878552e9 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -1,13 +1,15 @@ # Source Code Links - + +- + + - - - - - diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index e9c3020099b..cc0c2cc9aef 100644 --- a/packages/kilo-gateway/package.json +++ b/packages/kilo-gateway/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-gateway", - "version": "7.1.3", + "version": "7.1.11", "type": "module", "license": "MIT", "description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration", @@ -30,7 +30,7 @@ "@kilocode/plugin": "workspace:*", "@kilocode/sdk": "workspace:*", "@ai-sdk/anthropic": "2.0.65", - "@ai-sdk/openai": "2.0.89", + "@ai-sdk/openai": "2.0.101", "@ai-sdk/openai-compatible": "1.0.32", "@openrouter/ai-sdk-provider": "1.5.4", "@clack/prompts": "1.0.0-alpha.1", diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json index fced9e2474b..b709bbcea07 100644 --- a/packages/kilo-i18n/package.json +++ b/packages/kilo-i18n/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-i18n", - "version": "7.1.3", + "version": "7.1.11", "type": "module", "license": "MIT", "description": "Kilo-specific i18n translations and overrides", diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json index e907ce0451f..c4c56bb6060 100644 --- a/packages/kilo-telemetry/package.json +++ b/packages/kilo-telemetry/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-telemetry", - "version": "7.1.3", + "version": "7.1.11", "type": "module", "license": "MIT", "description": "Telemetry for Kilo CLI - PostHog analytics integration", diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index eb8f9e0b9a7..1e9a2132eac 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-ui", - "version": "7.1.3", + "version": "7.1.11", "type": "module", "license": "MIT", "exports": { diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index f41d3cbd8ed..187a6a2df21 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -669,6 +669,15 @@ function isContextGroupTool(part: PartType): part is ToolPart { function ExaOutput(props: { output?: string }) { const links = createMemo(() => urls(props.output)) + const data = useData() + + const open = (url: string, event: MouseEvent) => { + event.stopPropagation() + event.preventDefault() + const handler = data.openUrl + if (handler) return handler(url) + window.open(url, "_blank", "noopener,noreferrer") + } return ( 0}> @@ -676,13 +685,7 @@ function ExaOutput(props: { output?: string }) {
{(url) => ( - event.stopPropagation()} - > + {url} )} @@ -1306,6 +1309,15 @@ function useToolReveal(pending: () => boolean, animate?: () => boolean) { function WebfetchMeta(props: { url: string; animate?: boolean }) { let ref: HTMLSpanElement | undefined useToolFade(() => ref, { wipe: true, animate: props.animate }) + const data = useData() + + const open = (event: MouseEvent) => { + event.stopPropagation() + event.preventDefault() + const handler = data.openUrl + if (handler) return handler(props.url) + window.open(props.url, "_blank", "noopener,noreferrer") + } return ( @@ -1316,11 +1328,11 @@ function WebfetchMeta(props: { url: string; animate?: boolean }) { href={props.url} target="_blank" rel="noopener noreferrer" - onClick={(event) => event.stopPropagation()} + onClick={open} > {props.url} -
+
@@ -2007,8 +2019,8 @@ interface ApplyPatchFile { relativePath: string type: "add" | "update" | "delete" | "move" diff: string - before: string - after: string + before?: string + after?: string additions: number deletions: number movePath?: string @@ -2063,7 +2075,6 @@ ToolRegistry.register({ path={file().relativePath.includes("/") ? getDirectory(file().relativePath) : undefined} changes={{ additions: file().additions, deletions: file().deletions }} animate={reveal()} - soft onClick={ data.openFile && file().filePath ? (e: MouseEvent) => { @@ -2159,7 +2170,7 @@ ToolRegistry.register({ - +
} > -
- -
+ +
+ +
+
)} diff --git a/packages/kilo-ui/src/hooks/create-auto-scroll.tsx b/packages/kilo-ui/src/hooks/create-auto-scroll.tsx index cd490a65f22..6ea1f5d8ad0 100644 --- a/packages/kilo-ui/src/hooks/create-auto-scroll.tsx +++ b/packages/kilo-ui/src/hooks/create-auto-scroll.tsx @@ -17,6 +17,7 @@ export function createAutoScroll(options: AutoScrollOptions) { let stopTimer: ReturnType | undefined let cleanup: (() => void) | undefined let userInitiated = false + let lastScrollTop: number | undefined const threshold = () => options.bottomThreshold ?? 10 @@ -54,6 +55,7 @@ export function createAutoScroll(options: AutoScrollOptions) { // `scrollTop` assignment bypasses any CSS `scroll-behavior: smooth`. el.scrollTop = el.scrollHeight + lastScrollTop = el.scrollTop } const scrollToBottom = (force: boolean) => { @@ -112,11 +114,17 @@ export function createAutoScroll(options: AutoScrollOptions) { if (distance < threshold()) { if (store.userScrolled) setStore("userScrolled", false) + lastScrollTop = el.scrollTop return } if (!store.userScrolled && !byUser) { - scrollToBottomNow("auto") + if (el.scrollTop < (lastScrollTop ?? el.scrollTop)) { + stop() + } else { + scrollToBottomNow("auto") + } + lastScrollTop = el.scrollTop return } diff --git a/packages/kilo-vscode/LICENSE b/packages/kilo-vscode/LICENSE new file mode 100644 index 00000000000..5cb50685f18 --- /dev/null +++ b/packages/kilo-vscode/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 Kilo Code +Copyright (c) 2025 opencode + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/kilo-vscode/README.md b/packages/kilo-vscode/README.md index aef1c95a4d2..e69a35887f8 100644 --- a/packages/kilo-vscode/README.md +++ b/packages/kilo-vscode/README.md @@ -1,9 +1,9 @@

- VS Code Marketplace - X (Twitter) - Substack Blog - Discord - Reddit + VS Code Marketplace + X (Twitter) + Substack Blog + Discord + Reddit

# 🚀 Kilo diff --git a/packages/kilo-vscode/docs/agent-behaviour/mcp-server-creation.md b/packages/kilo-vscode/docs/agent-behaviour/mcp-server-creation.md index 7656a450dcb..2b2096bb9c1 100644 --- a/packages/kilo-vscode/docs/agent-behaviour/mcp-server-creation.md +++ b/packages/kilo-vscode/docs/agent-behaviour/mcp-server-creation.md @@ -2,7 +2,7 @@ **Priority:** P2 -The legacy MCP Servers sub-tab (842 lines) had full server lifecycle management. The new sub-tab can only view and remove servers. +The legacy MCP Servers sub-tab (842 lines) had full server lifecycle management. The new sub-tab supports viewing, removing, and toggling servers with live connection status. ## Side-by-Side Comparison @@ -12,8 +12,8 @@ The legacy MCP Servers sub-tab (842 lines) had full server lifecycle management. | Add server | Via edit config file buttons | Not available | | Remove server | Delete button + confirmation | Remove button + confirmation | | Edit server config | Via edit config file buttons | Not available | -| Connection status | Colored dot (green/yellow/red) | Not displayed | -| Enable/disable toggle | `ToggleSwitch` per server | Not available | +| Connection status | Colored dot (green/yellow/red) | Live status display | +| Enable/disable toggle | `ToggleSwitch` per server | Connect/disconnect toggle | | Restart/refresh | Per-server refresh button | Not available | | Refresh all | "Refresh All MCP Servers" button | Not available | | Edit Global MCP config | Button to open file | Not available | @@ -42,8 +42,6 @@ The new extension has none of this expandable detail. - **Add MCP Server dialog**: Form to define a new server with name, transport type (stdio/SSE), command+args or URL, environment variables. Write to CLI config via `updateConfig()` - **Edit MCP Server**: Allow modifying existing server configurations -- **Connection status indicators**: Display per-server connection state. Requires CLI to expose MCP connection status via API or SSE events -- **Enable/disable toggle**: Per-server toggle that writes to CLI config without removing the server - **Restart/refresh button**: Per-server and "refresh all" buttons. Requires CLI endpoint to restart MCP servers - **Edit config file buttons**: Quick links to open global/project MCP config in the editor diff --git a/packages/kilo-vscode/docs/features/custom-openai-provider-ui.md b/packages/kilo-vscode/docs/features/custom-openai-provider-ui.md deleted file mode 100644 index b23f58af664..00000000000 --- a/packages/kilo-vscode/docs/features/custom-openai-provider-ui.md +++ /dev/null @@ -1,15 +0,0 @@ -# Custom OpenAI-Compatible Provider UI - -**Priority:** P1 -**Issue:** [#6163](https://github.com/Kilo-Org/kilocode/issues/6163) - -No UI for adding custom OpenAI-compatible providers. Users must manually edit `opencode.json`. The web/TUI app has a full `DialogCustomProvider`. - -## Remaining Work - -- Port `DialogCustomProvider` from `packages/app/src/components/dialog-custom-provider.tsx` to extension webview -- Add "Add custom provider" button to the Providers tab -- Dialog should collect: Provider ID, display name, base URL, API key, model entries, optional custom HTTP headers -- On save, write to CLI config via `handleUpdateConfig()` endpoint -- Validate inputs; show inline errors -- After adding, provider should appear in list and models in model selector diff --git a/packages/kilo-vscode/docs/migration/memorybank-migration.md b/packages/kilo-vscode/docs/migration/memorybank-migration.md deleted file mode 100644 index 6956da0d1cc..00000000000 --- a/packages/kilo-vscode/docs/migration/memorybank-migration.md +++ /dev/null @@ -1,12 +0,0 @@ -# MemoryBank Migration - -**Priority:** P1 -**Issue:** [#6091](https://github.com/Kilo-Org/kilocode/issues/6091) - -## Remaining Work - -- Detect whether user has MemoryBank content in old extension's `workspaceState` -- Offer to migrate MemoryBank content to `AGENTS.md` in workspace root (or `~/.kilocode/rules/` for global rules) -- Show content for review before writing — do not silently overwrite existing `AGENTS.md` -- If `AGENTS.md` already exists, offer to append with a clear delimiter -- Show documentation explaining that `AGENTS.md` / rules files are the equivalent of MemoryBank diff --git a/packages/kilo-vscode/docs/migration/settings-migration.md b/packages/kilo-vscode/docs/migration/settings-migration.md deleted file mode 100644 index 140a592f500..00000000000 --- a/packages/kilo-vscode/docs/migration/settings-migration.md +++ /dev/null @@ -1,30 +0,0 @@ -# Settings Migration from Old Extension - -**Priority:** P1 -**Issue:** [#6089](https://github.com/Kilo-Org/kilocode/issues/6089) - -## Remaining Work - -- On first activation, detect whether old extension settings exist in `vscode.ExtensionContext.globalState` or `vscode.workspace.getConfiguration('kilo-code')` -- Read relevant settings: API keys, provider configuration, model preferences, auto-approve rules, custom instructions -- Map old settings keys to CLI config equivalents in `opencode.json` -- If CLI config already has settings, show a diff and ask user to confirm before overwriting -- Write approved settings to CLI config via `/global/config` endpoint or directly to `opencode.json` -- Show what was migrated and what was not -- Mark migration as complete in `globalState` so it doesn't run again - -## Agent Behaviour Tab Settings to Migrate - -Settings from the legacy "Agent Behaviour" tab (Modes, MCP Servers, Rules, Workflows, Skills sub-tabs): - -| Legacy Setting | CLI Equivalent | Notes | -| ----------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------- | -| `customModes` (custom mode definitions) | CLI agent config in `opencode.json` | Map `roleDefinition` → `prompt`, `groups` → CLI tool permissions | -| `customModePrompts` (per-mode overrides) | Per-agent `prompt`, `temperature`, etc. | Map each mode slug to CLI agent name | -| `customInstructions` (global, shown in Modes sub-tab) | Rule files in `.kilocode/rules/` or `config.instructions` | May need a global instructions config key in CLI | -| `modeApiConfigs` (per-mode model) | Per-agent `model` | Map mode slug → agent name → model ID | -| MCP server configs | `config.mcp` | CLI owns MCP config | -| `localRulesToggles` / `globalRulesToggles` | `config.instructions` | Toggle state doesn't map directly — CLI has path list | -| `localWorkflowToggles` / `globalWorkflowToggles` | CLI custom commands (TBD) | Workflow concept mapping needs clarification | - -See [Agent Behaviour Tab Parity](../agent-behaviour/) docs for detailed sub-tab comparisons. diff --git a/packages/kilo-vscode/docs/migration/upgrade-onboarding.md b/packages/kilo-vscode/docs/migration/upgrade-onboarding.md deleted file mode 100644 index 4f409abd2ee..00000000000 --- a/packages/kilo-vscode/docs/migration/upgrade-onboarding.md +++ /dev/null @@ -1,15 +0,0 @@ -# Upgrade Onboarding Experience - -**Priority:** P1 -**Issue:** [#6188](https://github.com/Kilo-Org/kilocode/issues/6188) - -## Remaining Work - -- Detect upgraders by checking for old `globalState` keys (e.g., `kilo-code.taskHistory`) -- Show distinct onboarding screen that: - - Acknowledges they are an existing user - - Explains the new CLI backend architecture - - Shows what settings have been migrated and what hasn't - - Links to documentation on what changed -- Do not show generic "new user" welcome screen to upgraders -- Store `kilo-code.new.upgradeOnboardingShown` flag in `globalState` diff --git a/packages/kilo-vscode/docs/non-agent-features/mcp-and-mcp-hub.md b/packages/kilo-vscode/docs/non-agent-features/mcp-and-mcp-hub.md index 0d2e3eb1742..abfd67524fa 100644 --- a/packages/kilo-vscode/docs/non-agent-features/mcp-and-mcp-hub.md +++ b/packages/kilo-vscode/docs/non-agent-features/mcp-and-mcp-hub.md @@ -2,13 +2,11 @@ **Priority:** P1 -Settings UI shows a read-only list of configured MCP servers. HTTP client methods exist for add/connect/disconnect. +Settings UI supports viewing, removing, and toggling MCP servers with live connection status. Add/edit is not yet available. ## Remaining Work -- Add/edit/delete MCP server controls in the settings UI -- Connect/disconnect controls per server +- Add/edit MCP server controls in the settings UI - Tool allowlisting/disablement per server -- Connection status display per server (connected/disconnected/error) - Auto-reconnect and error history display - MCP Hub for discovering available MCP servers diff --git a/packages/kilo-vscode/docs/non-agent-features/settings-ui.md b/packages/kilo-vscode/docs/non-agent-features/settings-ui.md index 47dc84ffd58..0bd936280a0 100644 --- a/packages/kilo-vscode/docs/non-agent-features/settings-ui.md +++ b/packages/kilo-vscode/docs/non-agent-features/settings-ui.md @@ -2,14 +2,13 @@ **Priority:** P1 -12 of 14 tabs are functional. +12 of 14 tabs are functional. Import/export settings is available in the About tab. ## Remaining Work - **Terminal tab**: Shows "Not implemented" message - **Prompts tab**: Shows "Not implemented" message - **Workflows subtab** (inside AgentBehaviour): Renders a `` with no functionality -- Import/export settings functionality ## Related Docs diff --git a/packages/kilo-vscode/docs/opencode-migration-plan.md b/packages/kilo-vscode/docs/opencode-migration-plan.md index e16202e4dfd..bf244c5a446 100644 --- a/packages/kilo-vscode/docs/opencode-migration-plan.md +++ b/packages/kilo-vscode/docs/opencode-migration-plan.md @@ -22,23 +22,23 @@ This document tracks remaining work needed for feature parity with the old exten ## Non-Agent Feature Parity -| Feature | Remaining Work | Backend | Priority | -| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------- | -| [Authentication & Enterprise](non-agent-features/authentication-organization-enterprise-enforcement.md) | Org feature flags, MDM policy enforcement | CLI handles its auth; extension handles org/MDM | P1 | -| [Auto-Purge](non-agent-features/auto-purge.md) | Scheduled cleanup of old session/task storage | Extension-side (storage ownership TBD) | P3 | -| [Cloud Task Support](non-agent-features/cloud-task-support.md) | Upload local sessions to cloud, real-time sync, conflict resolution | Kilo cloud API + CLI; extension provides UI | P2 | -| [Code Reviews](non-agent-features/code-reviews.md) | Local review mode, automated AI review of uncommitted/branch changes | CLI (partial); extension for VS Code review UX | P2 | -| [Codebase Indexing & Semantic Search](non-agent-features/codebase-indexing-semantic-search.md) | Vector indexing, semantic search, embeddings infrastructure | CLI has grep/glob endpoints; semantic indexing is extension or cloud | P2 | -| [Contribution Tracking](non-agent-features/contribution-tracking.md) | AI attribution tracking, line fingerprinting, reporting | Extension-side | P3 | -| [Custom Commands](non-agent-features/custom-command-system.md) | Slash command system, project-level command discovery, YAML frontmatter support | CLI has custom commands; extension provides UI entry points | P2 | -| [Marketplace](non-agent-features/marketplace.md) | Catalog, install, update capabilities (toolbar button exists but renders a stub) | Extension-side | P2 | -| [MCP & MCP Hub](non-agent-features/mcp-and-mcp-hub.md) | MCP configuration UI (add/edit/delete servers), tool allowlisting, connection status display | CLI owns MCP lifecycle; extension provides config UI | P1 | -| [Repository Initialization](non-agent-features/repository-initialization.md) | /init command support for setting up agentic engineering | CLI /init endpoint; extension provides UI trigger | P3 | -| [Rules & Workflows](non-agent-features/rules-and-workflows.md) | Workflow management UI (rules subtab exists, workflows subtab is a stub) | CLI owns rules runtime; extension provides management UI | P3 | -| [Settings Sync](non-agent-features/settings-sync-integration.md) | VS Code Settings Sync allowlist registration | Extension-side (VS Code API) | P3 | -| [Settings UI](non-agent-features/settings-ui.md) | Terminal and Prompts tabs (show "Not implemented"), Workflows subtab stub, import/export settings | CLI exposes config; extension provides settings forms | P1 | -| [Skills System](non-agent-features/skills-system.md) | Skill execution, discovery, hot-reload (config UI for paths/URLs exists) | CLI has skills runtime; extension provides packaging/UI | P2 | -| [Speech-to-Text](non-agent-features/speech-to-text.md) | Voice input, streaming STT | Webview (mic capture); CLI-compatible STT optional | P3 | +| Feature | Remaining Work | Backend | Priority | +| ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------- | +| [Authentication & Enterprise](non-agent-features/authentication-organization-enterprise-enforcement.md) | Org feature flags, MDM policy enforcement | CLI handles its auth; extension handles org/MDM | P1 | +| [Auto-Purge](non-agent-features/auto-purge.md) | Scheduled cleanup of old session/task storage | Extension-side (storage ownership TBD) | P3 | +| [Cloud Task Support](non-agent-features/cloud-task-support.md) | Upload local sessions to cloud, real-time sync, conflict resolution | Kilo cloud API + CLI; extension provides UI | P2 | +| [Code Reviews](non-agent-features/code-reviews.md) | Local review mode, automated AI review of uncommitted/branch changes | CLI (partial); extension for VS Code review UX | P2 | +| [Codebase Indexing & Semantic Search](non-agent-features/codebase-indexing-semantic-search.md) | Vector indexing, semantic search, embeddings infrastructure | CLI has grep/glob endpoints; semantic indexing is extension or cloud | P2 | +| [Contribution Tracking](non-agent-features/contribution-tracking.md) | AI attribution tracking, line fingerprinting, reporting | Extension-side | P3 | +| [Custom Commands](non-agent-features/custom-command-system.md) | Slash command system, project-level command discovery, YAML frontmatter support | CLI has custom commands; extension provides UI entry points | P2 | +| [Marketplace](non-agent-features/marketplace.md) | Catalog, install, update capabilities (toolbar button exists but renders a stub) | Extension-side | P2 | +| [MCP & MCP Hub](non-agent-features/mcp-and-mcp-hub.md) | MCP configuration UI (add/edit/delete servers), tool allowlisting | CLI owns MCP lifecycle; extension provides config UI | P1 | +| [Repository Initialization](non-agent-features/repository-initialization.md) | /init command support for setting up agentic engineering | CLI /init endpoint; extension provides UI trigger | P3 | +| [Rules & Workflows](non-agent-features/rules-and-workflows.md) | Workflow management UI (rules subtab exists, workflows subtab is a stub) | CLI owns rules runtime; extension provides management UI | P3 | +| [Settings Sync](non-agent-features/settings-sync-integration.md) | VS Code Settings Sync allowlist registration | Extension-side (VS Code API) | P3 | +| [Settings UI](non-agent-features/settings-ui.md) | Terminal and Prompts tabs (show "Not implemented"), Workflows subtab stub | CLI exposes config; extension provides settings forms | P1 | +| [Skills System](non-agent-features/skills-system.md) | Skill execution, discovery, hot-reload (config UI for paths/URLs exists) | CLI has skills runtime; extension provides packaging/UI | P2 | +| [Speech-to-Text](non-agent-features/speech-to-text.md) | Voice input, streaming STT | Webview (mic capture); CLI-compatible STT optional | P3 | --- @@ -49,7 +49,7 @@ The "Agent Behaviour" settings tab contains 5 sub-tabs in both the legacy and ne | Sub-Tab | Remaining Work | Priority | | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | [Modes / Agents](agent-behaviour/modes-subtab-parity.md) | Core CRUD done (PR #7225). Remaining: when-to-use, system prompt preview, import/export, default variant, hidden/disable, org features | P2 | -| [MCP Servers](agent-behaviour/mcp-server-creation.md) | Add/edit servers, connection status, enable/disable toggle, restart, per-server timeout, expandable detail (tools/resources/logs/auth) | P2 | +| [MCP Servers](agent-behaviour/mcp-server-creation.md) | Add/edit servers, restart, per-server timeout, expandable detail (tools/resources/logs/auth) | P2 | | [Rules & Workflows](agent-behaviour/rules-workflows-subtab-parity.md) | Rules: description text, global/workspace separation, per-rule toggles, new file creation, auto-discovery. Workflows: entire sub-tab is a stub | P2/P3 | | Skills | Minor gaps: project/global separation, mode badge per skill. Covered by [Skills System](non-agent-features/skills-system.md) | P2 | @@ -70,28 +70,19 @@ Open issues from the [GitHub project board](https://github.com/orgs/Kilo-Org/pro ### Features -| Feature | Remaining Work | Priority | -| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------- | -| [File Attachments](features/file-attachments.md) | Add non-image file attachment via button, drag-and-drop, or file picker | P2 | -| [Task Completion Notification](features/task-completion-notification.md) | VS Code toast when task completes or awaits input while panel is hidden | P2 | -| [Custom OpenAI-Compatible Provider UI](features/custom-openai-provider-ui.md) | Port `DialogCustomProvider` from `packages/app` to extension webview | P1 | -| [Remember Last Model Choice](features/remember-last-model.md) | Persist last-used model and pre-select it for new sessions | P2 | -| [Expandable MCP Tools](features/expandable-mcp-tools.md) | Make MCP tool rows expandable to show inputs/outputs like regular tools | P2 | -| [Session Preview Improvements](features/session-preview-improvements.md) | Evaluate showing first message snippet or improving title generation | P2 | - -### Migration (old extension → new) - -| Feature | Remaining Work | Priority | -| --------------------------------------------------------- | ---------------------------------------------------------------------------- | -------- | -| [Settings Migration](migration/settings-migration.md) | Read old extension settings and offer to import into CLI config on first run | P1 | -| [MemoryBank Migration](migration/memorybank-migration.md) | Detect MemoryBank content and offer to migrate it to AGENTS.md | P1 | -| [Upgrade Onboarding](migration/upgrade-onboarding.md) | Show distinct onboarding for upgraders explaining what changed | P1 | +| Feature | Remaining Work | Priority | +| ------------------------------------------------------------------------ | ----------------------------------------------------------------------- | -------- | +| [File Attachments](features/file-attachments.md) | Add non-image file attachment via button, drag-and-drop, or file picker | P2 | +| [Task Completion Notification](features/task-completion-notification.md) | VS Code toast when task completes or awaits input while panel is hidden | P2 | +| [Remember Last Model Choice](features/remember-last-model.md) | Persist last-used model and pre-select it for new sessions | P2 | +| [Expandable MCP Tools](features/expandable-mcp-tools.md) | Make MCP tool rows expandable to show inputs/outputs like regular tools | P2 | +| [Session Preview Improvements](features/session-preview-improvements.md) | Evaluate showing first message snippet or improving title generation | P2 | ### Error Handling & Reliability | Feature | Remaining Work | Priority | | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------- | -| [Pre-Release Switch CPU Spike](error-handling/pre-release-switch-crash.md) | Fix race condition / process conflict when switching release ↔ pre-release | P0 | +| [Pre-Release Switch CPU Spike](error-handling/pre-release-switch-crash.md) | Fix race condition / process conflict when switching release ↔ pre-release | P0 | | [Extension View Doesn't Refresh on Update](error-handling/extension-refresh-on-update.md) | Force webview reload when extension version changes | P1 | | [Propagate CLI Errors to UI](error-handling/propagate-cli-errors-to-ui.md) | Surface CLI stderr errors in chat or as VS Code notifications | P1 | | [CLI Startup Errors](error-handling/cli-startup-errors.md) | Detect CLI process exit before connection; show error with details and retry | P1 | diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 38924ae85c4..233cd852845 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -2,7 +2,7 @@ "name": "kilo-code", "displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete", "description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.", - "version": "7.1.3", + "version": "7.1.11", "icon": "assets/icons/logo-outline-black.png", "galleryBanner": { "color": "#FFFFFF", @@ -17,6 +17,7 @@ "engines": { "vscode": "^1.105.1" }, + "license": "MIT", "author": { "name": "Kilo Code" }, @@ -89,11 +90,6 @@ "title": "History", "icon": "$(history)" }, - { - "command": "kilo-code.new.cloudHistoryButtonClicked", - "title": "Cloud History", - "icon": "$(cloud)" - }, { "command": "kilo-code.new.profileButtonClicked", "title": "Profile", @@ -333,33 +329,28 @@ "when": "view == kilo-code.SidebarProvider" }, { - "command": "kilo-code.new.agentManagerOpen", + "command": "kilo-code.new.historyButtonClicked", "group": "navigation@1", "when": "view == kilo-code.SidebarProvider" }, { - "command": "kilo-code.new.marketplaceButtonClicked", + "command": "kilo-code.new.agentManagerOpen", "group": "navigation@2", "when": "view == kilo-code.SidebarProvider" }, { - "command": "kilo-code.new.cloudHistoryButtonClicked", + "command": "kilo-code.new.marketplaceButtonClicked", "group": "navigation@3", "when": "view == kilo-code.SidebarProvider" }, { - "command": "kilo-code.new.historyButtonClicked", + "command": "kilo-code.new.profileButtonClicked", "group": "navigation@4", "when": "view == kilo-code.SidebarProvider" }, - { - "command": "kilo-code.new.profileButtonClicked", - "group": "navigation@5", - "when": "view == kilo-code.SidebarProvider" - }, { "command": "kilo-code.new.settingsButtonClicked", - "group": "navigation@6", + "group": "navigation@5", "when": "view == kilo-code.SidebarProvider" } ], @@ -753,7 +744,7 @@ }, "scripts": { "prepare:cli-binary": "bun script/local-bin.ts", - "compile": "bun run prepare:cli-binary && bun run rebuild-sdk && bun run typecheck && bun run lint && node esbuild.js", + "compile": "bun run prepare:cli-binary -- --force && bun run rebuild-sdk && bun run typecheck && bun run lint && node esbuild.js", "watch": "bun run rebuild-sdk && bun run --parallel watch:esbuild watch:tsc", "watch:esbuild": "bun run prepare:cli-binary && node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", diff --git a/packages/kilo-vscode/script/build.ts b/packages/kilo-vscode/script/build.ts index cb9b162560c..86eb685dcbc 100644 --- a/packages/kilo-vscode/script/build.ts +++ b/packages/kilo-vscode/script/build.ts @@ -36,9 +36,10 @@ const targets = [ const binDir = join(import.meta.dir, "..", "bin") const distDir = join(import.meta.dir, "..", "dist") const outDir = join(import.meta.dir, "..", "out") +const outDirProd = join(import.meta.dir, "..", "out", "prod") console.log("\n🧹 Cleaning up directories...") -for (const dir of [binDir, distDir, outDir]) { +for (const dir of [binDir, distDir, outDir, outDirProd]) { if (existsSync(dir)) { rmSync(dir, { recursive: true, force: true }) console.log(` ✓ Cleaned ${dir}`) @@ -47,6 +48,7 @@ for (const dir of [binDir, distDir, outDir]) { mkdirSync(outDir, { recursive: true }) mkdirSync(distDir, { recursive: true }) +mkdirSync(outDirProd, { recursive: true }) console.log("\n🔄 Rebuilding SDK types (ensures dist/ is in sync with server API)...") await $`bun run --cwd ${join(import.meta.dir, "..", "..", "sdk", "js")} build` @@ -87,6 +89,13 @@ for (const config of targets) { npm_config_ignore_scripts: "true", }) console.log(` ✅ Created ${vsixPath}`) + + const prodVsixPath = join(outDirProd, `kilo-vscode-${config.target}.vsix`) + await $`vsce package --no-dependencies --skip-license --target ${config.target} -o ${prodVsixPath}`.env({ + ...process.env, + npm_config_ignore_scripts: "true", + }) + console.log(` ✅ Created ${prodVsixPath}`) } console.log("\n✨ All VSIX packages built successfully!") diff --git a/packages/kilo-vscode/script/local-bin.ts b/packages/kilo-vscode/script/local-bin.ts index 7346f98d8f9..ee31c76a666 100644 --- a/packages/kilo-vscode/script/local-bin.ts +++ b/packages/kilo-vscode/script/local-bin.ts @@ -24,11 +24,42 @@ const opencodeDir = join(packagesDir, "opencode") const targetBinDir = join(kiloVscodeDir, "bin") const binName = process.platform === "win32" ? "kilo.exe" : "kilo" const targetBinPath = join(targetBinDir, binName) +const versionFile = join(targetBinDir, ".cli-version") function log(msg: string) { console.log(`[local-bin] ${msg}`) } +async function cliSourceHash(): Promise { + try { + const result = await $`git log -1 --format=%H -- .`.cwd(opencodeDir).quiet() + return result.text().trim() || null + } catch { + return null + } +} + +async function isDirty(): Promise { + try { + const result = await $`git status --porcelain -- .`.cwd(opencodeDir).quiet() + return result.text().trim().length > 0 + } catch { + return false + } +} + +async function isStale(): Promise { + if (await isDirty()) return true + const hash = await cliSourceHash() + if (!hash) return false // can't determine — assume fresh + try { + const stored = (await Bun.file(versionFile).text()).trim() + return stored !== hash + } catch { + return true // no version file — treat as stale + } +} + function platformTag(): string { const os = process.platform === "win32" ? "windows" : process.platform return `cli-${os}-${process.arch}` @@ -114,8 +145,12 @@ async function ensureBuiltBinary(): Promise { async function main() { const targetFile = Bun.file(targetBinPath) + const exists = await targetFile.exists() - if ((await targetFile.exists()) && !forceRebuild) { + const stale = exists && !forceRebuild && (await isStale()) + const rebuild = forceRebuild || stale + + if (exists && !rebuild) { const st = statSync(targetBinPath) log( `CLI binary already present at ${relative(kiloVscodeDir, targetBinPath)} (${Math.round(st.size / 1024 / 1024)}MB). Use --force to rebuild.`, @@ -123,8 +158,8 @@ async function main() { return } - if ((await targetFile.exists()) && forceRebuild) { - log(`Removing existing binary (--force).`) + if (exists && rebuild) { + log(stale ? `CLI source has changed — rebuilding.` : `Removing existing binary (--force).`) rmSync(targetBinPath) // Also remove the prebuilt dist so ensureBuiltBinary() triggers a fresh build const distDir = join(opencodeDir, "dist") @@ -144,6 +179,10 @@ async function main() { await $`cp ${sourceBinPath} ${targetBinPath}` chmodSync(targetBinPath, 0o755) + // Record the CLI source version so future runs detect when a rebuild is needed + const hash = await cliSourceHash() + if (hash) await Bun.write(versionFile, hash + "\n") + log(`Copied CLI binary from ${relative(packagesDir, sourceBinPath)} -> ${relative(kiloVscodeDir, targetBinPath)}`) } diff --git a/packages/kilo-vscode/script/publish.ts b/packages/kilo-vscode/script/publish.ts index 87ce086f029..3a526e86d5a 100755 --- a/packages/kilo-vscode/script/publish.ts +++ b/packages/kilo-vscode/script/publish.ts @@ -7,6 +7,8 @@ import { Script } from "@opencode-ai/script" console.log(`Publishing VSCode extension for release: v${Script.version}`) const outDir = process.env.VSIX_DIR || join(import.meta.dir, "..", "out") +const outDirProd = join(outDir, "prod") + console.log(`Using VSIX directory: ${outDir}`) if (!existsSync(outDir)) { @@ -37,15 +39,14 @@ console.log(`\nFound ${vsixFiles.length} VSIX files`) for (const target of targets) { const vsixPath = join(outDir, `kilo-vscode-${target}.vsix`) - console.log(`\n🚀 Publishing ${target} to VS Code Marketplace...`) await $`vsce publish --pre-release --packagePath ${vsixPath}` console.log(` ✅ Published ${target} to VS Code Marketplace`) - // Note: Open VSX publishing is commented out as it doesn't support prereleases - // console.log(`\n📤 Publishing ${target} to Open VSX...`) - // await $`npx ovsx publish ${vsixPath} --target ${target} -p ${process.env.OPENVSX_TOKEN}` - // console.log(` ✅ Published ${target} to Open VSX`) + const prodVsixPath = join(outDirProd, `kilo-vscode-${target}.vsix`) + console.log(`\n📤 Publishing ${target} to Open VSX...`) + await $`npx ovsx publish --pat ${process.env.OPENVSX_TOKEN} --packagePath ${prodVsixPath}` + console.log(` ✅ Published Prod ${target} to Open VSX`) } if (Script.release) { diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 9439e6fb265..c6e1a046da1 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -28,8 +28,13 @@ import { isEventFromForeignProject, loadSessions as loadSessionsUtil, flushPendingSessionRefresh as flushPendingSessionRefreshUtil, + resolveContextDirectory, + resolveWorkspaceDirectory, type SessionRefreshContext, } from "./kilo-provider-utils" +import { GitOps } from "./agent-manager/GitOps" +import { GitStatsPoller, type LocalStats } from "./agent-manager/GitStatsPoller" +import { getWorkspaceRoot } from "./review-utils" import { MarketplaceService } from "./services/marketplace" import { resolveProjectDirectory } from "./project-directory" import { getBusySessionCount, seedSessionStatuses } from "./session-status" @@ -75,6 +80,7 @@ import { disconnectProvider as disconnectProviderAction, saveCustomProvider as saveCustomProviderAction, } from "./provider-actions" +import { fetchOpenAIModels, FetchModelsError } from "./shared/fetch-models" type KiloProviderOptions = { projectDirectory?: string | null @@ -86,6 +92,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private webview: vscode.Webview | null = null private currentSession: Session | null = null + /** Remembers the last selected session so /new can stay in the same worktree after clearSession. */ + private contextSessionID: string | undefined private connectionState: "connecting" | "connected" | "disconnected" | "error" = "connecting" private loginAttempt = 0 private isWebviewReady = false @@ -105,6 +113,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private cachedCommandsMessage: unknown = null /** Cached configLoaded payload so requestConfig can be served before client is ready */ private cachedConfigMessage: unknown = null + /** Cached mcpStatusLoaded payload so requestMcpStatus can be served before client is ready */ + private cachedMcpStatusMessage: unknown = null /** Ref-count of in-flight handleUpdateConfig calls; prevents fetchAndSendConfig from sending stale data */ private pending = 0 /** Cached notificationsLoaded payload */ @@ -133,6 +143,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private unsubscribeNotificationDismiss: (() => void) | null = null private unsubscribeLanguageChange: (() => void) | null = null private unsubscribeProfileChange: (() => void) | null = null + private unsubscribeMigrationComplete: (() => void) | null = null // legacy-migration private initConnectionPromise: Promise | null = null private webviewMessageDisposable: vscode.Disposable | null = null @@ -143,11 +154,19 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private chatAutocomplete: ChatTextAreaAutocomplete | null = null private projectDirectory: string | null | undefined private slimEditMetadata = true + /** Worktree diff stats poller for the sidebar badge — reuses GitStatsPoller (local stats only) */ + private statsPoller: GitStatsPoller | null = null + private cachedStats: unknown = null /** Optional interceptor called before the standard message handler. * Return null to consume the message, or return a (possibly transformed) message. */ private onBeforeMessage: ((msg: Record) => Promise | null>) | null = null + /** Handler for "Continue in Worktree" — set by extension.ts to delegate to AgentManagerProvider. */ + private continueInWorktreeHandler: + | ((sessionId: string, progress: (status: string, detail?: string, error?: string) => void) => Promise) + | null = null + constructor( private readonly extensionUri: vscode.Uri, private readonly connectionService: KiloConnectionService, @@ -257,17 +276,27 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper data: profileData, }) + // Re-send cached worktree stats so the badge renders immediately after webview reload. + if (this.cachedStats) this.postMessage(this.cachedStats) + // Seed session status map so the Settings panel knows about already-running sessions. // Must run after webview is ready (postMessage is a no-op before that). - void this.seedSessionStatusMap() + // Only reconcile (reset missing busy→idle) when the map is empty, i.e. + // on the very first seed before any real-time SSE events have arrived. + // On SSE reconnects or webview recreations the live SSE data is + // authoritative and reconciliation risks race-resetting busy sessions. + const reconcile = this.sessionStatusMap.size === 0 + void this.seedSessionStatusMap(reconcile) } // legacy-migration start - // Only show the migration wizard once the CLI connection is established so the - // webview has finished loading providers/agents before we navigate to the wizard. - if (reason === "webviewReady" && this.connectionState === "connected") { - void checkAndShowMigrationWizard(this.migrationCtx) - } else if (reason === "sse-connected") { + // Show the migration wizard once the CLI connection is established. + // Three triggers cover all timing scenarios: + // "webviewReady" + connected — webview loaded after SSE was already up + // "sse-connected" — SSE connected after webview was ready + // "initializeConnection" — sidebar path where connect() resolves before + // onStateChange is subscribed, so sse-connected never fires + if (this.connectionState === "connected") { void checkAndShowMigrationWizard(this.migrationCtx) } // legacy-migration end @@ -294,6 +323,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper // Handle messages from webview (shared handler) this.setupWebviewMessageHandler(webviewView.webview) + // Pause stats polling when sidebar is hidden, resume when visible + webviewView.onDidChangeVisibility(() => { + this.statsPoller?.setEnabled(webviewView.visible) + }) + // Initialize connection to CLI backend this.initializeConnection() } @@ -325,6 +359,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper */ public registerSession(session: Session): void { this.currentSession = session + this.contextSessionID = session.id this.trackedSessionIds.add(session.id) this.postMessage({ type: "sessionCreated", @@ -374,6 +409,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.postMessage({ type: "openCloudSession", sessionId }) } + /** Register the handler for "Continue in Worktree" messages from the sidebar. */ + public setContinueInWorktreeHandler( + handler: (sessionId: string, progress: (status: string, detail?: string, error?: string) => void) => Promise, + ): void { + this.continueInWorktreeHandler = handler + } + /** * Attach to a webview that already has its own HTML set. * Sets up message handling and connection without overriding HTML content. @@ -494,6 +536,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper await this.handleCreateSession() break case "clearSession": + this.contextSessionID = this.currentSession?.id ?? this.contextSessionID this.currentSession = null break case "loadMessages": @@ -537,9 +580,33 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "openSettingsPanel": vscode.commands.executeCommand("kilo-code.new.settingsButtonClicked", message.tab) break + case "openMarketplacePanel": + vscode.commands.executeCommand("kilo-code.new.marketplaceButtonClicked", this.projectDirectory) + break case "openChanges": vscode.commands.executeCommand("kilo-code.new.showChanges") break + case "continueInWorktree": + if (message.sessionId && this.continueInWorktreeHandler) { + this.continueInWorktreeHandler(message.sessionId, (status: string, detail?: string, error?: string) => { + this.postMessage({ type: "continueInWorktreeProgress", status, detail, error }) + }).catch((err: unknown) => { + console.error("[Kilo New] continueInWorktree failed:", err) + this.postMessage({ + type: "continueInWorktreeProgress", + status: "error", + error: err instanceof Error ? err.message : String(err), + }) + }) + } else if (message.sessionId) { + console.error("[Kilo New] continueInWorktree: no handler registered") + this.postMessage({ + type: "continueInWorktreeProgress", + status: "error", + error: "Continue in Worktree is not available", + }) + } + break case "retryConnection": console.log("[Kilo New] KiloProvider: 🔄 Retrying connection...") this.initializeConnection().catch((e) => @@ -567,6 +634,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "saveCustomProvider": await this.handleProviderAction(message) break + case "fetchCustomProviderModels": + this.handleFetchCustomProviderModels(message).catch((e) => + console.error("[Kilo New] fetchCustomProviderModels failed:", e), + ) + break case "compact": await this.handleCompact(message.sessionID, message.providerID, message.modelID) break @@ -590,6 +662,17 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "removeMcp": this.handleRemoveMcp(message.name).catch((e) => console.error("[Kilo New] handleRemoveMcp failed:", e)) break + case "requestMcpStatus": + this.fetchAndSendMcpStatus().catch((e) => console.error("[Kilo New] fetchAndSendMcpStatus failed:", e)) + break + case "connectMcp": + this.handleConnectMcp(message.name).catch((e) => console.error("[Kilo New] handleConnectMcp failed:", e)) + break + case "disconnectMcp": + this.handleDisconnectMcp(message.name).catch((e) => + console.error("[Kilo New] handleDisconnectMcp failed:", e), + ) + break case "questionReply": await handleQuestionReply(this.questionCtx, message.requestID, message.answers) @@ -600,6 +683,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "requestConfig": this.fetchAndSendConfig().catch((e) => console.error("[Kilo New] fetchAndSendConfig failed:", e)) break + case "requestGlobalConfig": + this.fetchAndSendGlobalConfig().catch((e) => console.error("[Kilo New] fetchAndSendGlobalConfig failed:", e)) + break case "updateConfig": await this.handleUpdateConfig(message.config) break @@ -940,6 +1026,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.postMessage({ type: "profileData", data }) }) + // legacy-migration start + // Subscribe to migration-complete broadcast from any KiloProvider instance + this.unsubscribeMigrationComplete = this.connectionService.onMigrationComplete(() => { + this.postMessage({ type: "migrationState", needed: false }) + }) + // legacy-migration end + // Get current state and push to webview const serverInfo = this.connectionService.getServerInfo() this.connectionState = this.connectionService.getConnectionState() @@ -972,6 +1065,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper ]) this.sendNotificationSettings() + // Start polling worktree diff stats for the sidebar badge + this.startStatsPolling() + console.log("[Kilo New] KiloProvider: ✅ initializeConnection completed successfully") } catch (error) { console.error("[Kilo New] KiloProvider: ❌ Failed to initialize connection:", error) @@ -1005,9 +1101,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } try { - const workspaceDir = this.getWorkspaceDirectory() + const workspaceDir = this.getContextDirectory() const { data: session } = await this.client.session.create({ directory: workspaceDir }, { throwOnError: true }) this.currentSession = session + this.contextSessionID = session.id + this.trackDirectory(session.id, workspaceDir) this.trackedSessionIds.add(session.id) // Notify webview of the new session @@ -1030,6 +1128,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private async handleLoadMessages(sessionID: string): Promise { // Track the session so we receive its SSE events this.trackedSessionIds.add(sessionID) + this.contextSessionID = sessionID if (!this.client) { this.postMessage({ @@ -1068,6 +1167,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper .then((result) => { if (result.data && !abort.signal.aborted) { this.currentSession = result.data + this.contextSessionID = result.data.id } }) .catch((err: unknown) => console.warn("[Kilo New] KiloProvider: getSession failed (non-critical):", err)) @@ -1386,6 +1486,22 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return saveCustomProviderAction(ctx, rid, pid, config, key, this.cachedConfigMessage, set) } + private async handleFetchCustomProviderModels(msg: Record): Promise { + const rid = typeof msg.requestId === "string" ? msg.requestId : "" + const url = typeof msg.baseURL === "string" ? msg.baseURL : "" + if (!rid || !url) return + const key = typeof msg.apiKey === "string" ? msg.apiKey : undefined + const headers = msg.headers && typeof msg.headers === "object" ? (msg.headers as Record) : undefined + try { + const models = await fetchOpenAIModels({ baseURL: url, apiKey: key, headers }) + this.postMessage({ type: "customProviderModelsFetched", requestId: rid, models }) + } catch (err: unknown) { + const message = err instanceof Error ? err.message : "Failed to fetch models" + const auth = err instanceof FetchModelsError && err.auth + this.postMessage({ type: "customProviderModelsFetched", requestId: rid, error: message, auth }) + } + } + /** * Fetch agents (modes) from the backend and send to webview. */ @@ -1412,6 +1528,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper mode: a.mode, native: a.native, color: a.color, + deprecated: a.deprecated, })), defaultAgent, } @@ -1575,6 +1692,51 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + private async fetchAndSendMcpStatus(): Promise { + if (!this.client) { + if (this.cachedMcpStatusMessage) { + this.postMessage(this.cachedMcpStatusMessage) + } + return + } + + try { + const directory = this.getWorkspaceDirectory() + const { data } = await this.client.mcp.status({ directory }) + if (data) { + const message = { type: "mcpStatusLoaded", status: data } + this.cachedMcpStatusMessage = message + this.postMessage(message) + } + } catch (error) { + console.error("[Kilo New] KiloProvider: Failed to fetch MCP status:", error) + } + } + + private async handleConnectMcp(name: string): Promise { + if (!this.client) return + try { + const directory = this.getWorkspaceDirectory() + await this.client.mcp.connect({ name, directory }) + await this.fetchAndSendMcpStatus() + } catch (error) { + console.error("[Kilo New] KiloProvider: Failed to connect MCP:", name, error) + await this.fetchAndSendMcpStatus() + } + } + + private async handleDisconnectMcp(name: string): Promise { + if (!this.client) return + try { + const directory = this.getWorkspaceDirectory() + await this.client.mcp.disconnect({ name, directory }) + await this.fetchAndSendMcpStatus() + } catch (error) { + console.error("[Kilo New] KiloProvider: Failed to disconnect MCP:", name, error) + await this.fetchAndSendMcpStatus() + } + } + /** * Dispose the CLI backend instance so it re-reads config from disk. * Call after any marketplace install/remove that writes config files directly. @@ -1664,15 +1826,30 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + /** Fetch global-only config (no project/managed layers) for settings export. */ + private async fetchAndSendGlobalConfig(): Promise { + if (!this.client || this.connectionState !== "connected") return + try { + const { data: config } = await this.client.global.config.get({ throwOnError: true }) + this.postMessage({ type: "globalConfigLoaded", config }) + } catch (error) { + console.error("[Kilo New] KiloProvider: Failed to fetch global config:", error) + } + } + /** * Seed sessionStatusMap with current session statuses on connect. * Without this, the Settings panel (which has no tracked sessions) would see * busyCount() = 0 for sessions that were already running before it opened. + * + * @param reconcile When true, reset locally-busy sessions absent from the + * server response to idle (crash recovery). Set to false on SSE reconnects + * to avoid a race where a brief HTTP fetch gap causes the spinner to vanish. */ - private async seedSessionStatusMap(): Promise { + private async seedSessionStatusMap(reconcile = true): Promise { if (!this.client || this.connectionState !== "connected") return const dir = this.getWorkspaceDirectory() - await seedSessionStatuses(this.client, dir, this.sessionStatusMap, (msg) => this.postMessage(msg)) + await seedSessionStatuses(this.client, dir, this.sessionStatusMap, (msg) => this.postMessage(msg), reconcile) } /** @@ -1855,11 +2032,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private async resolveSession(sessionID?: string): Promise<{ sid: string; dir: string } | undefined> { if (!this.client) return undefined - const dir = this.getWorkspaceDirectory(sessionID || this.currentSession?.id) + const dir = sessionID ? this.getWorkspaceDirectory(sessionID) : this.getContextDirectory() if (!sessionID && !this.currentSession) { const { data: session } = await this.client.session.create({ directory: dir }, { throwOnError: true }) this.currentSession = session + this.contextSessionID = session.id + this.trackDirectory(session.id, dir) this.trackedSessionIds.add(session.id) this.postMessage({ type: "sessionCreated", @@ -2119,6 +2298,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper }, set currentSession(session) { self.currentSession = session + if (session) self.contextSessionID = session.id }, trackedSessionIds: this.trackedSessionIds, connectionService: this.connectionService, @@ -2259,15 +2439,29 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper if (!key.startsWith(prefix)) continue const parts = key.split(".") const section = parts.slice(0, -1).join(".") - const leaf = parts[parts.length - 1] + const leaf = parts[parts.length - 1]! const config = vscode.workspace.getConfiguration(section) await config.update(leaf, undefined, vscode.ConfigurationTarget.Global) } + // Clear globalState items that are not part of the configuration + await this.extensionContext?.globalState.update("variantSelections", undefined) + await this.extensionContext?.globalState.update("recentModels", undefined) + await this.extensionContext?.globalState.update("kilo.dismissedNotificationIds", undefined) + // Re-send all settings to the webview so the UI reflects the reset this.sendAutocompleteSettings() this.sendBrowserSettings() this.sendNotificationSettings() + + // Re-send globalState items to the webview + this.postMessage({ type: "variantsLoaded", variants: {} }) + this.postMessage({ type: "recentsLoaded", recents: [] }) + + // Re-fetch notifications to reflect cleared dismissed IDs + await this.fetchAndSendNotifications() + + vscode.window.showInformationMessage("Kilo Code settings have been reset to defaults.") } /** @@ -2358,10 +2552,12 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper // Side effects that must happen before the webview message is sent if (event.type === "session.created" && !this.currentSession) { this.currentSession = event.properties.info + this.contextSessionID = event.properties.info.id this.trackedSessionIds.add(event.properties.info.id) } if (event.type === "session.updated" && this.currentSession?.id === event.properties.info.id) { this.currentSession = event.properties.info + this.contextSessionID = event.properties.info.id } const msg = mapSSEEventToWebviewMessage(event, sessionID) @@ -2545,17 +2741,38 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper * Checks session directory overrides first (e.g., worktree paths), then falls back to workspace root. */ private getWorkspaceDirectory(sessionId?: string): string { - if (sessionId) { - const dir = this.sessionDirectories.get(sessionId) - if (dir) return dir - } + return resolveWorkspaceDirectory({ + sessionID: sessionId, + sessionDirectories: this.sessionDirectories, + workspaceDirectory: this.getRootDirectory(), + }) + } + + private getContextDirectory(): string { + return resolveContextDirectory({ + currentSessionID: this.currentSession?.id, + contextSessionID: this.contextSessionID, + sessionDirectories: this.sessionDirectories, + workspaceDirectory: this.getRootDirectory(), + }) + } + + private getRootDirectory(): string { const workspaceFolders = vscode.workspace.workspaceFolders if (workspaceFolders && workspaceFolders.length > 0) { - return workspaceFolders[0].uri.fsPath + return workspaceFolders[0]!.uri.fsPath } return process.cwd() } + private trackDirectory(sessionId: string, dir: string) { + if (path.resolve(dir) === path.resolve(this.getRootDirectory())) { + this.sessionDirectories.delete(sessionId) + return + } + this.sessionDirectories.set(sessionId, dir) + } + private getProjectDirectory(sessionId?: string): string | undefined { return resolveProjectDirectory(this.projectDirectory, () => this.getWorkspaceDirectory(sessionId)) } @@ -2592,7 +2809,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper set migrationCheckInFlight(val) { self.migrationCheckInFlight = val }, + refreshSessions: () => this.refreshSessions(), disposeGlobal: () => this.disposeGlobal(), + broadcastComplete: () => this.connectionService.notifyMigrationComplete(), } } @@ -2604,16 +2823,44 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return this.marketplace } + // ── Worktree stats polling (sidebar diff badge) ────────────────── + + private startStatsPolling(): void { + this.statsPoller?.stop() + const git = new GitOps({ log: () => {} }) + this.statsPoller = new GitStatsPoller({ + getWorktrees: () => [], + getWorkspaceRoot: () => getWorkspaceRoot(), + getClient: () => this.connectionService.getClient(), + git, + onStats: () => {}, + onLocalStats: (stats: LocalStats) => { + const msg = { + type: "worktreeStatsLoaded" as const, + files: stats.files, + additions: stats.additions, + deletions: stats.deletions, + } + this.cachedStats = msg + this.postMessage(msg) + }, + log: () => {}, + }) + this.statsPoller.setEnabled(true) + } + /** * Dispose of the provider and clean up subscriptions. * Does NOT kill the server — that's the connection service's job. */ dispose(): void { + this.statsPoller?.stop() this.unsubscribeEvent?.() this.unsubscribeState?.() this.unsubscribeNotificationDismiss?.() this.unsubscribeLanguageChange?.() this.unsubscribeProfileChange?.() + this.unsubscribeMigrationComplete?.() this.webviewMessageDisposable?.dispose() this.trackedSessionIds.clear() this.syncedChildSessions.clear() diff --git a/packages/kilo-vscode/src/SettingsEditorProvider.ts b/packages/kilo-vscode/src/SettingsEditorProvider.ts index afaa819944b..922754e319f 100644 --- a/packages/kilo-vscode/src/SettingsEditorProvider.ts +++ b/packages/kilo-vscode/src/SettingsEditorProvider.ts @@ -51,10 +51,10 @@ export class SettingsEditorProvider implements vscode.Disposable { return view } - openPanel(view: PanelView, tab?: string): void { + openPanel(view: PanelView, tab?: string, directory?: string | null): void { if (tab) this.tabs.set(view, tab) - const projectDirectory = this.getProjectDirectory() + const projectDirectory = directory ?? this.getProjectDirectory() const existing = this.panels.get(view) if (existing) { this.providers.get(view)?.setProjectDirectory(projectDirectory) diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index fb2c2658d2b..b463e7682da 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -18,6 +18,7 @@ import { SessionTerminalManager } from "./SessionTerminalManager" import { createTerminalHost } from "./terminal-host" import { executeVscodeTask } from "./task-runner" import { forkSession } from "./fork-session" +import { continueInWorktree } from "./continue-in-worktree" import { shouldStopDiffPolling } from "./delete-worktree" import { buildKeybindingMap } from "./format-keybinding" import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version" @@ -103,6 +104,7 @@ export class AgentManagerProvider implements Disposable { if (this.panel) { this.log("Panel already open, revealing") this.panel.reveal() + this.postToWebview({ type: "action", action: "focusInput" }) return } this.log("Opening Agent Manager panel") @@ -118,6 +120,11 @@ export class AgentManagerProvider implements Disposable { /** Restore the Agent Manager panel from a previously serialized state. * The caller (extension.ts / vscode-host.ts) wraps the raw panel before passing it. */ public deserializePanel(ctx: PanelContext): void { + if (this.panel) { + this.log("Panel already exists during deserialization, disposing duplicate") + ctx.dispose() + return + } this.log("Deserializing Agent Manager panel") this.attachPanel(ctx) } @@ -129,6 +136,11 @@ export class AgentManagerProvider implements Disposable { /** Wire up a panel context (shared by openPanel and deserializePanel). */ private attachPanel(ctx: PanelContext): void { + if (this.panel) { + this.log("Disposing previous panel before attaching new one") + this.panel.dispose() + this.panel = undefined + } this.panel = ctx this.stateReady = this.initializeState() @@ -136,11 +148,15 @@ export class AgentManagerProvider implements Disposable { this.sendKeybindings() ctx.onDidDispose(() => { - this.log("Panel disposed") - this.statsPoller.stop() - this.stopDiffPolling() + // Only clear if this is still the active panel — a newer panel may + // have already replaced us via attachPanel. + if (this.panel === ctx) { + this.log("Panel disposed") + this.statsPoller.stop() + this.stopDiffPolling() + this.panel = undefined + } ctx.sessions.dispose() - this.panel = undefined }) } @@ -192,6 +208,17 @@ export class AgentManagerProvider implements Disposable { if (m.type === "agentManager.deleteWorktree") return this.onDeleteWorktree(m.worktreeId) if (m.type === "agentManager.removeStaleWorktree") return this.onRemoveStaleWorktree(m.worktreeId) if (m.type === "agentManager.promoteSession") return this.onPromoteSession(m.sessionId) + if (m.type === "agentManager.openLocally") { + if (!this.panel) return null + this.panel.sessions.clearSessionDirectory(m.sessionId) + return null + } + if (m.type === "continueInWorktree") { + void this.continueFromSidebar(m.sessionId, (status, detail, error) => { + this.panel?.postMessage({ type: "continueInWorktreeProgress", status, detail, error }) + }) + return null + } if (m.type === "agentManager.addSessionToWorktree") return this.onAddSessionToWorktree(m.worktreeId) if (m.type === "agentManager.forkSession") return this.onForkSession(m.sessionId, m.worktreeId) if (m.type === "agentManager.closeSession") return this.onCloseSession(m.sessionId) @@ -1795,6 +1822,7 @@ export class AgentManagerProvider implements Disposable { public focusPanel(): void { if (!this.panel) return this.panel.reveal(false) + this.postToWebview({ type: "action", action: "focusInput" }) } public isActive(): boolean { @@ -1806,6 +1834,42 @@ export class AgentManagerProvider implements Disposable { return this.panel?.sessions.getSessionDirectories() ?? new Map() } + /** + * Continue a sidebar session in a new worktree. + * Captures git state, creates worktree, applies state, forks session. + * Called from KiloProvider when the sidebar sends "continueInWorktree". + */ + public async continueFromSidebar( + sessionId: string, + progress: (status: string, detail?: string, error?: string) => void, + ): Promise { + const root = this.getRoot() + if (!root) { + progress("error", undefined, "No workspace folder open") + return + } + + this.openPanel() + await this.waitForStateReady("continueFromSidebar") + + await continueInWorktree( + { + root, + getClient: () => this.connectionService.getClient(), + createWorktreeOnDisk: (opts) => this.createWorktreeOnDisk(opts), + runSetupScript: (p, b, id) => this.runSetupScriptForWorktree(p, b, id), + getStateManager: () => this.getStateManager(), + registerWorktreeSession: (sid, dir) => this.registerWorktreeSession(sid, dir), + registerSession: (session) => this.panel?.sessions.registerSession(session), + notifyReady: (sid, result, wid) => this.notifyWorktreeReady(sid, result, wid), + capture: (event, props) => this.host.capture(event, props), + log: (...args) => this.log(...args), + }, + sessionId, + progress, + ) + } + public postMessage(message: unknown): void { this.panel?.postMessage(message) } diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts index a95226ffdab..899d7471c94 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts @@ -10,8 +10,6 @@ import * as path from "path" import * as fs from "fs" import { randomUUID } from "crypto" import simpleGit, { type SimpleGit } from "simple-git" - -const TEMP_PREFIX = ".kilo-delete-" import { generateBranchName, sanitizeBranchName } from "./branch-name" import type { GitOps } from "./GitOps" import { execWithShellEnv } from "./shell-env" @@ -29,6 +27,9 @@ import { type BranchListItem, } from "./git-import" +const TEMP_PREFIX = ".kilo-delete-" +const RM_OPTS: fs.RmOptions = { recursive: true, force: true, maxRetries: 3, retryDelay: 200 } + interface WorktreeInfo { branch: string path: string @@ -388,7 +389,7 @@ export class WorktreeManager { if (branch) await this.deleteBranch(branch) // 4. Background delete — fire-and-forget, cross-platform - fs.promises.rm(temp, { recursive: true, force: true }).catch((err) => { + fs.promises.rm(temp, RM_OPTS).catch((err) => { this.log(`Background cleanup failed for ${temp}: ${err}`) }) } @@ -411,7 +412,7 @@ export class WorktreeManager { for (const e of entries) { if (e.isDirectory() && e.name.startsWith(TEMP_PREFIX)) { const stale = path.join(this.dir, e.name) - fs.promises.rm(stale, { recursive: true, force: true }).catch((err) => { + fs.promises.rm(stale, RM_OPTS).catch((err) => { this.log(`Failed to clean orphaned temp dir ${stale}: ${err}`) }) } diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts index 695af8899b3..6dbaee91edb 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts @@ -318,8 +318,11 @@ export class WorktreeStateManager { this.reviewDiffStyle = "unified" for (const [id, wt] of Object.entries(data.worktrees ?? {})) { - // Rewrite stale .kilocode/ paths (handles both Unix / and Windows \ separators) - const fixed = wt.path?.replace(/[/\\]\.kilocode[/\\]/g, `${path.sep}.kilo${path.sep}`) ?? wt.path + // Rewrite stale .kilocode paths while preserving the separator style already stored. + const fixed = + wt.path?.replace(/([/\\])\.kilocode([/\\])/g, (_match, leadingSep, trailingSep) => { + return `${leadingSep}.kilo${trailingSep}` + }) ?? wt.path this.worktrees.set(id, { id, ...wt, path: fixed }) } for (const [id, s] of Object.entries(data.sessions ?? {})) { diff --git a/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts new file mode 100644 index 00000000000..30e0168ba97 --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts @@ -0,0 +1,139 @@ +import type { KiloClient, Session } from "@kilocode/sdk/v2/client" +import type { CreateWorktreeResult } from "./WorktreeManager" +import type { WorktreeStateManager } from "./WorktreeStateManager" +import { capture as captureGitState, apply as applyGitState, type GitSnapshot } from "./git-transfer" +import { getErrorMessage } from "../kilo-provider-utils" +import { PLATFORM } from "./constants" + +export interface ContinueContext { + root: string + getClient: () => KiloClient + createWorktreeOnDisk: (opts: { baseBranch: string }) => Promise<{ + worktree: { id: string } + result: CreateWorktreeResult + } | null> + runSetupScript: (path: string, branch: string, worktreeId: string) => Promise + getStateManager: () => WorktreeStateManager | undefined + registerWorktreeSession: (sessionId: string, directory: string) => void + registerSession: (session: Session) => void + notifyReady: (sessionId: string, result: CreateWorktreeResult, worktreeId: string) => void + capture: (event: string, props: Record) => void + log: (...args: unknown[]) => void +} + +/** Result type for each step — either success with a value or an error string. */ +export type StepResult = { ok: true; value: T } | { ok: false; error: string } + +/** Abort a running session. Best-effort — failures are logged but not fatal. */ +export async function abortSession(ctx: ContinueContext, sessionId: string): Promise { + try { + const client = ctx.getClient() + await client.session.abort({ sessionID: sessionId }).catch((err) => { + ctx.log("Session abort failed (may already be idle):", getErrorMessage(err)) + }) + } catch (err) { + ctx.log("Client not available for abort, continuing:", getErrorMessage(err)) + } +} + +/** Capture git state from the workspace root. */ +export async function captureState(ctx: ContinueContext): Promise> { + try { + const snapshot = await captureGitState(ctx.root, (...args) => ctx.log(...args)) + return { ok: true, value: snapshot } + } catch (err) { + return { ok: false, error: `Failed to capture git state: ${getErrorMessage(err)}` } + } +} + +/** Create a worktree and run the setup script. */ +export async function prepareWorktree( + ctx: ContinueContext, + branch: string, +): Promise> { + const created = await ctx.createWorktreeOnDisk({ baseBranch: branch }) + if (!created) return { ok: false, error: "Failed to create worktree" } + await ctx.runSetupScript(created.result.path, created.result.branch, created.worktree.id) + return { ok: true, value: { worktreeId: created.worktree.id, result: created.result } } +} + +/** Apply a git snapshot to a worktree directory. */ +export async function transferState( + ctx: ContinueContext, + snapshot: GitSnapshot, + target: string, +): Promise> { + const applied = await applyGitState(snapshot, target, (...args) => ctx.log(...args)) + if (!applied.ok) { + ctx.log("Git state transfer failed:", applied.error) + return { ok: false, error: applied.error ?? "Failed to apply changes to worktree" } + } + return { ok: true, value: undefined } +} + +/** Fork the session into the worktree directory. */ +export async function forkSession(ctx: ContinueContext, sessionId: string, dir: string): Promise> { + let client: KiloClient + try { + client = ctx.getClient() + } catch (err) { + ctx.log("Client not available for session fork:", getErrorMessage(err)) + return { ok: false, error: "Not connected to CLI backend" } + } + try { + const { data } = await client.session.fork({ sessionID: sessionId, directory: dir }, { throwOnError: true }) + return { ok: true, value: data } + } catch (err) { + return { ok: false, error: `Failed to fork session: ${getErrorMessage(err)}` } + } +} + +/** Register the forked session in state and emit telemetry. */ +export function registerSession( + ctx: ContinueContext, + session: Session, + result: CreateWorktreeResult, + worktreeId: string, + sourceId: string, +): void { + const state = ctx.getStateManager() + if (state) state.addSession(session.id, worktreeId) + ctx.registerWorktreeSession(session.id, result.path) + ctx.registerSession(session) + ctx.notifyReady(session.id, result, worktreeId) + ctx.capture("Continue in Worktree", { source: PLATFORM, sessionId: session.id, worktreeId }) + ctx.log(`Continued sidebar session ${sourceId} → worktree ${worktreeId} (session ${session.id})`) +} + +/** + * Continue a sidebar session in a new worktree. + * Orchestrates the atomic steps: abort → capture → prepare → transfer → fork → register. + * + * Pure orchestration — no vscode imports. + */ +export async function continueInWorktree( + ctx: ContinueContext, + sessionId: string, + progress: (status: string, detail?: string, error?: string) => void, +): Promise { + await abortSession(ctx, sessionId) + + progress("capturing", "Capturing git changes...") + const captured = await captureState(ctx) + if (!captured.ok) return progress("error", undefined, captured.error) + + progress("creating", "Creating worktree...") + const prepared = await prepareWorktree(ctx, captured.value.branch) + if (!prepared.ok) return progress("error", undefined, prepared.error) + + progress("transferring", "Transferring changes...") + const transferred = await transferState(ctx, captured.value, prepared.value.result.path) + if (!transferred.ok) return progress("error", undefined, transferred.error) + + progress("forking", "Starting session...") + const forked = await forkSession(ctx, sessionId, prepared.value.result.path) + if (!forked.ok) return progress("error", undefined, forked.error) + + registerSession(ctx, forked.value, prepared.value.result, prepared.value.worktreeId, sessionId) + progress("done") +} diff --git a/packages/kilo-vscode/src/agent-manager/git-transfer.ts b/packages/kilo-vscode/src/agent-manager/git-transfer.ts new file mode 100644 index 00000000000..54916204c7f --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/git-transfer.ts @@ -0,0 +1,161 @@ +import * as nodePath from "path" +import * as fs from "fs/promises" +import * as cp from "child_process" + +/** + * Portable git state snapshot — captures uncommitted changes as patches + * that can be applied to any directory on the same commit. + * + * Used by "Continue in Worktree" to copy git state from the user's + * working tree into a fresh worktree without modifying the source. + */ +export interface GitSnapshot { + branch: string + head: string + /** Binary-safe unified diff of unstaged changes, or null if clean. */ + unstaged: string | null + /** Binary-safe unified diff of staged changes, or null if none staged. */ + staged: string | null + /** Untracked files (new files not yet added to git). */ + untracked: UntrackedFile[] +} + +export interface UntrackedFile { + /** Relative path from repo root. */ + path: string + /** Raw file content. */ + content: Buffer +} + +const MAX_FILE = 10 * 1024 * 1024 // 10 MB + +function git(args: string[], cwd: string, stdin?: string): Promise<{ code: number; stdout: string; stderr: string }> { + return new Promise((resolve) => { + if (stdin !== undefined) { + // Use spawn for stdin piping — execFile doesn't reliably create a stdin pipe + const child = cp.spawn("git", args, { cwd, windowsHide: true }) + let stdout = "" + let stderr = "" + child.stdout.on("data", (d: Buffer) => (stdout += d.toString())) + child.stderr.on("data", (d: Buffer) => (stderr += d.toString())) + child.on("close", (code) => resolve({ code: code ?? 1, stdout, stderr })) + child.stdin.end(stdin) + } else { + cp.execFile( + "git", + args, + { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, windowsHide: true }, + (error, stdout, stderr) => { + if (!error) { + resolve({ code: 0, stdout, stderr }) + return + } + const exec = error as cp.ExecException + resolve({ code: typeof exec.code === "number" ? exec.code : 1, stdout: stdout ?? "", stderr: stderr ?? "" }) + }, + ) + } + }) +} + +async function raw(args: string[], cwd: string): Promise { + const result = await git(args, cwd) + return result.stdout.trim() +} + +/** + * Capture the current git state from `cwd` as a portable snapshot. + * This is a read-only operation — the source directory is never modified. + */ +export async function capture(cwd: string, log: (...args: unknown[]) => void): Promise { + const patch = (args: string[]) => + git(args, cwd).then((r) => { + const out = r.stdout + return out.trim() ? out : null + }) + + const [branch, head, unstaged, staged, untrackedRaw] = await Promise.all([ + raw(["branch", "--show-current"], cwd), + raw(["rev-parse", "HEAD"], cwd), + patch(["diff", "--binary"]), + patch(["diff", "--cached", "--binary"]), + raw(["ls-files", "--others", "--exclude-standard"], cwd).then((s: string) => + s.split("\n").filter((l: string) => l.length > 0), + ), + ]) + + const untracked: UntrackedFile[] = [] + for (const rel of untrackedRaw) { + const full = nodePath.resolve(cwd, rel) + try { + const stat = await fs.stat(full) + if (stat.size > MAX_FILE) { + log(`Skipping untracked file ${rel}: ${(stat.size / 1024 / 1024).toFixed(1)} MB exceeds limit`) + continue + } + const content = await fs.readFile(full) + untracked.push({ path: rel, content }) + } catch (err) { + log(`Failed to read untracked file ${rel}:`, err) + } + } + + return { branch, head, unstaged, staged, untracked } +} + +/** + * Apply a git snapshot to a target directory. + * Applies staged changes (and re-stages them), unstaged changes, and writes untracked files. + */ +export async function apply( + snapshot: GitSnapshot, + target: string, + log: (...args: unknown[]) => void, +): Promise<{ ok: boolean; error?: string }> { + // Apply staged patch first, then re-stage those files + if (snapshot.staged) { + const result = await git(["apply", "--whitespace=nowarn", "-"], target, snapshot.staged) + if (result.code !== 0) { + const msg = result.stderr.trim() || "Patch did not apply" + log("Failed to apply staged patch:", msg) + return { ok: false, error: `Staged patch failed: ${msg}` } + } + const files = parsePatchFiles(snapshot.staged) + if (files.length > 0) { + await git(["add", "--", ...files], target) + } + } + + // Apply unstaged patch (leave as unstaged working-tree changes) + if (snapshot.unstaged) { + const result = await git(["apply", "--whitespace=nowarn", "-"], target, snapshot.unstaged) + if (result.code !== 0) { + const msg = result.stderr.trim() || "Patch did not apply" + log("Failed to apply unstaged patch:", msg) + return { ok: false, error: `Unstaged patch failed: ${msg}` } + } + } + + // Write untracked files + for (const file of snapshot.untracked) { + const full = nodePath.resolve(target, file.path) + try { + await fs.mkdir(nodePath.dirname(full), { recursive: true }) + await fs.writeFile(full, file.content) + } catch (err) { + log(`Failed to write untracked file ${file.path}:`, err) + } + } + + return { ok: true } +} + +/** Extract file paths from a unified diff's `diff --git a/... b/...` headers. */ +function parsePatchFiles(patch: string): string[] { + const files: string[] = [] + for (const line of patch.split("\n")) { + const match = /^diff --git a\/.+ b\/(.+)$/.exec(line) + if (match && match[1]) files.push(match[1]) + } + return files +} diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index a673490e6af..3dab224e322 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -229,6 +229,11 @@ interface PromoteSessionIn { sessionId: string } +interface OpenLocallyIn { + type: "agentManager.openLocally" + sessionId: string +} + interface AddSessionToWorktreeIn { type: "agentManager.addSessionToWorktree" worktreeId: string @@ -411,12 +416,18 @@ interface AbortIn { sessionID: string } +interface ContinueInWorktreeIn { + type: "continueInWorktree" + sessionId: string +} + /** All messages the Agent Manager expects from the webview (onMessage input). */ export type AgentManagerInMessage = | CreateWorktreeIn | DeleteWorktreeIn | RemoveStaleWorktreeIn | PromoteSessionIn + | OpenLocallyIn | AddSessionToWorktreeIn | CloseSessionIn | ForkSessionIn @@ -451,3 +462,4 @@ export type AgentManagerInMessage = | LoadMessagesIn | ClearSessionIn | AbortIn + | ContinueInWorktreeIn diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 89c3d460760..bf6d27da0f2 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -56,11 +56,22 @@ export function activate(context: vscode.ExtensionContext) { }), ) + // Ensure Agent Manager keybindings work when a VS Code terminal has focus. + // The terminal intercepts all keystrokes unless the command is listed in + // terminal.integrated.commandsToSkipShell, which only contains built-in + // commands by default. + ensureCommandsSkipShell(["kilo-code.new.agentManagerOpen", "kilo-code.new.agentManager.showTerminal"]) + // Create Agent Manager provider for editor panel const agentManagerHost = new VscodeHost(context.extensionUri, connectionService, context) const agentManagerProvider = new AgentManagerProvider(agentManagerHost, connectionService) context.subscriptions.push(agentManagerProvider) + // Wire "Continue in Worktree" from sidebar → Agent Manager + provider.setContinueInWorktreeHandler((sessionId, progress) => + agentManagerProvider.continueFromSidebar(sessionId, progress), + ) + // Register serializer so Agent Manager restores when VS Code restarts context.subscriptions.push( vscode.window.registerWebviewPanelSerializer(AgentManagerProvider.viewType, { @@ -79,6 +90,9 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.registerWebviewPanelSerializer("kilo-code.new.TabPanel", { deserializeWebviewPanel(panel: vscode.WebviewPanel) { const tabProvider = new KiloProvider(context.extensionUri, connectionService, context) + tabProvider.setContinueInWorktreeHandler((sessionId, progress) => + agentManagerProvider.continueFromSidebar(sessionId, progress), + ) tabProvider.resolveWebviewPanel(panel) panel.onDidDispose( () => { @@ -149,15 +163,12 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("kilo-code.new.agentManagerOpen", () => { agentManagerProvider.openPanel() }), - vscode.commands.registerCommand("kilo-code.new.marketplaceButtonClicked", () => { - settingsEditorProvider.openPanel("marketplace") + vscode.commands.registerCommand("kilo-code.new.marketplaceButtonClicked", (directory?: string) => { + settingsEditorProvider.openPanel("marketplace", undefined, directory) }), vscode.commands.registerCommand("kilo-code.new.historyButtonClicked", () => { provider.postMessage({ type: "action", action: "historyButtonClicked" }) }), - vscode.commands.registerCommand("kilo-code.new.cloudHistoryButtonClicked", () => { - provider.postMessage({ type: "action", action: "cloudHistoryButtonClicked" }) - }), vscode.commands.registerCommand("kilo-code.new.cycleAgentMode", () => { provider.postMessage({ type: "action", action: "cycleAgentMode" }) agentManagerProvider.postMessage({ type: "action", action: "cycleAgentMode" }) @@ -174,7 +185,7 @@ export function activate(context: vscode.ExtensionContext) { }), // legacy-migration start vscode.commands.registerCommand("kilo-code.new.openMigrationWizard", () => { - provider.postMessage({ type: "navigate", view: "migration" }) + provider.postMessage({ type: "migrationState", needed: true }) }), // legacy-migration end vscode.commands.registerCommand("kilo-code.new.generateTerminalCommand", async () => { @@ -188,7 +199,7 @@ export function activate(context: vscode.ExtensionContext) { provider.postMessage({ type: "triggerTask", text: `Generate a terminal command: ${input}` }) }), vscode.commands.registerCommand("kilo-code.new.openInTab", () => { - return openKiloInNewTab(context, connectionService) + return openKiloInNewTab(context, connectionService, agentManagerProvider) }), vscode.commands.registerCommand("kilo-code.new.showChanges", () => { diffViewerProvider.openPanel() @@ -315,7 +326,11 @@ export function deactivate() { TelemetryProxy.getInstance().shutdown() } -async function openKiloInNewTab(context: vscode.ExtensionContext, connectionService: KiloConnectionService) { +async function openKiloInNewTab( + context: vscode.ExtensionContext, + connectionService: KiloConnectionService, + agentManagerProvider: AgentManagerProvider, +) { const lastCol = Math.max(...vscode.window.visibleTextEditors.map((e) => e.viewColumn || 0), 0) const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0 @@ -337,6 +352,9 @@ async function openKiloInNewTab(context: vscode.ExtensionContext, connectionServ } const tabProvider = new KiloProvider(context.extensionUri, connectionService, context) + tabProvider.setContinueInWorktreeHandler((sessionId, progress) => + agentManagerProvider.continueFromSidebar(sessionId, progress), + ) tabProvider.resolveWebviewPanel(panel) // Wait for the new panel to become active before locking the editor group. @@ -354,6 +372,26 @@ async function openKiloInNewTab(context: vscode.ExtensionContext, connectionServ ) } +/** + * Add extension commands to terminal.integrated.commandsToSkipShell so they + * work when a VS Code terminal has focus. The setting only ships with built-in + * commands; extension commands must be added explicitly. + */ +function ensureCommandsSkipShell(commands: string[]): void { + const config = vscode.workspace.getConfiguration("terminal.integrated") + const info = config.inspect("commandsToSkipShell") + // Update whichever scope already carries an override so we don't + // shadow workspace settings or leak workspace values into global. + const [existing, target] = info?.workspaceFolderValue + ? [info.workspaceFolderValue, vscode.ConfigurationTarget.WorkspaceFolder] + : info?.workspaceValue + ? [info.workspaceValue, vscode.ConfigurationTarget.Workspace] + : [info?.globalValue ?? [], vscode.ConfigurationTarget.Global] + const missing = commands.filter((cmd) => !existing.includes(cmd)) + if (missing.length === 0) return + config.update("commandsToSkipShell", [...existing, ...missing], target) +} + function waitForWebviewPanelToBeActive(panel: vscode.WebviewPanel): Promise { if (panel.active) { return Promise.resolve() diff --git a/packages/kilo-vscode/src/kilo-provider-utils.ts b/packages/kilo-vscode/src/kilo-provider-utils.ts index 6449f43f917..6b91cb668ec 100644 --- a/packages/kilo-vscode/src/kilo-provider-utils.ts +++ b/packages/kilo-vscode/src/kilo-provider-utils.ts @@ -61,6 +61,7 @@ export function getErrorMessage(error: unknown): string { export function sessionToWebview(session: Session) { return { id: session.id, + parentID: session.parentID ?? null, title: session.title, createdAt: new Date(session.time.created).toISOString(), updatedAt: new Date(session.time.updated).toISOString(), @@ -167,6 +168,32 @@ export function buildSettingPath(key: string): { section: string; leaf: string } return { section, leaf } } +export function resolveWorkspaceDirectory(input: { + sessionID?: string + sessionDirectories: Map + workspaceDirectory: string +}) { + if (!input.sessionID) return input.workspaceDirectory + + const dir = input.sessionDirectories.get(input.sessionID) + if (dir) return dir + + return input.workspaceDirectory +} + +export function resolveContextDirectory(input: { + currentSessionID?: string + contextSessionID?: string + sessionDirectories: Map + workspaceDirectory: string +}) { + return resolveWorkspaceDirectory({ + sessionID: input.currentSessionID ?? input.contextSessionID, + sessionDirectories: input.sessionDirectories, + workspaceDirectory: input.workspaceDirectory, + }) +} + export type WebviewMessage = | { type: "partUpdated" diff --git a/packages/kilo-vscode/src/kilo-provider/handlers/migration.ts b/packages/kilo-vscode/src/kilo-provider/handlers/migration.ts index 2a508d4f9cc..b95d630610f 100644 --- a/packages/kilo-vscode/src/kilo-provider/handlers/migration.ts +++ b/packages/kilo-vscode/src/kilo-provider/handlers/migration.ts @@ -28,14 +28,19 @@ export interface MigrationContext { readonly client: KiloClient | null readonly extensionContext: MigrationExtensionContext | undefined postMessage(msg: unknown): void + refreshSessions(): void cachedLegacyData: LegacyMigrationData | null migrationCheckInFlight: boolean disposeGlobal(): Promise + broadcastComplete(): void } /** - * Check for legacy data on first run and auto-navigate to the migration wizard + * Check for legacy data on first run and send migration state to the webview * if the user has not yet been prompted. + * + * Uses a state-based approach (migrationState message) instead of navigate + * to avoid race conditions with SettingsEditorProvider's view navigation. */ export async function checkAndShowMigrationWizard(ctx: MigrationContext): Promise { if (!ctx.extensionContext) return @@ -58,13 +63,14 @@ export async function checkAndShowMigrationWizard(ctx: MigrationContext): Promis ctx.cachedLegacyData = data console.log("[Kilo New] KiloProvider: 🔄 Legacy data detected, showing migration wizard") - ctx.postMessage({ type: "navigate", view: "migration" }) ctx.postMessage({ - type: "legacyMigrationData", + type: "migrationState", + needed: true, data: { providers: data.providers, mcpServers: data.mcpServers, customModes: data.customModes, + sessions: data.sessions, defaultModel: data.defaultModel, settings: data.settings, }, @@ -84,6 +90,7 @@ export async function handleRequestLegacyMigrationData(ctx: MigrationContext): P providers: data.providers, mcpServers: data.mcpServers, customModes: data.customModes, + sessions: data.sessions, defaultModel: data.defaultModel, settings: data.settings, }, @@ -107,19 +114,19 @@ export async function handleStartLegacyMigration( ctx.cachedLegacyData?.settings, ) - // Dispose all instances after migration - // Reloading the data will be handled once the server replies with a global.disposed event - await ctx.disposeGlobal() - - // Only mark as completed if at least one item succeeded — if everything failed - // the user can still re-run migration via Settings → About. + const failed = results.some((r) => r.status === "error") const success = results.some((r) => r.status === "success") - if (success) { + if (!failed && success) { + // Dispose all instances after a fully successful migration. + // Reloading the data will be handled once the server replies with a global.disposed event. + await ctx.disposeGlobal() await MigrationService.setMigrationStatus( ctx.extensionContext as Parameters[0], "completed", ) + ctx.broadcastComplete() + ctx.refreshSessions() } ctx.postMessage({ type: "legacyMigrationComplete", results }) @@ -139,13 +146,14 @@ export async function handleStartLegacyMigration( } } -/** Record that the user skipped migration. */ +/** Record that the user skipped migration and broadcast to all instances. */ export async function handleSkipLegacyMigration(ctx: MigrationContext): Promise { if (!ctx.extensionContext) return await MigrationService.setMigrationStatus( ctx.extensionContext as Parameters[0], "skipped", ) + ctx.broadcastComplete() } /** Clear legacy data from SecretStorage and globalState after user opts in. */ diff --git a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts index 08700ac7161..3ec760d7c4a 100644 --- a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts +++ b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts @@ -1,54 +1,169 @@ /** - * Pure data-transform helpers that strip heavy edit-tool metadata from + * Pure data-transform helpers that strip heavy tool metadata from * message parts before sending them to the webview via postMessage. * + * The webview communicates with the extension over VS Code's IPC bridge. + * Every message is JSON-serialised → deserialised on each side. Tool parts + * from edit, apply_patch, multiedit and write often carry full file contents + * (before/after snapshots, patch text, written content). Sending those on + * every session switch makes serialisation the dominant bottleneck. + * + * This module strips fields the webview never (or rarely) needs while keeping + * everything required to render collapsed tool-part headers and diagnostics. + * * No vscode dependency — safe to unit-test in isolation. */ -/** Strip a filediff down to path + addition/deletion counts. */ -function slimMeta(meta: unknown): Record | undefined { - if (!meta || typeof meta !== "object") return undefined +// Max chars to keep for truncated output fields (bash metadata.output etc.) +const OUTPUT_CAP = 4000 - const obj = meta as Record - const filediff = obj.filediff - if (!filediff || typeof filediff !== "object") return undefined +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- - const diff = filediff as Record - const file = typeof diff.file === "string" ? diff.file : undefined - const additions = typeof diff.additions === "number" ? diff.additions : 0 - const deletions = typeof diff.deletions === "number" ? diff.deletions : 0 - - const result: Record = { - filediff: { - ...(file ? { file } : {}), - additions, - deletions, - }, - } - // Preserve diagnostics so post-edit LSP errors still render - if (obj.diagnostics) result.diagnostics = obj.diagnostics - return result +function isObj(v: unknown): v is Record { + return !!v && typeof v === "object" && !Array.isArray(v) } -/** Strip heavy metadata from a single edit tool part; pass-through for all other part types. */ +/** Truncate a string value to cap, appending a marker when trimmed. */ +function cap(v: unknown, limit = OUTPUT_CAP): string | undefined { + if (typeof v !== "string") return undefined + if (v.length <= limit) return v + return v.slice(0, limit) + `\n… (truncated, ${v.length - limit} chars omitted)` +} + +// --------------------------------------------------------------------------- +// Per-tool slimmers +// --------------------------------------------------------------------------- + +/** edit: strip filediff.before/after (webview falls back to input.oldString/newString). */ +function slimEdit(state: Record): Record { + const next = { ...state } + const meta = state.metadata + if (!isObj(meta)) { + delete next.metadata + return next + } + + const result: Record = {} + const fd = meta.filediff + if (isObj(fd)) { + result.filediff = { + ...(typeof fd.file === "string" ? { file: fd.file } : {}), + additions: typeof fd.additions === "number" ? fd.additions : 0, + deletions: typeof fd.deletions === "number" ? fd.deletions : 0, + } + } + if (meta.diagnostics) result.diagnostics = meta.diagnostics + next.metadata = result + return next +} + +/** apply_patch: strip files[].before/after/diff + input.patchText. */ +function slimPatch(state: Record): Record { + const next = { ...state } + const meta = state.metadata + if (isObj(meta) && Array.isArray(meta.files)) { + next.metadata = { + ...meta, + files: (meta.files as Record[]).map((f) => ({ + filePath: f.filePath, + relativePath: f.relativePath, + type: f.type, + additions: f.additions, + deletions: f.deletions, + movePath: f.movePath, + })), + } + if (isObj(meta) && meta.diagnostics) { + ;(next.metadata as Record).diagnostics = meta.diagnostics + } + } + // Strip the full patch text from input — only keep files count for title + const input = state.input + if (isObj(input) && typeof input.patchText === "string") { + next.input = { ...input, patchText: undefined } + } + return next +} + +/** multiedit: strip nested results (each is a full edit metadata object). */ +function slimMultiedit(state: Record): Record { + const next = { ...state } + const meta = state.metadata + if (isObj(meta) && Array.isArray(meta.results)) { + next.metadata = { + ...meta, + results: (meta.results as Record[]).map((r) => { + const slim: Record = {} + if (r.diagnostics) slim.diagnostics = r.diagnostics + const fd = r.filediff + if (isObj(fd)) { + slim.filediff = { + ...(typeof fd.file === "string" ? { file: fd.file } : {}), + additions: typeof fd.additions === "number" ? fd.additions : 0, + deletions: typeof fd.deletions === "number" ? fd.deletions : 0, + } + } + return slim + }), + } + } + return next +} + +/** write: strip input.content (entire file). Keep filePath + diagnostics. */ +function slimWrite(state: Record): Record { + const next = { ...state } + const input = state.input + if (isObj(input) && typeof input.content === "string") { + next.input = { ...input, content: undefined } + } + return next +} + +/** bash: truncate metadata.output (up to 30KB) and state.output (up to 50KB). */ +function slimBash(state: Record): Record { + const next = { ...state } + const meta = state.metadata + if (isObj(meta) && typeof meta.output === "string" && meta.output.length > OUTPUT_CAP) { + next.metadata = { ...meta, output: cap(meta.output) } + } + if (typeof state.output === "string" && (state.output as string).length > OUTPUT_CAP) { + next.output = cap(state.output) + } + return next +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +const slimmers: Record) => Record> = { + edit: slimEdit, + apply_patch: slimPatch, + multiedit: slimMultiedit, + write: slimWrite, + bash: slimBash, +} + +/** Strip heavy metadata from a single tool part; pass-through for non-tool parts. */ export function slimPart(part: T): T { if (!part || typeof part !== "object") return part const obj = part as Record - if (obj.type !== "tool" || obj.tool !== "edit") return part + if (obj.type !== "tool") return part + + const tool = obj.tool + if (typeof tool !== "string") return part + + const fn = slimmers[tool] + if (!fn) return part const state = obj.state - if (!state || typeof state !== "object") return part + if (!isObj(state)) return part - const next = { ...(state as Record) } - const meta = slimMeta(next.metadata) - if (meta) next.metadata = meta - else delete next.metadata - - return { - ...obj, - state: next, - } as T + return { ...obj, state: fn(state) } as T } /** Slim every part in an array. */ diff --git a/packages/kilo-vscode/src/legacy-migration/errors/migration-error.ts b/packages/kilo-vscode/src/legacy-migration/errors/migration-error.ts new file mode 100644 index 00000000000..a54892df80e --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/errors/migration-error.ts @@ -0,0 +1,83 @@ +interface ErrorLike { + message?: unknown + status?: unknown + data?: unknown + body?: unknown +} + +function isObject(value: unknown): value is Record { + return typeof value === "object" && value !== null +} + +function getText(value: unknown): string | undefined { + if (typeof value === "string") { + const text = value.trim() + return text || undefined + } + + if (typeof value === "number" || typeof value === "boolean") { + return String(value) + } + + return undefined +} + +function getMessage(value: unknown) { + if (!isObject(value)) return undefined + return getText((value as ErrorLike).message) +} + +function getStatus(value: unknown) { + if (!isObject(value)) return undefined + const status = (value as ErrorLike).status + return typeof status === "number" ? String(status) : getText(status) +} + +function getBody(value: unknown) { + if (!isObject(value)) return undefined + + const body = (value as ErrorLike).body + const text = getText(body) + if (text) return text + + if (isObject(body)) { + const msg = getMessage(body) + if (msg) return msg + } + + return undefined +} + +function getData(value: unknown) { + if (!isObject(value)) return undefined + + const data = (value as ErrorLike).data + const text = getText(data) + if (text) return text + + if (isObject(data)) { + const msg = getMessage(data) + if (msg) return msg + } + + return undefined +} + +export function getMigrationErrorMessage(err: unknown) { + const message = getMessage(err) + if (message) return message + + const body = getBody(err) + if (body) return body + + const data = getData(err) + if (data) return data + + const status = getStatus(err) + if (status) return `Request failed (${status})` + + const text = getText(err) + if (text) return text + + return "Unknown migration error" +} diff --git a/packages/kilo-vscode/src/legacy-migration/legacy-types.ts b/packages/kilo-vscode/src/legacy-migration/legacy-types.ts index 3f3af3018f9..854a7655bfb 100644 --- a/packages/kilo-vscode/src/legacy-migration/legacy-types.ts +++ b/packages/kilo-vscode/src/legacy-migration/legacy-types.ts @@ -288,6 +288,7 @@ export interface LegacyMigrationData { providers: MigrationProviderInfo[] mcpServers: MigrationMcpServerInfo[] customModes: MigrationCustomModeInfo[] + sessions?: string[] defaultModel?: { provider: string; model: string } settings?: LegacySettings hasData: boolean @@ -318,13 +319,7 @@ export interface MigrationSelections { providers: string[] mcpServers: string[] customModes: string[] + sessions?: string[] defaultModel: boolean settings: MigrationSettingsSelections } - -export interface MigrationResultItem { - item: string - category: "provider" | "mcpServer" | "customMode" | "defaultModel" | "settings" - status: "success" | "warning" | "error" - message?: string -} diff --git a/packages/kilo-vscode/src/legacy-migration/migration-service.ts b/packages/kilo-vscode/src/legacy-migration/migration-service.ts index 1fd2b1e6d1c..1f4d4117204 100644 --- a/packages/kilo-vscode/src/legacy-migration/migration-service.ts +++ b/packages/kilo-vscode/src/legacy-migration/migration-service.ts @@ -16,6 +16,7 @@ import type { } from "@kilocode/sdk/v2/client" import { PROVIDER_MAP, UNSUPPORTED_PROVIDERS, DEFAULT_MODE_SLUGS } from "./provider-mapping" import type { ProviderMapping } from "./provider-mapping" +import { getMigrationErrorMessage } from "./errors/migration-error" import type { LegacyProviderProfiles, LegacyProviderSettings, @@ -30,8 +31,10 @@ import type { MigrationProviderInfo, MigrationMcpServerInfo, MigrationCustomModeInfo, - MigrationResultItem, } from "./legacy-types" +import type { MigrationResultItem } from "./migration-types" +import { createSessionID } from "./sessions/lib/ids" +import { migrate as migrateSession } from "./sessions/migrate" // --------------------------------------------------------------------------- // Constants @@ -68,6 +71,7 @@ export async function detectLegacyData(context: vscode.ExtensionContext): Promis const mcpSettings = await readLegacyMcpSettings(context) const customModes = await readLegacyCustomModes(context) const settings = readLegacySettings(context) + const sessions = await readSessionsInGlobalStorage(context) const oauthProviders = new Set() const codexRaw = await context.secrets.get(CODEX_OAUTH_SECRET_KEY) @@ -92,18 +96,29 @@ export async function detectLegacyData(context: vscode.ExtensionContext): Promis Boolean(settings.language) || Boolean(settings.autocomplete) - const hasData = providers.length > 0 || mcpServers.length > 0 || modes.length > 0 || hasSettings + const hasData = + providers.length > 0 || mcpServers.length > 0 || modes.length > 0 || hasSettings || sessions.length > 0 return { providers, mcpServers, customModes: modes, + sessions: sessions.length > 0 ? sessions : undefined, defaultModel, settings: hasSettings ? settings : undefined, hasData, } } +async function readSessionsInGlobalStorage(context: vscode.ExtensionContext) { + const dir = vscode.Uri.joinPath(context.globalStorageUri, "tasks") + const items = await vscode.workspace.fs.readDirectory(dir).then( + (items) => items, + () => [] as [string, vscode.FileType][], + ) + return items.filter(([, type]) => type === vscode.FileType.Directory).map(([name]) => name) +} + // --------------------------------------------------------------------------- // Migration // --------------------------------------------------------------------------- @@ -197,6 +212,21 @@ export async function migrate( } } + if (selections.sessions?.length) { + for (const id of selections.sessions) { + onProgress(id, "migrating") + const result = await migrateSession(id, context, client) + const reason = result.ok ? "Session migrated" : result.message + results.push({ + item: id, + category: "session", + status: result.ok ? "success" : "error", + message: reason, + }) + onProgress(id, result.ok ? "success" : "error", reason) + } + } + // Migrate default model if (selections.defaultModel && profiles) { const activeName = profiles.currentApiConfigName @@ -374,6 +404,24 @@ async function migrateProvider( return { item: profileName, category: "provider", status: "warning", message: "No API key found in profile" } } + // The profile endpoint requires type:"oauth". The legacy extension stored the same Kilo + // API token — write it in the OAuth format the new extension expects (matching device-auth: + // access + refresh + 1-year expiry). + if (mapping.id === "kilo") { + const org = mapping.organizationIdField ? (settings[mapping.organizationIdField] as string | undefined) : undefined + await client.auth.set({ + providerID: "kilo", + auth: { + type: "oauth" as const, + access: apiKey, + refresh: apiKey, + expires: Date.now() + 365 * 24 * 60 * 60 * 1000, + accountId: org, + }, + }) + return { item: profileName, category: "provider", status: "success" } + } + // For providers that support an organization ID (e.g. Kilo Gateway), migrate using OAuth // auth so the CLI can read accountId for org-scoped API requests. const organizationId = mapping.organizationIdField @@ -617,7 +665,7 @@ async function migrateAutocomplete(settings: LegacyAutocompleteSettings): Promis item: "Autocomplete settings", category: "settings", status: "error", - message: err instanceof Error ? err.message : String(err), + message: getMigrationErrorMessage(err), } } } @@ -665,7 +713,7 @@ async function migrateLanguage(language: string): Promise { item: "Language preference", category: "settings", status: "error", - message: err instanceof Error ? err.message : String(err), + message: getMigrationErrorMessage(err), } } } diff --git a/packages/kilo-vscode/src/legacy-migration/migration-types.ts b/packages/kilo-vscode/src/legacy-migration/migration-types.ts new file mode 100644 index 00000000000..bd32d39ffd5 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/migration-types.ts @@ -0,0 +1,6 @@ +export interface MigrationResultItem { + item: string + category: "provider" | "mcpServer" | "customMode" | "defaultModel" | "settings" | "session" + status: "success" | "warning" | "error" + message?: string +} diff --git a/packages/kilo-vscode/src/legacy-migration/provider-mapping.ts b/packages/kilo-vscode/src/legacy-migration/provider-mapping.ts index 28d8cf5dca3..02c67a2357b 100644 --- a/packages/kilo-vscode/src/legacy-migration/provider-mapping.ts +++ b/packages/kilo-vscode/src/legacy-migration/provider-mapping.ts @@ -251,6 +251,13 @@ export const PROVIDER_MAP: Record = { key: "syntheticApiKey", name: "Synthetic", }, + apertis: { + id: "apertis", + key: "apertisApiKey", + name: "Apertis", + modelField: "apertisModelId", + urlField: "apertisBaseUrl", + }, "openai-codex": { id: "openai", key: "", @@ -295,7 +302,6 @@ export const UNSUPPORTED_PROVIDERS = new Set([ "virtual-quota-fallback", "glama", "roo", - "apertis", ]) /** Built-in default mode slugs that should not be migrated */ diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/ids.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/ids.ts new file mode 100644 index 00000000000..8f15054c049 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/ids.ts @@ -0,0 +1,29 @@ +import { createHash } from "node:crypto" + +export function createProjectID(worktree?: string) { + return hash(worktree ?? "") +} + +export function createSessionID(id: string) { + return prefixed("ses", id) +} + +export function createMessageID(id: string, index: number) { + return prefixed("msg", `${id}:${index}`) +} + +export function createPartID(id: string, index: number, part: number) { + return prefixed("prt", `${id}:${index}:${part}`) +} + +export function createExtraPartID(id: string, index: number, kind: string) { + return prefixed("prt", `${id}:${index}:${kind}`) +} + +function prefixed(prefix: string, value: string) { + return `${prefix}_migrated_${hash(value).slice(0, 26)}` +} + +function hash(value: string) { + return createHash("sha1").update(value).digest("hex") +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/legacy-conversation.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/legacy-conversation.ts new file mode 100644 index 00000000000..e103ef589d2 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/legacy-conversation.ts @@ -0,0 +1,17 @@ +import * as path from "node:path" +import * as vscode from "vscode" +import type { LegacyApiMessage } from "./legacy-types" + +export async function getApiConversationHistory(id: string, dir: string) { + const file = path.join(dir, id, "api_conversation_history.json") + return vscode.workspace.fs.readFile(vscode.Uri.file(file)) +} + +export function parseFile(file: Uint8Array): LegacyApiMessage[] { + const text = Buffer.from(file).toString("utf8") + const json = JSON.parse(text) as unknown + if (!Array.isArray(json)) { + throw new Error("Legacy conversation history must be a JSON array") + } + return json.filter((entry): entry is LegacyApiMessage => Boolean(entry && typeof entry === "object")) +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/legacy-types.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/legacy-types.ts new file mode 100644 index 00000000000..7288fd51a99 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/legacy-types.ts @@ -0,0 +1,28 @@ +import type { Anthropic } from "@anthropic-ai/sdk" + +export interface LegacyHistoryItem { + id: string + rootTaskId?: string + parentTaskId?: string + ts?: number + task?: string + workspace?: string + mode?: string +} + +export type LegacyApiMessage = Anthropic.MessageParam & { + ts?: number + isSummary?: boolean + id?: string + type?: "reasoning" + summary?: unknown[] + encrypted_content?: string + text?: string + reasoning_details?: unknown[] + reasoning_content?: string + condenseId?: string + condenseParent?: string + truncationId?: string + truncationParent?: string + isTruncationMarker?: boolean +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/messages.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/messages.ts new file mode 100644 index 00000000000..e59616216df --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/messages.ts @@ -0,0 +1,82 @@ +import type { KilocodeSessionImportMessageData as Message } from "@kilocode/sdk/v2" +import type { LegacyApiMessage, LegacyHistoryItem } from "./legacy-types" +import { createMessageID, createSessionID } from "./ids" + +type Body = NonNullable +type Data = Body["data"] +type User = Extract +type Assistant = Extract + +export function parseMessagesFromConversation( + conversation: LegacyApiMessage[], + id: string, + item?: LegacyHistoryItem, +): Array> { + return conversation + .filter((entry) => entry.role === "user" || entry.role === "assistant") + .map((entry, index) => parseMessage(entry, index, id, item)) + .filter((message): message is NonNullable => Boolean(message)) +} + +function parseMessage( + entry: LegacyApiMessage, + index: number, + id: string, + item?: LegacyHistoryItem, +): NonNullable | undefined { + const created = entry.ts ?? item?.ts ?? 0 + + if (entry.role === "user") { + const data: User = { + role: "user", + time: { created }, + agent: "user", + model: { + providerID: "legacy", + modelID: "legacy", + }, + } + + return { + id: createMessageID(id, index), + sessionID: createSessionID(id), + timeCreated: created, + data, + } + } + + if (entry.role === "assistant") { + const data: Assistant = { + role: "assistant", + time: { created, completed: created }, + parentID: index > 0 ? createMessageID(id, index - 1) : createMessageID(id, index), + modelID: "legacy", + providerID: "legacy", + mode: item?.mode ?? "code", + agent: "main", + path: { + cwd: item?.workspace ?? "", + root: item?.workspace ?? "", + }, + cost: 0, + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { + read: 0, + write: 0, + }, + }, + } + + return { + id: createMessageID(id, index), + sessionID: createSessionID(id), + timeCreated: created, + data, + } + } + + return undefined +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/merge-tools.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/merge-tools.ts new file mode 100644 index 00000000000..1604654b7d8 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/merge-tools.ts @@ -0,0 +1,66 @@ +import type { KilocodeSessionImportPartData as Part } from "@kilocode/sdk/v2" +import type { LegacyApiMessage } from "../legacy-types" +import { getText, getToolUse, isToolResult, record } from "./parts-util" + +type Body = NonNullable +type Data = Body["data"] +type Tool = Extract +type ToolCompleted = Extract + +// This takes the "tool started" half and the matching "tool finished" half, +// and merges both so the migrated session keeps one complete tool action. +export function mergeToolUseAndResult( + partID: string, + messageID: string, + sessionID: string, + created: number, + conversation: LegacyApiMessage[], + result: { type?: string; tool_use_id?: string; content?: unknown }, +): NonNullable | undefined { + const tool = getToolUseFromConversation(conversation, result.tool_use_id) + if (!tool) return undefined + const callID = typeof tool.id === "string" ? tool.id : partID + const name = typeof tool.name === "string" ? tool.name : "unknown" + const output = getText(result.content) ?? name + const state: ToolCompleted = { + status: "completed", + input: record(tool.input), + output, + title: name, + metadata: {}, + time: { + start: created, + end: created, + }, + } + + const data: Tool = { + type: "tool", + callID, + tool: name, + state, + } + + return { + id: partID, + messageID, + sessionID, + timeCreated: created, + data, + } +} + +export function thereIsNoToolResult(conversation: LegacyApiMessage[], id: string | undefined) { + return !conversation.some( + (entry) => + Array.isArray(entry.content) && entry.content.some((part) => isToolResult(part) && part.tool_use_id === id), + ) +} + +function getToolUseFromConversation(conversation: LegacyApiMessage[], id: string | undefined) { + for (const entry of conversation) { + const tool = getToolUse(entry, id) + if (tool) return tool + } + return undefined +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts-builder.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts-builder.ts new file mode 100644 index 00000000000..fd91dbaa445 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts-builder.ts @@ -0,0 +1,102 @@ +import type { KilocodeSessionImportPartData as Part } from "@kilocode/sdk/v2" +import { cleanLegacyTaskText, isLegacySystemErrorText, record } from "./parts-util" + +type Body = NonNullable +type Data = Body["data"] +type Text = Extract +type Reasoning = Extract +type Tool = Extract +type ToolCompleted = Extract + +export function toTool( + partID: string, + messageID: string, + sessionID: string, + created: number, + part: { type?: string; id?: string; name?: string; input?: unknown }, +): NonNullable { + const tool = typeof part.name === "string" ? part.name : "unknown" + const state: ToolCompleted = { + status: "completed", + input: record(part.input), + output: tool, + title: tool, + metadata: {}, + time: { + start: created, + end: created, + }, + } + + const data: Tool = { + type: "tool", + callID: part.id ?? partID, + tool, + state, + } + + return { + id: partID, + messageID, + sessionID, + timeCreated: created, + data, + } +} + +export function toText( + partID: string, + messageID: string, + sessionID: string, + created: number, + text: string, +): NonNullable { + const value = cleanLegacyTaskText(text) + const data: Text = { + type: "text", + text: value, + ...(isLegacySystemErrorText(value) && { + ignored: true, + metadata: { + source: "legacy-system-error", + }, + }), + time: { + start: created, + end: created, + }, + } + + return { + id: partID, + messageID, + sessionID, + timeCreated: created, + data, + } +} + +export function toReasoning( + partID: string, + messageID: string, + sessionID: string, + created: number, + text: string, +): NonNullable { + const data: Reasoning = { + type: "reasoning", + text, + time: { + start: created, + end: created, + }, + } + + return { + id: partID, + messageID, + sessionID, + timeCreated: created, + data, + } +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts-util.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts-util.ts new file mode 100644 index 00000000000..d6359eafd9d --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts-util.ts @@ -0,0 +1,132 @@ +import type { LegacyApiMessage } from "../legacy-types" + +type ToolUse = { + type?: string + id?: string + name?: string + input?: unknown +} + +export function isSimpleText(input: LegacyApiMessage): input is LegacyApiMessage & { content: string } { + return typeof input.content === "string" && Boolean(input.content) +} + +export function isReasoning(input: LegacyApiMessage): input is LegacyApiMessage & { type: "reasoning"; text: string } { + return input.type === "reasoning" && typeof input.text === "string" && Boolean(input.text) +} + +export function isProviderSpecificReasoning(input: LegacyApiMessage) { + return Boolean(getReasoningText(input)) +} + +export function getReasoningText(input: LegacyApiMessage) { + if (typeof input.reasoning_content === "string" && input.reasoning_content.trim()) { + // Some providers store the model thinking outside normal content blocks, so we need to lift it manually. + return input.reasoning_content.trim() + } + + if (!Array.isArray(input.reasoning_details)) return undefined + + const text = input.reasoning_details + .flatMap((item) => { + if (!item || typeof item !== "object") return [] + if (typeof (item as { text?: unknown }).text === "string") return [(item as { text: string }).text] + if (typeof (item as { reasoning?: unknown }).reasoning === "string") + return [(item as { reasoning: string }).reasoning] + return [] + }) + .join("\n") + .trim() + + // reasoning_details can come as provider-specific arrays, so we collapse the readable text we can find. + return text || undefined +} + +export function isSingleTextWithinMessage(input: unknown): input is { type?: string; text: string } { + return isText(input) && Boolean(input.text) +} + +export function isEnvironmentDetails(input: string) { + return /^\s*[\s\S]*<\/environment_details>\s*$/i.test(input) +} + +export function isCompletionResult( + input: unknown, +): input is { type?: string; name?: string; input: { result: string } } { + return Boolean( + input && + typeof input === "object" && + "type" in input && + input.type === "tool_use" && + "name" in input && + input.name === "attempt_completion" && + "input" in input && + input.input && + typeof input.input === "object" && + "result" in input.input && + typeof input.input.result === "string" && + input.input.result, + ) +} + +export function record(input: unknown): Record { + if (!input || typeof input !== "object" || Array.isArray(input)) return {} + return input as Record +} + +export function isToolUse(input: unknown): input is { type?: string; id?: string; name?: string; input?: unknown } { + return Boolean(input && typeof input === "object" && "type" in input && input.type === "tool_use") +} + +export function isText(input: unknown): input is { type?: string; text?: string } { + return Boolean(input && typeof input === "object" && "type" in input && input.type === "text") +} + +export function isToolResult(input: unknown): input is { type?: string; tool_use_id?: string; content?: unknown } { + return Boolean(input && typeof input === "object" && "type" in input && input.type === "tool_result") +} + +// This looks through the blocks inside one legacy message and finds the tool_use whose id +// matches the tool_result we are processing, so we know which tool call produced that result. +export function getToolUse(input: LegacyApiMessage, id: string | undefined) { + if (!Array.isArray(input.content)) return undefined + return input.content.find((part) => isToolUse(part) && part.id === id) as ToolUse | undefined +} + +export function getText(input: unknown) { + if (typeof input === "string") return input + if (!Array.isArray(input)) return undefined + const text = input + .flatMap((item) => { + if (isText(item) && item.text) return [item.text] + return [] + }) + .join("\n") + .trim() + return text || undefined +} + +export function cleanLegacyTaskText(input: string) { + // Legacy sometimes stores the real user prompt wrapped inside ..., followed by + // extra ... prompt scaffolding. We only want the + // actual task text to appear in the migrated conversation, so if a block exists we keep + // just that inner text and drop the wrapper plus the extra environment block. + const task = input.match(/([\s\S]*?)<\/task>/i)?.[1]?.trim() + if (task) return task + + if (isEnvironmentDetails(input)) return "" + + return input +} + +export function isLegacySystemErrorText(input: string) { + return input.trimStart().startsWith("[ERROR]") +} + +export function getFeedbackText(input: unknown) { + const text = getText(input) + if (!text) return undefined + const match = text.match(/([\s\S]*?)<\/feedback>/i) + const value = match?.[1]?.trim() + return value || undefined +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts.ts new file mode 100644 index 00000000000..4b122a2f0a5 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/parts/parts.ts @@ -0,0 +1,107 @@ +import type { KilocodeSessionImportPartData as Part } from "@kilocode/sdk/v2" +import type { LegacyApiMessage, LegacyHistoryItem } from "../legacy-types" +import { createExtraPartID, createMessageID, createPartID, createSessionID } from "../ids" +import { toReasoning, toText, toTool } from "./parts-builder" +import { + getFeedbackText, + isCompletionResult, + isEnvironmentDetails, + getReasoningText, + isProviderSpecificReasoning, + isReasoning, + isSimpleText, + isSingleTextWithinMessage, + isToolResult, + isToolUse, +} from "./parts-util" +import { mergeToolUseAndResult, thereIsNoToolResult } from "./merge-tools" + +export function parsePartsFromConversation( + conversation: LegacyApiMessage[], + id: string, + item?: LegacyHistoryItem, +): Array> { + const list = conversation.filter((entry) => entry.role === "user" || entry.role === "assistant") + return list.flatMap((entry, index) => parseParts(entry, index, id, list, item)) +} + +function parseParts( + entry: LegacyApiMessage, + index: number, + id: string, + conversation: LegacyApiMessage[], + item?: LegacyHistoryItem, +): Array> { + const messageID = createMessageID(id, index) + const sessionID = createSessionID(id) + const created = entry.ts ?? item?.ts ?? 0 + + if (isSimpleText(entry)) { + // Ignore raw blocks because they are legacy prompt scaffolding, + // not actual user-visible conversation content we want to preserve in the migrated session. + if (isEnvironmentDetails(entry.content)) return [] + return [toText(createPartID(id, index, 0), messageID, sessionID, created, entry.content)] + } + + if (!Array.isArray(entry.content)) return [] + + const parts: Array> = [] + + if (isReasoning(entry)) { + parts.push(toReasoning(createExtraPartID(id, index, "reasoning"), messageID, sessionID, created, entry.text)) + } + + // Some providers store thinking outside normal content blocks, so this handles those provider-specific fields. + if (!isReasoning(entry) && isProviderSpecificReasoning(entry)) { + const reasoning = getReasoningText(entry) + if (reasoning) { + parts.push( + toReasoning(createExtraPartID(id, index, "provider-reasoning"), messageID, sessionID, created, reasoning), + ) + } + } + + entry.content.forEach((part, partIndex) => { + const partID = createPartID(id, index, partIndex) + + // Legacy can store a message as several pieces; this handles one text block inside that larger message. + if (isSingleTextWithinMessage(part)) { + // Ignore standalone text blocks for the same reason: they describe + // editor/runtime context for the old prompt, but they are not meaningful chat content. + if (isEnvironmentDetails(part.text)) return + parts.push(toText(partID, messageID, sessionID, created, part.text)) + return + } + + // The legacy session can contain a final completion message after an assistant interaction. + // Treat it like a regular assistant text part so the migrated session keeps that final visible answer. + if (isCompletionResult(part)) { + const text = part.input.result + parts.push(toText(partID, messageID, sessionID, created, text)) + return + } + + if (isToolUse(part) && thereIsNoToolResult(conversation, part.id)) { + parts.push(toTool(partID, messageID, sessionID, created, part)) + return + } + + if (isToolResult(part)) { + const feedback = getFeedbackText(part.content) + if (feedback) { + parts.push( + toText(createExtraPartID(id, index, `feedback-${partIndex}`), messageID, sessionID, created, feedback), + ) + } + + // tool_result usually lives in the following user message, while the matching tool_use lives + // in the earlier assistant message, so we need the whole conversation to reconcile both halves. + const tool = mergeToolUseAndResult(partID, messageID, sessionID, created, conversation, part) + if (!tool) return + parts.push(tool) + return + } + }) + + return parts +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/project.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/project.ts new file mode 100644 index 00000000000..b073f535d19 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/project.ts @@ -0,0 +1,29 @@ +import type { KilocodeSessionImportProjectData as Project } from "@kilocode/sdk/v2" +import type { LegacyHistoryItem } from "./legacy-types" +import { createProjectID } from "./ids" + +export function createProject(item?: LegacyHistoryItem): NonNullable { + const project = makeProject() + + project.id = createProjectID(item?.workspace) + + project.worktree = item?.workspace ?? "" + + project.sandboxes = item?.workspace ? [item.workspace] : [] + + project.timeCreated = item?.ts ?? 0 + + project.timeUpdated = item?.ts ?? 0 + + return project +} + +function makeProject(): NonNullable { + return { + id: "", + worktree: "", + sandboxes: [], + timeCreated: 0, + timeUpdated: 0, + } +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/lib/session.ts b/packages/kilo-vscode/src/legacy-migration/sessions/lib/session.ts new file mode 100644 index 00000000000..14058c6e94f --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/lib/session.ts @@ -0,0 +1,42 @@ +import type { KilocodeSessionImportSessionData as Session } from "@kilocode/sdk/v2" +import type { LegacyHistoryItem } from "./legacy-types" +import { createSessionID } from "./ids" + +export function createSession( + id: string, + item: LegacyHistoryItem | undefined, + projectID: string, +): NonNullable { + const session = makeSession() + + session.id = createSessionID(id) + + session.projectID = projectID + + session.slug = id + + session.directory = item?.workspace ?? "" + + session.title = item?.task ?? id + + session.version = "v2" + + session.timeCreated = item?.ts ?? 0 + + session.timeUpdated = item?.ts ?? 0 + + return session +} + +function makeSession(): NonNullable { + return { + id: "", + projectID: "", + slug: "", + directory: "", + title: "", + version: "", + timeCreated: 0, + timeUpdated: 0, + } +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/migrate.ts b/packages/kilo-vscode/src/legacy-migration/sessions/migrate.ts new file mode 100644 index 00000000000..0fa38fdaf0d --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/migrate.ts @@ -0,0 +1,65 @@ +import * as vscode from "vscode" +import type { KiloClient } from "@kilocode/sdk/v2/client" +import { getMigrationErrorMessage } from "../errors/migration-error" +import type { LegacyHistoryItem } from "./lib/legacy-types" +import { parseSession } from "./parser" + +type Result = + | { + ok: true + skipped?: boolean + payload: Awaited> + } + | { + ok: false + payload: Awaited> + message: string + } + +export async function migrate(id: string, context: vscode.ExtensionContext, client: KiloClient): Promise { + const dir = vscode.Uri.joinPath(context.globalStorageUri, "tasks").fsPath + const items = context.globalState.get("taskHistory", []) + const item = items.find((item) => item.id === id) + const payload = await parseSession(id, dir, item) + + try { + const project = await client.kilocode.sessionImport.project(payload.project, { throwOnError: true }) + const projectID = project.data?.id ?? payload.project.id + const session = await client.kilocode.sessionImport.session( + { + ...payload.session, + projectID, + query_directory: payload.session.directory, + body_directory: payload.session.directory, + }, + { throwOnError: true }, + ) + // Skip child imports when the session already exists so rerunning migration only imports missing sessions. + if (session.data?.skipped) { + return { + ok: true, + skipped: true, + payload, + } + } + + for (const msg of payload.messages) { + await client.kilocode.sessionImport.message(msg, { throwOnError: true }) + } + + for (const part of payload.parts) { + await client.kilocode.sessionImport.part(part, { throwOnError: true }) + } + + return { + ok: true, + payload, + } + } catch (error) { + return { + ok: false, + payload, + message: getMigrationErrorMessage(error), + } + } +} diff --git a/packages/kilo-vscode/src/legacy-migration/sessions/parser.ts b/packages/kilo-vscode/src/legacy-migration/sessions/parser.ts new file mode 100644 index 00000000000..76a063b3f80 --- /dev/null +++ b/packages/kilo-vscode/src/legacy-migration/sessions/parser.ts @@ -0,0 +1,35 @@ +import type { LegacyHistoryItem } from "./lib/legacy-types" +import type { + KilocodeSessionImportMessageData as Message, + KilocodeSessionImportPartData as Part, + KilocodeSessionImportProjectData as Project, + KilocodeSessionImportSessionData as Session, +} from "@kilocode/sdk/v2" +import { getApiConversationHistory, parseFile } from "./lib/legacy-conversation" +import { parseMessagesFromConversation } from "./lib/messages" +import { parsePartsFromConversation } from "./lib/parts/parts" +import { createProject } from "./lib/project" +import { createSession } from "./lib/session" + +export interface NormalizedSession { + project: NonNullable + session: NonNullable + messages: Array> + parts: Array> +} + +export async function parseSession(id: string, dir: string, item?: LegacyHistoryItem): Promise { + const project = createProject(item) + const session = createSession(id, item, project.id) + const file = await getApiConversationHistory(id, dir) + const conversation = parseFile(file) + const messages = parseMessagesFromConversation(conversation, id, item) + const parts = parsePartsFromConversation(conversation, id, item) + + return { + project, + session, + messages, + parts, + } +} diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts index 73fe0cdf10c..93ef16b749e 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts @@ -126,6 +126,8 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple private recentlyVisitedRangesService: RecentlyVisitedRangesService private recentlyEditedTracker: RecentlyEditedTracker private debounceTimer: NodeJS.Timeout | null = null + /** The pending request associated with the current debounce timer (if any) */ + private debouncedPendingRequest: PendingRequest | null = null private isFirstCall: boolean = true private ignoreController: Promise /** Abort controller for the current in-flight FIM request */ @@ -297,6 +299,8 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple clearTimeout(this.debounceTimer) this.debounceTimer = null } + this.debouncedPendingRequest = null + this.pendingRequests.length = 0 this.fimAbortController?.abort() this.fimAbortController = null this.telemetry?.dispose() @@ -500,14 +504,25 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple } // If this is the first call (no pending debounce), execute immediately + // but still track it as a pending request so subsequent calls can reuse it if (this.isFirstCall && this.debounceTimer === null) { this.isFirstCall = false - return this.fetchAndCacheSuggestion(prompt, prefix, suffix, languageId) + const promise = this.fetchAndCacheSuggestion(prompt, prefix, suffix, languageId) + const leading: PendingRequest = { prefix, suffix, promise } + promise.finally(() => this.removePendingRequest(leading)) + this.pendingRequests.push(leading) + return promise } - // Clear any existing timer (reset the debounce) + // Clear any existing timer and remove the stale pending request it belongs to. + // The cancelled timer's callback will never fire, so the pending entry would + // otherwise linger with a never-resolving promise. if (this.debounceTimer !== null) { clearTimeout(this.debounceTimer) + if (this.debouncedPendingRequest) { + this.removePendingRequest(this.debouncedPendingRequest) + this.debouncedPendingRequest = null + } } // Create the pending request object first so we can reference it in the cleanup @@ -520,6 +535,7 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple const requestPromise = new Promise((resolve) => { this.debounceTimer = setTimeout(async () => { this.debounceTimer = null + this.debouncedPendingRequest = null this.isFirstCall = true // Reset for next sequence await this.fetchAndCacheSuggestion(prompt, prefix, suffix, languageId) // Remove this request from pending when done @@ -531,6 +547,9 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple // Complete the pending request object pendingRequest.promise = requestPromise + // Track so we can remove it if the timer is cleared by a subsequent call + this.debouncedPendingRequest = pendingRequest + // Add to the list of pending requests this.pendingRequests.push(pendingRequest) diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteInlineCompletionProvider.test.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteInlineCompletionProvider.test.ts index 77a9297f640..65d709f7232 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteInlineCompletionProvider.test.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteInlineCompletionProvider.test.ts @@ -1973,6 +1973,57 @@ describe("AutocompleteInlineCompletionProvider", () => { expect(callCount).toBe(1) }) + it("should reuse slow leading-edge request when user types forward before it completes", async () => { + // Mock the model with a slow response that takes 500ms + let callCount = 0 + let resolvers: Array<() => void> = [] + vi.mocked(mockModel.generateFimResponse).mockImplementation(async (_prefix, _suffix, onChunk) => { + callCount++ + // Simulate a slow FIM response — wait for manual resolution + await new Promise((resolve) => { + resolvers.push(resolve) + }) + if (onChunk) { + onChunk("console.log('test');") + } + return { + cost: 0.01, + inputTokens: 100, + outputTokens: 50, + cacheWriteTokens: 0, + cacheReadTokens: 0, + } + }) + + // First request: leading edge fires immediately for "const x = 1" + const doc1 = new MockTextDocument(vscode.Uri.file("/test.ts"), "const x = 1\nconst y = 2") + const pos1 = new vscode.Position(0, 11) + const promise1 = provider.provideInlineCompletionItems(doc1, pos1, mockContext, mockToken) + + // Leading edge should have started the request immediately + expect(callCount).toBe(1) + + // User types "c" while the leading-edge request is still in-flight + const doc2 = new MockTextDocument(vscode.Uri.file("/test.ts"), "const x = 1c\nconst y = 2") + const pos2 = new vscode.Position(0, 12) + const promise2 = provider.provideInlineCompletionItems(doc2, pos2, mockContext, mockToken) + + // The second request should NOT have triggered a new FIM call — + // it should reuse the leading-edge pending request since the prefix + // extends and the suffix is unchanged + expect(callCount).toBe(1) + + // Now resolve the FIM response + resolvers[0]() + await vi.advanceTimersByTimeAsync(500) + + await promise1 + await promise2 + + // Only one FIM request should have been made total + expect(callCount).toBe(1) + }) + it("should NOT reuse pending request when suffix changes", async () => { // Mock the model to track call count let callCount = 0 diff --git a/packages/kilo-vscode/src/services/cli-backend/connection-service.ts b/packages/kilo-vscode/src/services/cli-backend/connection-service.ts index 929aa1cbf9a..abee02099a5 100644 --- a/packages/kilo-vscode/src/services/cli-backend/connection-service.ts +++ b/packages/kilo-vscode/src/services/cli-backend/connection-service.ts @@ -12,6 +12,7 @@ type SSEEventFilter = (event: Event) => boolean type NotificationDismissListener = (notificationId: string) => void type LanguageChangeListener = (locale: string) => void type ProfileChangeListener = (data: unknown) => void +type MigrationCompleteListener = () => void // Poll /global/health at the same interval as packages/app/src/context/server.tsx. // This provides a second detection channel for server death independent of the SSE heartbeat. @@ -36,6 +37,7 @@ export class KiloConnectionService { private readonly notificationDismissListeners: Set = new Set() private readonly languageChangeListeners: Set = new Set() private readonly profileChangeListeners: Set = new Set() + private readonly migrationCompleteListeners: Set = new Set() /** * Shared mapping used to resolve session scope for events that don't reliably include a sessionID. @@ -207,6 +209,25 @@ export class KiloConnectionService { } } + /** + * Subscribe to migration-complete events broadcast from any KiloProvider. Returns unsubscribe function. + */ + onMigrationComplete(listener: MigrationCompleteListener): () => void { + this.migrationCompleteListeners.add(listener) + return () => { + this.migrationCompleteListeners.delete(listener) + } + } + + /** + * Broadcast a migration-complete event to all subscribed KiloProvider instances. + */ + notifyMigrationComplete(): void { + for (const listener of this.migrationCompleteListeners) { + listener() + } + } + /** * Subscribe to connection state changes. Returns unsubscribe function. */ @@ -228,6 +249,7 @@ export class KiloConnectionService { this.stateListeners.clear() this.notificationDismissListeners.clear() this.profileChangeListeners.clear() + this.migrationCompleteListeners.clear() this.messageSessionIdsByMessageId.clear() this.client = null this.sseClient = null @@ -260,7 +282,7 @@ export class KiloConnectionService { const healthy = await this.checkHealth(baseUrl, password) if (!healthy && this.state === "connected") { console.warn("[Kilo New] ConnectionService: ❤️‍🩹 Health check failed — forcing SSE reconnect") - this.sseClient?.disconnect() + this.sseClient?.reconnect() } }, HEALTH_POLL_INTERVAL_MS) diff --git a/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts b/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts index 88163caafbc..b758b4ba105 100644 --- a/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts +++ b/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts @@ -41,7 +41,12 @@ const bundles: Record> = { function resolveLocale(lang: string): string { const lower = lang.toLowerCase() - if (lower.startsWith("zh")) return lower.includes("hant") ? "zht" : "zh" + if (lower.startsWith("zh")) { + if (lower === "zht") return "zht" + const traditional = + lower.includes("hant") || lower.includes("-tw") || lower.includes("-hk") || lower.includes("-mo") + return traditional ? "zht" : "zh" + } if (lower.startsWith("nb") || lower.startsWith("nn")) return "no" if (lower.startsWith("pt")) return "br" for (const key of Object.keys(bundles)) { diff --git a/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts b/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts index 967b86fa5c5..0021e4e7a9a 100644 --- a/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts +++ b/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts @@ -32,6 +32,7 @@ export class SdkSSEAdapter { private readonly stateHandlers = new Set() private abortController: AbortController | null = null + private attemptController: AbortController | null = null private heartbeatTimer: ReturnType | null = null // 15s matches packages/app/src/context/global-sdk.tsx — server sends heartbeats @@ -71,9 +72,24 @@ export class SdkSSEAdapter { console.log("[Kilo New] SSE: 🔌 disconnect() called") this.abortController?.abort() this.abortController = null + this.attemptController = null this.clearHeartbeat() } + /** + * Force the current SSE attempt to reconnect without killing the outer loop. + * Aborts only the per-attempt controller so `consumeLoop` re-enters its + * reconnection path instead of terminating permanently. + */ + reconnect(): void { + if (!this.attemptController) { + console.log("[Kilo New] SSE: ⚠️ reconnect() called but no active attempt") + return + } + console.log("[Kilo New] SSE: 🔄 reconnect() — aborting current attempt") + this.attemptController.abort() + } + /** * Disconnect and clear all registered handlers. */ @@ -121,14 +137,26 @@ export class SdkSSEAdapter { const onAbort = () => attempt.abort() signal.addEventListener("abort", onAbort) + this.attemptController = attempt + try { console.log("[Kilo New] SSE: 🎬 Calling SDK global.event()...") const events = await this.client.global.event({ signal: attempt.signal, + // Disable SDK-internal retries — consumeLoop handles reconnection + // with its own outer while-loop. Without this the SDK's infinite + // retry loop with exponential backoff runs in parallel, causing + // duplicate connections and "error" state flicker. + sseMaxRetryAttempts: 1, onSseError: (error) => { if (signal.aborted) { return } + // Filter AbortErrors — they are expected during heartbeat timeout + // or manual reconnect() calls, not real connection failures. + if (error instanceof DOMException && error.name === "AbortError") { + return + } console.error("[Kilo New] SSE: ❌ SDK SSE error callback:", error) this.notifyError(error instanceof Error ? error : new Error(String(error))) }, @@ -153,12 +181,16 @@ export class SdkSSEAdapter { console.log("[Kilo New] SSE: 📭 Stream ended normally") } catch (error) { - if (!signal.aborted) { + // Suppress AbortErrors — they are expected when the heartbeat timer + // or reconnect() aborts the per-attempt controller. + const aborted = signal.aborted || (error instanceof DOMException && error.name === "AbortError") + if (!aborted) { console.error("[Kilo New] SSE: ❌ Stream error:", error) this.notifyError(error instanceof Error ? error : new Error(String(error))) } } finally { signal.removeEventListener("abort", onAbort) + this.attemptController = null this.clearHeartbeat() } diff --git a/packages/kilo-vscode/src/session-status.ts b/packages/kilo-vscode/src/session-status.ts index 6e086f15197..e596603e704 100644 --- a/packages/kilo-vscode/src/session-status.ts +++ b/packages/kilo-vscode/src/session-status.ts @@ -16,17 +16,26 @@ export function getBusySessionCount(map: Map): nu * Fetch all current session statuses and seed the provided map + webview. * Called on connect so the Settings panel knows about already-running sessions * without waiting for the next session.status SSE event. + * + * When `reconcile` is true (default: first seed), locally-busy sessions absent + * from the server response are reset to idle — covering server crash/restart. + * On SSE reconnects set `reconcile: false` to avoid a race where the HTTP + * fetch briefly returns stale data and the spinner disappears mid-stream. */ export async function seedSessionStatuses( client: KiloClient, dir: string, map: Map, post: (msg: unknown) => void, + reconcile = true, ): Promise { try { const result = await client.session.status({ directory: dir }) if (!result.data) return - for (const [sid, info] of Object.entries(result.data) as [string, SessionStatus][]) { + const active = result.data + + // Seed/update entries the server knows about + for (const [sid, info] of Object.entries(active) as [string, SessionStatus][]) { map.set(sid, info.type) post({ type: "sessionStatus", @@ -35,6 +44,19 @@ export async function seedSessionStatuses( ...(info.type === "retry" ? { attempt: info.attempt, message: info.message, next: info.next } : {}), }) } + + // Reconcile: any locally non-idle session absent from the server response + // means the server lost its in-memory state (crash/restart). Reset to idle. + // Skipped on SSE reconnects — the real-time SSE events are authoritative + // for status transitions and the brief HTTP fetch can race with them. + if (reconcile) { + for (const [sid, status] of map) { + if (status !== "idle" && !active[sid]) { + map.set(sid, "idle") + post({ type: "sessionStatus", sessionID: sid, status: "idle" }) + } + } + } } catch (error) { console.error("[Kilo New] KiloProvider: Failed to seed session statuses:", error) } diff --git a/packages/kilo-vscode/src/shared/fetch-models.ts b/packages/kilo-vscode/src/shared/fetch-models.ts new file mode 100644 index 00000000000..c8ae62260e9 --- /dev/null +++ b/packages/kilo-vscode/src/shared/fetch-models.ts @@ -0,0 +1,66 @@ +/** + * Fetch available models from an OpenAI-compatible /models endpoint. + * Runs in the extension host — no CLI backend dependency. + */ + +type Options = { + baseURL: string + apiKey?: string + headers?: Record +} + +type ModelEntry = { + id: string + name: string +} + +export class FetchModelsError extends Error { + constructor( + message: string, + public readonly status?: number, + ) { + super(message) + this.name = "FetchModelsError" + } + + get auth() { + return this.status === 401 || this.status === 403 + } +} + +export async function fetchOpenAIModels(opts: Options): Promise { + const url = opts.baseURL.replace(/\/+$/, "") + "/models" + const headers: Record = { + "Content-Type": "application/json", + ...opts.headers, + } + if (opts.apiKey) { + headers["Authorization"] = `Bearer ${opts.apiKey}` + } + + const response = await fetch(url, { + method: "GET", + headers, + signal: AbortSignal.timeout(15_000), + }) + + if (!response.ok) { + const text = await response.text().catch(() => "") + throw new FetchModelsError(`HTTP ${response.status}: ${text.slice(0, 200)}`, response.status) + } + + const body = (await response.json()) as { data?: Array<{ id?: string; name?: string }> } + const items = body?.data + if (!Array.isArray(items)) return [] + + const seen = new Set() + const result: ModelEntry[] = [] + for (const item of items) { + const id = typeof item.id === "string" ? item.id.trim() : "" + if (!id || seen.has(id)) continue + seen.add(id) + result.push({ id, name: typeof item.name === "string" ? item.name.trim() : id }) + } + result.sort((a, b) => a.id.localeCompare(b.id)) + return result +} diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index a13c84ec48c..bf2ffcb5706 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -605,3 +605,66 @@ describe("Agent Manager — VS Code import boundary", () => { ).toEqual([]) }) }) + +// --------------------------------------------------------------------------- +// Provider chain parity — sidebar App.tsx vs AgentManagerApp.tsx +// +// The agent manager reuses ChatView (and therefore MessageList, etc.) from the +// sidebar. Any context provider that ChatView's tree may call useXxx() on must +// also be present in the agent manager's provider chain. A missing provider +// crashes the entire SolidJS component tree silently. +// +// Regression: PR #7473 moved KiloNotifications into MessageList. It calls +// useNotifications(), but NotificationsProvider was only in App.tsx — the agent +// manager rendered a blank screen. +// --------------------------------------------------------------------------- + +const APP_FILE = path.join(ROOT, "webview-ui/src/App.tsx") +const AGENT_MANAGER_APP_FILE = path.join(ROOT, "webview-ui/agent-manager/AgentManagerApp.tsx") + +describe("Agent Manager — provider chain parity with sidebar", () => { + /** + * Extract provider component names used as JSX elements in a file. + * Matches `` patterns, returning the names. + */ + function extractProviders(content: string): string[] { + const matches = [...content.matchAll(/<(\w+Provider)\b/g)] + return [...new Set(matches.map((m) => m[1]!))] + } + + /** + * Providers that the agent manager intentionally omits because it does not + * use the components that depend on them. If a shared component (ChatView, + * MessageList, etc.) starts using one of these, the test will fail and + * force the developer to add the provider to AgentManagerApp.tsx. + */ + const KNOWN_EXCLUSIONS: string[] = [ + // These are wrapped by LanguageBridge and DataBridge respectively, + // which the agent manager already includes in its provider chain. + "LanguageProvider", + "DataProvider", + ] + + it("agent manager includes all context providers from sidebar App.tsx", () => { + const sidebar = fs.readFileSync(APP_FILE, "utf-8") + const agent = fs.readFileSync(AGENT_MANAGER_APP_FILE, "utf-8") + + const sidebarProviders = extractProviders(sidebar) + const agentProviders = extractProviders(agent) + const agentSet = new Set(agentProviders) + const excluded = new Set(KNOWN_EXCLUSIONS) + + const missing = sidebarProviders.filter((p) => !agentSet.has(p) && !excluded.has(p)) + + expect( + missing, + `These providers are in App.tsx but missing from AgentManagerApp.tsx.\n` + + `The agent manager reuses ChatView — any provider that ChatView's component\n` + + `tree depends on must be present in both provider chains.\n\n` + + `Missing providers:\n` + + missing.map((p) => ` - ${p}`).join("\n") + + `\n\nFix: add the missing <${missing[0]}> to AgentManagerApp.tsx's provider chain,\n` + + `or add it to KNOWN_EXCLUSIONS with a justification if it's truly unused.`, + ).toEqual([]) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/config-utils.test.ts b/packages/kilo-vscode/tests/unit/config-utils.test.ts new file mode 100644 index 00000000000..c8974427c11 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/config-utils.test.ts @@ -0,0 +1,158 @@ +import { describe, it, expect } from "bun:test" +import { deepMerge, stripNulls, ConfigState } from "../../webview-ui/src/utils/config-utils" +import type { Config } from "../../webview-ui/src/types/messages" + +// --------------------------------------------------------------------------- +// Pure helpers +// --------------------------------------------------------------------------- + +describe("deepMerge", () => { + it("overrides scalar values", () => { + const target: Config = { snapshot: true } + const source: Partial = { snapshot: false } + expect(deepMerge(target, source)).toEqual({ snapshot: false }) + }) + + it("merges nested objects recursively", () => { + const target: Config = { agent: { code: { temperature: 0.5 } } } + const source: Partial = { agent: { code: { steps: 10 } } } + const result = deepMerge(target, source) + expect(result.agent?.code?.temperature).toBe(0.5) + expect(result.agent?.code?.steps).toBe(10) + }) + + it("preserves keys not present in source", () => { + const target: Config = { snapshot: true, username: "alice" } + const source: Partial = { snapshot: false } + expect(deepMerge(target, source)).toEqual({ snapshot: false, username: "alice" }) + }) + + it("replaces arrays instead of merging them", () => { + const target: Config = { instructions: ["a", "b"] } + const source: Partial = { instructions: ["c"] } + expect(deepMerge(target, source)).toEqual({ instructions: ["c"] }) + }) +}) + +describe("stripNulls", () => { + it("removes null values", () => { + const cfg = { snapshot: true, username: null } as unknown as Config + expect(stripNulls(cfg)).toEqual({ snapshot: true }) + }) + + it("removes undefined values", () => { + const cfg = { snapshot: true, username: undefined } as unknown as Config + expect(stripNulls(cfg)).toEqual({ snapshot: true }) + }) + + it("strips nulls recursively in nested objects", () => { + const cfg = { agent: { code: { temperature: 0.5, prompt: null } } } as unknown as Config + expect(stripNulls(cfg)).toEqual({ agent: { code: { temperature: 0.5 } } }) + }) +}) + +// --------------------------------------------------------------------------- +// Config state machine — reproduces the actual message-handler flow +// --------------------------------------------------------------------------- + +describe("ConfigState", () => { + it("configLoaded sets config when no draft is pending", () => { + const s = new ConfigState() + s.handleConfigLoaded({ snapshot: true, username: "alice" }) + expect(s.config).toEqual({ snapshot: true, username: "alice" }) + expect(s.loading).toBe(false) + }) + + describe("configLoaded while draft is pending (the reported bug)", () => { + it("preserves the user's pending toggle change", () => { + const s = new ConfigState() + + // 1. Server sends initial config + s.handleConfigLoaded({ snapshot: true, username: "alice" }) + expect(s.config.snapshot).toBe(true) + + // 2. User toggles snapshot off (but hasn't saved yet) + s.updateConfig({ snapshot: false }) + expect(s.config.snapshot).toBe(false) + expect(s.dirty).toBe(true) + + // 3. A configLoaded push arrives from the extension (e.g. SSE event, + // tab switch, or another webview triggers a config reload). + // The server still has snapshot: true. + s.handleConfigLoaded({ snapshot: true, username: "alice" }) + + // BUG (old code): config.snapshot would be reset to true here + // FIX: the draft is re-applied, so the user's toggle stays false + expect(s.config.snapshot).toBe(false) + expect(s.config.username).toBe("alice") + expect(s.dirty).toBe(true) + }) + + it("preserves nested draft changes across configLoaded pushes", () => { + const s = new ConfigState() + s.handleConfigLoaded({ agent: { code: { temperature: 0.7 } } }) + s.updateConfig({ agent: { code: { steps: 5 } } }) + + // Server pushes a reload — temperature may have changed server-side + s.handleConfigLoaded({ agent: { code: { temperature: 0.9 } } }) + + expect(s.config.agent?.code?.steps).toBe(5) + expect(s.config.agent?.code?.temperature).toBe(0.9) + }) + }) + + describe("configUpdated while draft is pending", () => { + it("preserves draft when update comes from another source", () => { + const s = new ConfigState() + s.handleConfigLoaded({ snapshot: true, username: "alice" }) + s.updateConfig({ snapshot: false }) + + // Another webview (e.g. PermissionDock) saves a different setting + s.handleConfigUpdated({ snapshot: true, username: "bob" }) + + expect(s.config.snapshot).toBe(false) // draft preserved + expect(s.config.username).toBe("bob") // server update applied + }) + + it("clears draft when update confirms our save", () => { + const s = new ConfigState() + s.handleConfigLoaded({ snapshot: true }) + s.updateConfig({ snapshot: false }) + s.saveConfig() + expect(s.saving).toBe(true) + + // Server confirms the write + s.handleConfigUpdated({ snapshot: false }) + + expect(s.config.snapshot).toBe(false) + expect(s.dirty).toBe(false) + expect(s.saving).toBe(false) + expect(Object.keys(s.draft).length).toBe(0) + }) + }) + + it("configLoaded is ignored while save is in-flight", () => { + const s = new ConfigState() + s.handleConfigLoaded({ snapshot: true }) + s.updateConfig({ snapshot: false }) + s.saveConfig() + + // A stale configLoaded arrives during the write round-trip + s.handleConfigLoaded({ snapshot: true }) + + // Config must not revert — the save is still in flight + expect(s.config.snapshot).toBe(false) + }) + + it("discardConfig restores server state", () => { + const s = new ConfigState() + s.handleConfigLoaded({ snapshot: true, username: "alice" }) + s.updateConfig({ snapshot: false }) + expect(s.dirty).toBe(true) + + s.discardConfig() + + expect(s.config.snapshot).toBe(true) + expect(s.dirty).toBe(false) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts b/packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts new file mode 100644 index 00000000000..9bbbb260b08 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts @@ -0,0 +1,145 @@ +import { describe, expect, it } from "bun:test" +import { + abortSession, + captureState, + forkSession, + registerSession, + type ContinueContext, + type StepResult, +} from "../../src/agent-manager/continue-in-worktree" +import type { CreateWorktreeResult } from "../../src/agent-manager/WorktreeManager" +import type { Session } from "@kilocode/sdk/v2/client" + +const noop = () => {} +const log = noop as (...args: unknown[]) => void + +function session(id: string): Session { + return { + id, + title: "test", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + } as unknown as Session +} + +function result(path: string): CreateWorktreeResult { + return { path, branch: "kilo/test" } as CreateWorktreeResult +} + +/** Build a minimal ContinueContext with overrides. */ +function ctx(overrides: Partial = {}): ContinueContext { + return { + root: "/tmp/test", + getClient: () => { + throw new Error("no client") + }, + createWorktreeOnDisk: async () => null, + runSetupScript: async () => {}, + getStateManager: () => undefined, + registerWorktreeSession: noop, + registerSession: noop, + notifyReady: noop, + capture: noop, + log, + ...overrides, + } +} + +describe("continue-in-worktree steps", () => { + describe("abortSession", () => { + it("does not throw when client is unavailable", async () => { + const c = ctx() + await abortSession(c, "session-1") + }) + + it("does not throw when abort rejects", async () => { + const c = ctx({ + getClient: () => + ({ + session: { abort: () => Promise.reject(new Error("fail")) }, + }) as never, + }) + await abortSession(c, "session-1") + }) + + it("calls abort on the client", async () => { + let called = false + const c = ctx({ + getClient: () => + ({ + session: { + abort: () => { + called = true + return Promise.resolve() + }, + }, + }) as never, + }) + await abortSession(c, "session-1") + expect(called).toBe(true) + }) + }) + + describe("forkSession", () => { + it("returns error when client is unavailable", async () => { + const c = ctx() + const res = await forkSession(c, "session-1", "/tmp/wt") + expect(res.ok).toBe(false) + if (!res.ok) expect(res.error).toBe("Not connected to CLI backend") + }) + + it("returns error when fork rejects", async () => { + const c = ctx({ + getClient: () => + ({ + session: { fork: () => Promise.reject(new Error("fork failed")) }, + }) as never, + }) + const res = await forkSession(c, "session-1", "/tmp/wt") + expect(res.ok).toBe(false) + if (!res.ok) expect(res.error).toContain("fork failed") + }) + + it("returns forked session on success", async () => { + const forked = session("forked-1") + const c = ctx({ + getClient: () => + ({ + session: { fork: () => Promise.resolve({ data: forked }) }, + }) as never, + }) + const res = await forkSession(c, "session-1", "/tmp/wt") + expect(res.ok).toBe(true) + if (res.ok) expect(res.value.id).toBe("forked-1") + }) + }) + + describe("registerSession", () => { + it("calls all registration hooks", () => { + const calls: string[] = [] + const state = { addSession: () => calls.push("addSession") } as never + const c = ctx({ + getStateManager: () => state, + registerWorktreeSession: () => calls.push("registerWorktreeSession"), + registerSession: () => calls.push("registerSession"), + notifyReady: () => calls.push("notifyReady"), + capture: () => calls.push("capture"), + }) + registerSession(c, session("s1"), result("/tmp/wt"), "wt1", "src-session") + expect(calls).toEqual(["addSession", "registerWorktreeSession", "registerSession", "notifyReady", "capture"]) + }) + + it("works without state manager", () => { + const calls: string[] = [] + const c = ctx({ + getStateManager: () => undefined, + registerWorktreeSession: () => calls.push("registerWorktreeSession"), + registerSession: () => calls.push("registerSession"), + notifyReady: () => calls.push("notifyReady"), + capture: () => calls.push("capture"), + }) + registerSession(c, session("s1"), result("/tmp/wt"), "wt1", "src-session") + expect(calls).toEqual(["registerWorktreeSession", "registerSession", "notifyReady", "capture"]) + }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/extension-arch.test.ts b/packages/kilo-vscode/tests/unit/extension-arch.test.ts index 8a935d450d2..233f844bdb3 100644 --- a/packages/kilo-vscode/tests/unit/extension-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/extension-arch.test.ts @@ -14,6 +14,8 @@ 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") +const EXTENSION_FILE = path.join(ROOT, "src/extension.ts") +const KILO_PROVIDER_FILE = path.join(ROOT, "src/KiloProvider.ts") function readSrcFiles(dir: string): string { const parts: string[] = [] @@ -84,3 +86,100 @@ describe("Extension — package.json command sync", () => { ).toEqual([]) }) }) + +// --------------------------------------------------------------------------- +// KiloProvider handler wiring — every new KiloProvider() must get +// setContinueInWorktreeHandler() called before resolving its webview. +// +// Regression: tab panels created via openKiloInNewTab() and the TabPanel +// deserializer were missing the handler, causing "Capturing changes..." to +// spin forever because the webview message was silently dropped. +// --------------------------------------------------------------------------- + +describe("Extension — KiloProvider handler wiring", () => { + const ext = fs.readFileSync(EXTENSION_FILE, "utf-8") + + /** + * Every `new KiloProvider(` in extension.ts must be followed (before the + * next `new KiloProvider(`) by a `setContinueInWorktreeHandler` call. + * This prevents future tab/panel additions from silently missing the handler. + */ + it("every KiloProvider instance gets setContinueInWorktreeHandler wired", () => { + const pattern = /new KiloProvider\(/g + const instances: number[] = [] + let match + while ((match = pattern.exec(ext)) !== null) { + instances.push(match.index) + } + + expect( + instances.length, + "expected at least 3 KiloProvider instances (sidebar, tab, deserializer)", + ).toBeGreaterThanOrEqual(3) + + const missing: string[] = [] + for (let i = 0; i < instances.length; i++) { + const start = instances[i] + const end = instances[i + 1] ?? ext.length + const region = ext.slice(start, end) + + if (!region.includes("setContinueInWorktreeHandler")) { + const line = ext.slice(0, start).split("\n").length + missing.push(`KiloProvider at line ${line}`) + } + } + + expect( + missing, + `These KiloProvider instances are missing setContinueInWorktreeHandler.\n` + + `Without it, "Continue in Worktree" silently no-ops and the spinner\n` + + `stays stuck on "Capturing changes..." forever.\n\n` + + missing.map((m) => ` - ${m}`).join("\n"), + ).toEqual([]) + }) + + it("openKiloInNewTab wires setContinueInWorktreeHandler before resolveWebviewPanel", () => { + const fn = ext.indexOf("function openKiloInNewTab") + expect(fn, "openKiloInNewTab must exist").toBeGreaterThan(-1) + const body = ext.slice(fn, fn + 1500) + const handler = body.indexOf("setContinueInWorktreeHandler") + const resolve = body.indexOf("resolveWebviewPanel") + expect(handler, "setContinueInWorktreeHandler must be called").toBeGreaterThan(-1) + expect(resolve, "resolveWebviewPanel must be called").toBeGreaterThan(-1) + expect(handler, "handler must be wired before resolving the panel").toBeLessThan(resolve) + }) + + it("TabPanel deserializer wires setContinueInWorktreeHandler before resolveWebviewPanel", () => { + const serializer = ext.indexOf('"kilo-code.new.TabPanel"') + expect(serializer, "TabPanel serializer must exist").toBeGreaterThan(-1) + const body = ext.slice(serializer, serializer + 800) + const handler = body.indexOf("setContinueInWorktreeHandler") + const resolve = body.indexOf("resolveWebviewPanel") + expect(handler, "setContinueInWorktreeHandler must be called in deserializer").toBeGreaterThan(-1) + expect(resolve, "resolveWebviewPanel must be called in deserializer").toBeGreaterThan(-1) + expect(handler, "handler must be wired before resolving the panel").toBeLessThan(resolve) + }) +}) + +// --------------------------------------------------------------------------- +// KiloProvider — continueInWorktree error fallback +// +// Regression: when continueInWorktreeHandler is null, the message handler +// must send an error back to the webview so the spinner resets. Previously +// it silently no-op'd, leaving the UI stuck. +// --------------------------------------------------------------------------- + +describe("KiloProvider — continueInWorktree error fallback", () => { + const provider = fs.readFileSync(KILO_PROVIDER_FILE, "utf-8") + + it("sends error progress when handler is missing", () => { + const caseStart = provider.indexOf('case "continueInWorktree"') + expect(caseStart, "continueInWorktree case must exist").toBeGreaterThan(-1) + const caseEnd = provider.indexOf("break", caseStart) + const block = provider.slice(caseStart, caseEnd) + + expect(block, "must have else branch for missing handler").toContain("else if") + expect(block, "must send error status back to webview").toContain('"error"') + expect(block, "must use continueInWorktreeProgress message type").toContain("continueInWorktreeProgress") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/file-tree.test.ts b/packages/kilo-vscode/tests/unit/file-tree.test.ts index b997f86cfd4..a54908dab20 100644 --- a/packages/kilo-vscode/tests/unit/file-tree.test.ts +++ b/packages/kilo-vscode/tests/unit/file-tree.test.ts @@ -1,5 +1,11 @@ import { describe, it, expect } from "bun:test" -import { buildFileTree, flatten, flattenChain, type FileTreeNode } from "../../webview-ui/agent-manager/file-tree-utils" +import { + buildFileTree, + flatten, + flattenChain, + treeOrder, + type FileTreeNode, +} from "../../webview-ui/agent-manager/file-tree-utils" import type { WorktreeFileDiff } from "../../webview-ui/src/types/messages" function diff(file: string, status?: "added" | "deleted" | "modified"): WorktreeFileDiff { @@ -13,13 +19,13 @@ describe("buildFileTree", () => { expect(buildFileTree([])).toEqual([]) }) - it("places root-level files at the top", () => { + it("sorts root-level files alphabetically", () => { const tree = buildFileTree([diff("README.md"), diff("package.json")]) expect(tree).toHaveLength(2) - expect(tree[0]!.name).toBe("README.md") - expect(tree[0]!.path).toBe("README.md") + expect(tree[0]!.name).toBe("package.json") + expect(tree[0]!.path).toBe("package.json") expect(tree[0]!.children).toBeUndefined() - expect(tree[1]!.name).toBe("package.json") + expect(tree[1]!.name).toBe("README.md") }) it("groups files under shared directories", () => { @@ -66,15 +72,15 @@ describe("buildFileTree", () => { expect(tree[0]!.children![0]!.children![0]!.children![0]!.children![0]!.name).toBe("e.ts") }) - it("mixes root-level files with directory files", () => { + it("sorts directories before files at each level", () => { const tree = buildFileTree([diff("README.md"), diff("src/index.ts"), diff("test/index.test.ts")]) - expect(tree).toHaveLength(3) // README.md, src/, test/ - expect(tree[0]!.name).toBe("README.md") - expect(tree[0]!.children).toBeUndefined() - expect(tree[1]!.name).toBe("src") + expect(tree).toHaveLength(3) // src/, test/, README.md + expect(tree[0]!.name).toBe("src") + expect(tree[0]!.children).toHaveLength(1) + expect(tree[1]!.name).toBe("test") expect(tree[1]!.children).toHaveLength(1) - expect(tree[2]!.name).toBe("test") - expect(tree[2]!.children).toHaveLength(1) + expect(tree[2]!.name).toBe("README.md") + expect(tree[2]!.children).toBeUndefined() }) it("attaches diff data to leaf nodes", () => { @@ -89,6 +95,17 @@ describe("buildFileTree", () => { expect(tree[0]!.diff).toBeUndefined() }) + it("sorts directories before files within each level", () => { + const tree = buildFileTree([diff("src/a.ts"), diff("src/b/c.ts")]) + const src = tree[0]! + expect(src.children).toHaveLength(2) + // Directory before file + expect(src.children![0]!.name).toBe("b") + expect(src.children![0]!.children).toBeDefined() + expect(src.children![1]!.name).toBe("a.ts") + expect(src.children![1]!.children).toBeUndefined() + }) + it("separates diverging paths with common prefix", () => { const tree = buildFileTree([diff("src/a.ts"), diff("src/b/c.ts")]) const src = tree[0]! @@ -310,3 +327,82 @@ describe("flatten", () => { expect(result[0]!.children![0]!.name).toBe("index.ts") }) }) + +// ── treeOrder ────────────────────────────────────────────────────────────── + +describe("treeOrder", () => { + it("returns empty array for empty input", () => { + expect(treeOrder([])).toEqual([]) + }) + + it("returns single-element array unchanged", () => { + const input = [diff("a.ts")] + expect(treeOrder(input)).toEqual(input) + }) + + it("groups files by directory", () => { + // Git lexicographic order interleaves directories: a/x, b/y, a/z + // but buildFileTree groups by directory: a/{x,z}, b/{y} + const input = [diff("a/x.ts"), diff("b/y.ts"), diff("a/z.ts")] + const result = treeOrder(input) + expect(result.map((d) => d.file)).toEqual(["a/x.ts", "a/z.ts", "b/y.ts"]) + }) + + it("groups untracked files with their directory siblings", () => { + // The actual bug: backend appends untracked/new files AFTER all tracked + // files. settings-io.ts (untracked, added) ends up after all the tracked + // i18n modifications, but should appear with its directory siblings. + const input = [ + diff("src/components/settings/about.ts"), // tracked modification + diff("src/i18n/ar.ts"), // tracked modification + diff("src/i18n/de.ts"), // tracked modification + diff("src/i18n/en.ts"), // tracked modification + diff("src/components/settings/settings-io.ts"), // untracked, appended last + diff("tests/unit/settings-io.test.ts"), // untracked, appended last + ] + const result = treeOrder(input) + // Tree groups by directory: components/settings/ together, i18n/ together + // Directories sorted alphabetically: components/ before i18n/ before tests/ + expect(result.map((d) => d.file)).toEqual([ + "src/components/settings/about.ts", + "src/components/settings/settings-io.ts", // now grouped with about.ts! + "src/i18n/ar.ts", + "src/i18n/de.ts", + "src/i18n/en.ts", + "tests/unit/settings-io.test.ts", + ]) + }) + + it("reorders when git path sort diverges from tree grouping", () => { + // Files from the same directory are scattered in the input (e.g. because + // untracked files are appended after tracked files) + const input = [ + diff("pkg/tests/a.test.ts"), + diff("pkg/webview/src/components/x.ts"), + diff("pkg/webview/src/i18n/ar.ts"), + diff("pkg/webview/src/i18n/de.ts"), + diff("pkg/tests/b.test.ts"), + ] + const result = treeOrder(input) + // Tree groups and sorts: tests/{a,b}, webview/src/{components/x, i18n/{ar,de}} + expect(result.map((d) => d.file)).toEqual([ + "pkg/tests/a.test.ts", + "pkg/tests/b.test.ts", + "pkg/webview/src/components/x.ts", + "pkg/webview/src/i18n/ar.ts", + "pkg/webview/src/i18n/de.ts", + ]) + }) + + it("sorts root-level files alphabetically", () => { + const input = [diff("README.md"), diff("package.json"), diff("tsconfig.json")] + const result = treeOrder(input) + expect(result.map((d) => d.file)).toEqual(["package.json", "README.md", "tsconfig.json"]) + }) + + it("puts directories before files at each level", () => { + const input = [diff("README.md"), diff("src/a.ts"), diff("src/b.ts"), diff("tsconfig.json")] + const result = treeOrder(input) + expect(result.map((d) => d.file)).toEqual(["src/a.ts", "src/b.ts", "README.md", "tsconfig.json"]) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/git-transfer.test.ts b/packages/kilo-vscode/tests/unit/git-transfer.test.ts new file mode 100644 index 00000000000..11b29e6c0c4 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/git-transfer.test.ts @@ -0,0 +1,215 @@ +import { describe, expect, it, beforeEach, afterEach } from "bun:test" +import * as fs from "fs/promises" +import * as path from "path" +import * as os from "os" +import * as cp from "child_process" +import { capture, apply } from "../../src/agent-manager/git-transfer" + +function git(args: string[], cwd: string): Promise { + return new Promise((resolve, reject) => { + cp.execFile("git", args, { cwd, encoding: "utf8" }, (err, stdout) => { + if (err) reject(err) + else resolve(stdout.trim()) + }) + }) +} + +const noop = () => {} + +describe("git-transfer", () => { + let dir: string + + beforeEach(async () => { + dir = await fs.mkdtemp(path.join(os.tmpdir(), "git-transfer-test-")) + await git(["init", "-b", "main"], dir) + await git(["config", "user.email", "test@test.com"], dir) + await git(["config", "user.name", "Test"], dir) + // Initial commit so HEAD exists + await fs.writeFile(path.join(dir, "init.txt"), "init\n") + await git(["add", "."], dir) + await git(["commit", "-m", "initial"], dir) + }) + + afterEach(async () => { + await fs.rm(dir, { recursive: true, force: true }) + }) + + describe("capture", () => { + it("captures branch and head", async () => { + const snapshot = await capture(dir, noop) + expect(snapshot.branch).toBe("main") + expect(snapshot.head).toMatch(/^[0-9a-f]{40}$/) + }) + + it("captures unstaged changes", async () => { + await fs.writeFile(path.join(dir, "init.txt"), "modified\n") + const snapshot = await capture(dir, noop) + expect(snapshot.unstaged).toContain("modified") + expect(snapshot.staged).toBeNull() + }) + + it("captures staged changes", async () => { + await fs.writeFile(path.join(dir, "init.txt"), "staged\n") + await git(["add", "init.txt"], dir) + const snapshot = await capture(dir, noop) + expect(snapshot.staged).toContain("staged") + expect(snapshot.unstaged).toBeNull() + }) + + it("captures both staged and unstaged", async () => { + await fs.writeFile(path.join(dir, "init.txt"), "staged\n") + await git(["add", "init.txt"], dir) + await fs.writeFile(path.join(dir, "init.txt"), "unstaged on top\n") + const snapshot = await capture(dir, noop) + expect(snapshot.staged).toContain("staged") + expect(snapshot.unstaged).toContain("unstaged on top") + }) + + it("captures untracked files", async () => { + await fs.writeFile(path.join(dir, "new.txt"), "brand new\n") + const snapshot = await capture(dir, noop) + expect(snapshot.untracked).toHaveLength(1) + expect(snapshot.untracked[0].path).toBe("new.txt") + expect(snapshot.untracked[0].content.toString()).toBe("brand new\n") + }) + + it("captures untracked files in subdirectories", async () => { + await fs.mkdir(path.join(dir, "sub"), { recursive: true }) + await fs.writeFile(path.join(dir, "sub", "deep.txt"), "deep\n") + const snapshot = await capture(dir, noop) + expect(snapshot.untracked).toHaveLength(1) + expect(snapshot.untracked[0].path).toBe("sub/deep.txt") + }) + + it("returns null patches when working tree is clean", async () => { + const snapshot = await capture(dir, noop) + expect(snapshot.unstaged).toBeNull() + expect(snapshot.staged).toBeNull() + expect(snapshot.untracked).toHaveLength(0) + }) + }) + + describe("apply", () => { + let target: string + + beforeEach(async () => { + // Create a target as a git worktree from the same repo (same commit) + target = path.join(os.tmpdir(), `git-transfer-target-${Date.now()}`) + await git(["worktree", "add", "-b", "test-wt", target, "HEAD"], dir) + }) + + afterEach(async () => { + await git(["worktree", "remove", "--force", target], dir).catch(() => {}) + await fs.rm(target, { recursive: true, force: true }).catch(() => {}) + }) + + it("applies unstaged changes", async () => { + await fs.writeFile(path.join(dir, "init.txt"), "modified\n") + const snapshot = await capture(dir, noop) + const result = await apply(snapshot, target, noop) + expect(result.ok).toBe(true) + const content = await fs.readFile(path.join(target, "init.txt"), "utf8") + expect(content).toBe("modified\n") + // Should show as modified in target + const status = await git(["status", "--porcelain"], target) + expect(status).toContain("M init.txt") + }) + + it("applies staged changes and re-stages them", async () => { + await fs.writeFile(path.join(dir, "init.txt"), "staged\n") + await git(["add", "init.txt"], dir) + const snapshot = await capture(dir, noop) + const result = await apply(snapshot, target, noop) + expect(result.ok).toBe(true) + const content = await fs.readFile(path.join(target, "init.txt"), "utf8") + expect(content).toBe("staged\n") + // Should be staged in target + const status = await git(["status", "--porcelain"], target) + expect(status).toContain("M init.txt") + }) + + it("writes untracked files", async () => { + await fs.writeFile(path.join(dir, "new.txt"), "brand new\n") + const snapshot = await capture(dir, noop) + const result = await apply(snapshot, target, noop) + expect(result.ok).toBe(true) + const content = await fs.readFile(path.join(target, "new.txt"), "utf8") + expect(content).toBe("brand new\n") + }) + + it("creates subdirectories for untracked files", async () => { + await fs.mkdir(path.join(dir, "a", "b"), { recursive: true }) + await fs.writeFile(path.join(dir, "a", "b", "c.txt"), "nested\n") + const snapshot = await capture(dir, noop) + const result = await apply(snapshot, target, noop) + expect(result.ok).toBe(true) + const content = await fs.readFile(path.join(target, "a", "b", "c.txt"), "utf8") + expect(content).toBe("nested\n") + }) + + it("returns error when patch cannot be applied", async () => { + await fs.writeFile(path.join(dir, "init.txt"), "modified\n") + const snapshot = await capture(dir, noop) + // Make target diverge so the patch fails + await fs.writeFile(path.join(target, "init.txt"), "conflicting\n") + await git(["add", "init.txt"], target) + await git(["commit", "-m", "diverge"], target) + const result = await apply(snapshot, target, noop) + expect(result.ok).toBe(false) + expect(result.error).toBeDefined() + }) + + it("applies empty snapshot without error", async () => { + const snapshot = await capture(dir, noop) + const result = await apply(snapshot, target, noop) + expect(result.ok).toBe(true) + }) + }) + + describe("round-trip", () => { + let target: string + + beforeEach(async () => { + target = path.join(os.tmpdir(), `git-transfer-rt-${Date.now()}`) + await git(["worktree", "add", "-b", `rt-${Date.now()}`, target, "HEAD"], dir) + }) + + afterEach(async () => { + await git(["worktree", "remove", "--force", target], dir).catch(() => {}) + await fs.rm(target, { recursive: true, force: true }).catch(() => {}) + }) + + it("preserves staged + unstaged + untracked in one round-trip", async () => { + // Stage a change + await fs.writeFile(path.join(dir, "init.txt"), "staged version\n") + await git(["add", "init.txt"], dir) + // Make an unstaged change on top + await fs.writeFile(path.join(dir, "init.txt"), "unstaged version\n") + // Add an untracked file + await fs.writeFile(path.join(dir, "extra.txt"), "extra\n") + + const snapshot = await capture(dir, noop) + const result = await apply(snapshot, target, noop) + expect(result.ok).toBe(true) + + // Unstaged content should be the working tree version + const content = await fs.readFile(path.join(target, "init.txt"), "utf8") + expect(content).toBe("unstaged version\n") + + // Untracked file should exist + const extra = await fs.readFile(path.join(target, "extra.txt"), "utf8") + expect(extra).toBe("extra\n") + }) + + it("does not modify the source directory", async () => { + await fs.writeFile(path.join(dir, "init.txt"), "changed\n") + await fs.writeFile(path.join(dir, "new.txt"), "new\n") + const before = await git(["status", "--porcelain"], dir) + + await capture(dir, noop) + + const after = await git(["status", "--porcelain"], dir) + expect(after).toBe(before) + }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts index 4f0e44c986a..d74842073d3 100644 --- a/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts @@ -80,6 +80,7 @@ function createConnection(client: ReturnType) { onNotificationDismissed: () => () => undefined, onLanguageChanged: () => () => undefined, onProfileChanged: () => () => undefined, + onMigrationComplete: () => () => undefined, getServerInfo: () => ({ port: 12345 }), getConnectionState: () => "connected" as const, resolveEventSessionId: () => undefined, diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-worktree-context.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-worktree-context.test.ts new file mode 100644 index 00000000000..41c9753c950 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/kilo-provider-worktree-context.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "bun:test" +import { resolveContextDirectory, resolveWorkspaceDirectory } from "../../src/kilo-provider-utils" + +describe("resolveWorkspaceDirectory", () => { + it("uses an explicit session worktree override", () => { + const dir = resolveWorkspaceDirectory({ + sessionID: "ses_worktree", + sessionDirectories: new Map([["ses_worktree", "/repo/.kilo/worktrees/feature"]]), + workspaceDirectory: "/repo", + }) + + expect(dir).toBe("/repo/.kilo/worktrees/feature") + }) + + it("falls back to the workspace root without a session id", () => { + const dir = resolveWorkspaceDirectory({ + sessionDirectories: new Map([["ses_worktree", "/repo/.kilo/worktrees/feature"]]), + workspaceDirectory: "/repo", + }) + + expect(dir).toBe("/repo") + }) +}) + +describe("resolveContextDirectory", () => { + it("uses the active session worktree when a worktree session is selected", () => { + const dir = resolveContextDirectory({ + currentSessionID: "ses_worktree", + sessionDirectories: new Map([["ses_worktree", "/repo/.kilo/worktrees/feature"]]), + workspaceDirectory: "/repo", + }) + + expect(dir).toBe("/repo/.kilo/worktrees/feature") + }) + + it("keeps the last worktree after clearSession removes the active session", () => { + const dir = resolveContextDirectory({ + contextSessionID: "ses_worktree", + sessionDirectories: new Map([["ses_worktree", "/repo/.kilo/worktrees/feature"]]), + workspaceDirectory: "/repo", + }) + + expect(dir).toBe("/repo/.kilo/worktrees/feature") + }) + + it("falls back to the workspace root when no worktree override exists", () => { + const dir = resolveContextDirectory({ + contextSessionID: "ses_local", + sessionDirectories: new Map(), + workspaceDirectory: "/repo", + }) + + expect(dir).toBe("/repo") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/language-utils.test.ts b/packages/kilo-vscode/tests/unit/language-utils.test.ts index 75ad9bd5f8c..cc1a8c286bd 100644 --- a/packages/kilo-vscode/tests/unit/language-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/language-utils.test.ts @@ -15,8 +15,11 @@ describe("normalizeLocale", () => { }) it("returns 'zht' for Traditional Chinese", () => { + expect(normalizeLocale("zht")).toBe("zht") expect(normalizeLocale("zh-Hant")).toBe("zht") - expect(normalizeLocale("zh-TW")).toBe("zh") + expect(normalizeLocale("zh-TW")).toBe("zht") + expect(normalizeLocale("zh-HK")).toBe("zht") + expect(normalizeLocale("zh-MO")).toBe("zht") expect(normalizeLocale("zh-hant-TW")).toBe("zht") }) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/messages.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/messages.test.ts new file mode 100644 index 00000000000..69450d78025 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/messages.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it } from "bun:test" +import { parseMessagesFromConversation } from "../../../src/legacy-migration/sessions/lib/messages" +import type { LegacyApiMessage } from "../../../src/legacy-migration/sessions/lib/legacy-types" + +const id = "019d3df5-d5d9-73dc-bc2c-43a6304ac62c" +const item = { + id, + ts: 1774861014564, + task: "In this folder I need you to create 3 python files with random content, oriented towards web development", + workspace: "/workspace/testing-4", + mode: "code", +} + +function sample(): LegacyApiMessage[] { + return [ + { + role: "user", + content: [ + { + type: "text", + text: "\nIn this folder I need you to create 3 python files with random content, oriented towards web development\n", + }, + ], + ts: 1774861014564, + }, + { + role: "assistant", + content: [ + { + type: "text", + text: "\n\nI'll create 3 Python files with web development-oriented content.", + }, + ], + ts: 1774861031791, + }, + { + role: "system", + content: "ignored", + ts: 1774861032000, + } as unknown as LegacyApiMessage, + ] +} + +describe("legacy migration messages", () => { + it("parses a basic legacy conversation into ordered user and assistant messages with stable ids", () => { + const list = parseMessagesFromConversation(sample(), id, item) + + expect(list).toHaveLength(2) + expect(list[0]?.data.role).toBe("user") + expect(list[1]?.data.role).toBe("assistant") + expect(list[0]?.id).toBeDefined() + expect(list[1]?.id).toBeDefined() + }) + + it("creates valid assistant message metadata for the SDK/backend shape", () => { + const list = parseMessagesFromConversation(sample(), id, item) + const msg = list.find((x) => x.data.role === "assistant") + + expect(msg?.data.role).toBe("assistant") + if (msg?.data.role !== "assistant") throw new Error("assistant message not found") + expect(msg.data.mode).toBe("code") + expect(msg.data.path.cwd).toBe("/workspace/testing-4") + expect(msg.data.path.root).toBe("/workspace/testing-4") + expect(msg.data.tokens.input).toBe(0) + expect(msg.data.tokens.output).toBe(0) + }) + + it("ignores unsupported legacy entries instead of producing broken messages", () => { + const list = parseMessagesFromConversation(sample(), id, item) + + expect(list).toHaveLength(2) + expect(list.some((x) => x.data.role !== "user" && x.data.role !== "assistant")).toBe(false) + }) + + it("keeps assistant parentID pointing to the previous imported message when skipped entries exist", () => { + const list = parseMessagesFromConversation( + [ + { + role: "user", + content: "hello", + ts: 1, + }, + { + role: "system", + content: "ignored", + ts: 2, + } as unknown as LegacyApiMessage, + { + role: "assistant", + content: "hi", + ts: 3, + }, + ], + id, + item, + ) + + const user = list[0] + const assistant = list[1] + + expect(user?.id).toBeDefined() + expect(assistant?.data.role).toBe("assistant") + if (assistant?.data.role !== "assistant") throw new Error("assistant message not found") + expect(assistant.data.parentID).toBe(user?.id) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/migrate.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/migrate.test.ts new file mode 100644 index 00000000000..5eede710e73 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/migrate.test.ts @@ -0,0 +1,212 @@ +import { beforeEach, describe, expect, it, mock } from "bun:test" + +const parseSession = mock(async () => ({ + project: { + id: "project-local", + worktree: "/workspace/testing", + sandboxes: ["/workspace/testing"], + timeCreated: 1, + timeUpdated: 1, + }, + session: { + id: "ses_local", + projectID: "project-local", + slug: "legacy-task-1", + directory: "/workspace/testing", + title: "Legacy task", + version: "v2", + timeCreated: 1, + timeUpdated: 1, + }, + messages: [], + parts: [], +})) + +mock.module("../../../src/legacy-migration/sessions/parser", () => ({ + parseSession, +})) + +const { migrate } = await import("../../../src/legacy-migration/sessions/migrate") + +function ctx() { + return { + globalStorageUri: { path: "/storage" }, + globalState: { + get: (key: string) => { + if (key === "taskHistory") return [{ id: "legacy-task-1", workspace: "/workspace/testing" }] + return undefined + }, + update: async () => undefined, + }, + } +} + +function client() { + const calls: Array<{ name: string; body: unknown }> = [] + return { + calls, + kilocode: { + sessionImport: { + project: async (body: unknown) => { + calls.push({ name: "project", body }) + return { data: { id: "project-real" } } + }, + session: async (body: unknown) => { + calls.push({ name: "session", body }) + return { data: { ok: true } } + }, + message: async (body: unknown) => { + calls.push({ name: "message", body }) + return { data: { ok: true } } + }, + part: async (body: unknown) => { + calls.push({ name: "part", body }) + return { data: { ok: true } } + }, + }, + }, + } +} + +describe("legacy migration migrate", () => { + beforeEach(() => { + parseSession.mockClear() + }) + + it("inserts project then session then message then part in the correct order", async () => { + const calls: string[] = [] + const api = { + kilocode: { + sessionImport: { + project: async () => { + calls.push("project") + return { data: { id: "project-real" } } + }, + session: async () => { + calls.push("session") + return { data: { ok: true } } + }, + message: async () => { + calls.push("message") + return { data: { ok: true } } + }, + part: async () => { + calls.push("part") + return { data: { ok: true } } + }, + }, + }, + } + + parseSession.mockResolvedValueOnce({ + project: { + id: "project-local", + worktree: "/workspace/testing", + sandboxes: ["/workspace/testing"], + timeCreated: 1, + timeUpdated: 1, + }, + session: { + id: "ses_local", + projectID: "project-local", + slug: "legacy-task-1", + directory: "/workspace/testing", + title: "Legacy task", + version: "v2", + timeCreated: 1, + timeUpdated: 1, + }, + messages: [ + { + id: "msg_1", + sessionID: "ses_local", + timeCreated: 1, + data: { + role: "user", + time: { created: 1 }, + agent: "user", + model: { providerID: "legacy", modelID: "legacy" }, + }, + }, + ] as never, + parts: [ + { + id: "prt_1", + sessionID: "ses_local", + messageID: "msg_1", + timeCreated: 1, + data: { + type: "text", + text: "hello", + time: { start: 1, end: 1 }, + }, + }, + ] as never, + }) + + await migrate("legacy-task-1", ctx() as never, api as never) + + expect(calls).toEqual(["project", "session", "message", "part"]) + }) + + it("uses the projectID returned by backend when inserting the session", async () => { + const api = client() + + await migrate("legacy-task-1", ctx() as never, api as never) + + const call = api.calls.find((x) => x.name === "session") + expect(call).toBeDefined() + expect((call?.body as { projectID: string }).projectID).toBe("project-real") + }) + + it("returns ok false without throwing when one backend insert fails", async () => { + const api = { + kilocode: { + sessionImport: { + project: async () => ({ data: { id: "project-real" } }), + session: async () => { + throw new Error("session failed") + }, + message: async () => ({ data: { ok: true } }), + part: async () => ({ data: { ok: true } }), + }, + }, + } + + const result = await migrate("legacy-task-1", ctx() as never, api as never) + + expect(result.ok).toBe(false) + }) + + it("skips message and part imports when backend reports the session already exists", async () => { + const calls: string[] = [] + const api = { + kilocode: { + sessionImport: { + project: async () => { + calls.push("project") + return { data: { id: "project-real" } } + }, + session: async () => { + calls.push("session") + return { data: { ok: true, skipped: true } } + }, + message: async () => { + calls.push("message") + return { data: { ok: true } } + }, + part: async () => { + calls.push("part") + return { data: { ok: true } } + }, + }, + }, + } + + const result = await migrate("legacy-task-1", ctx() as never, api as never) + + expect(result.ok).toBe(true) + expect(result).toHaveProperty("skipped", true) + expect(calls).toEqual(["project", "session"]) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/parser.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/parser.test.ts new file mode 100644 index 00000000000..db2477b265d --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/parser.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "bun:test" +import { createProject } from "../../../src/legacy-migration/sessions/lib/project" +import { createSession } from "../../../src/legacy-migration/sessions/lib/session" +import { createMessageID, createPartID } from "../../../src/legacy-migration/sessions/lib/ids" + +const id = "019d3df5-d5d9-73dc-bc2c-43a6304ac62c" +const item = { + id, + ts: 1774861014564, + task: "In this folder I need you to create 3 python files with random content, oriented towards web development", + workspace: "/workspace/testing-4", + mode: "code", +} + +describe("legacy migration parser", () => { + it("uses the final deterministic ids expected for migration", () => { + const project = createProject(item) + const session = createSession(id, item, project.id) + const msg1 = createMessageID(id, 0) + const msg2 = createMessageID(id, 1) + const prt1 = createPartID(id, 0, 0) + const prt2 = createPartID(id, 1, 0) + + expect(project.id).toBe(createProject(item).id) + expect(session.id).toBe(createSession(id, item, project.id).id) + expect(session.id.startsWith("ses_migrated_")).toBe(true) + expect(msg1).toBe(createMessageID(id, 0)) + expect(msg2).toBe(createMessageID(id, 1)) + expect(msg1.startsWith("msg_migrated_")).toBe(true) + expect(msg2.startsWith("msg_migrated_")).toBe(true) + expect(prt1).toBe(createPartID(id, 0, 0)) + expect(prt2).toBe(createPartID(id, 1, 0)) + expect(prt1.startsWith("prt_migrated_")).toBe(true) + expect(prt2.startsWith("prt_migrated_")).toBe(true) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/parts.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/parts.test.ts new file mode 100644 index 00000000000..60b81062410 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/parts.test.ts @@ -0,0 +1,224 @@ +import { describe, expect, it } from "bun:test" +import { parsePartsFromConversation } from "../../../src/legacy-migration/sessions/lib/parts/parts" +import type { LegacyApiMessage } from "../../../src/legacy-migration/sessions/lib/legacy-types" + +type Data = ReturnType[number]["data"] +type Text = Extract + +function text(list: ReturnType) { + return list.filter((x): x is (typeof list)[number] & { data: Text } => x.data.type === "text").map((x) => x.data.text) +} + +const id = "019d3df5-d5d9-73dc-bc2c-43a6304ac62c" +const item = { + id, + ts: 1774861014564, + task: "In this folder I need you to create 3 python files with random content, oriented towards web development", + workspace: "/workspace/testing-4", + mode: "code", +} + +function sample(): LegacyApiMessage[] { + return [ + { + role: "user", + content: [ + { + type: "text", + text: "\nIn this folder I need you to create 3 python files with random content, oriented towards web development\n", + }, + { + type: "text", + text: "\nCurrent time: 2026-03-30T12:54:59+02:00\n", + }, + ], + ts: 1774861014564, + }, + { + role: "assistant", + content: [ + { + type: "text", + text: "\n\nI'll create 3 Python files with web development-oriented content.", + }, + ], + ts: 1774861031791, + }, + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "toolu_attempt_1", + name: "attempt_completion", + input: { + result: "Created 3 Python files with web development content.", + }, + }, + ], + ts: 1774861079951, + }, + ] +} + +describe("legacy migration parts", () => { + it("converts plain text and text blocks into visible text parts without losing content", async () => { + const list = parsePartsFromConversation(sample(), id, item) + + const items = text(list) + + expect(items.some((x) => x.includes("In this folder I need you to create 3 python files"))).toBe(true) + expect(items.some((x) => x.includes("I'll create 3 Python files with web development-oriented content."))).toBe( + true, + ) + }) + + it("drops standalone environment_details blocks but keeps the real task text", async () => { + const list = parsePartsFromConversation(sample(), id, item) + + const items = text(list) + + expect(items.some((x) => x.includes(""))).toBe(false) + expect(items.some((x) => x.includes("In this folder I need you to create 3 python files"))).toBe(true) + }) + + it("keeps only the first task block content when text exists outside the legacy task wrapper", () => { + const list = parsePartsFromConversation( + [ + { + role: "user", + content: [ + { + type: "text", + text: "Some preamble\nactual task\nSome postamble", + }, + ], + ts: 1774861014564, + }, + ] as LegacyApiMessage[], + id, + item, + ) + + expect(text(list)).toEqual(["actual task"]) + }) + + it("preserves attempt_completion input.result as assistant-visible text", async () => { + const list = parsePartsFromConversation(sample(), id, item) + + const items = text(list) + + expect(items.some((x) => x.includes("Created 3 Python files with web development content"))).toBe(true) + }) + + it("adds a visible user text part for feedback embedded in tool_result", () => { + const list = parsePartsFromConversation( + [ + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "toolu_feedback_1", + name: "attempt_completion", + input: { + result: "Done.", + }, + }, + ], + ts: 10, + }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_feedback_1", + content: [ + { + type: "text", + text: "The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n\nAdd two more lines\n", + }, + ], + }, + ], + ts: 11, + }, + ] as LegacyApiMessage[], + id, + item, + ) + + expect(text(list)).toContain("Add two more lines") + }) + + it("does not create parts for skipped legacy entries like system messages", () => { + const list = parsePartsFromConversation( + [ + { + role: "system", + content: "ignored", + ts: 1774861015000, + } as unknown as LegacyApiMessage, + ], + id, + item, + ) + + expect(list).toEqual([]) + }) + + it("keeps part message ids aligned with imported message ids when skipped entries exist", () => { + const list = parsePartsFromConversation( + [ + { + role: "user", + content: "hello", + ts: 1, + }, + { + role: "system", + content: "ignored", + ts: 2, + } as unknown as LegacyApiMessage, + { + role: "assistant", + content: "hi", + ts: 3, + }, + ], + id, + item, + ) + + const ids = list.map((x) => x.messageID) + + expect(ids.some((x) => x === "msg_legacy-task-1_2")).toBe(false) + }) + + it("uses non-colliding ids for reasoning and normal content parts in the same message", () => { + const list = parsePartsFromConversation( + [ + { + role: "assistant", + type: "reasoning", + text: "Think first", + content: [ + { + type: "text", + text: "Visible answer", + }, + ], + ts: 1774861015000, + } as unknown as LegacyApiMessage, + ], + id, + item, + ) + + const ids = list.map((x) => x.id) + + expect(ids).toHaveLength(2) + expect(new Set(ids).size).toBe(2) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/project.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/project.test.ts new file mode 100644 index 00000000000..2f7331af489 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/project.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "bun:test" +import { createProject } from "../../../src/legacy-migration/sessions/lib/project" + +describe("legacy migration project", () => { + it("builds a project from the legacy workspace and timestamp", () => { + const project = createProject({ + id: "legacy-task-1", + ts: 1774861014564, + workspace: "/workspace/testing", + task: "Understand this project", + mode: "code", + }) + + expect(project.worktree).toBe("/workspace/testing") + expect(project.sandboxes).toEqual(["/workspace/testing"]) + expect(project.timeCreated).toBe(1774861014564) + expect(project.timeUpdated).toBe(1774861014564) + }) + + it("creates a deterministic project id for the same workspace", () => { + const a = createProject({ id: "legacy-1", workspace: "/workspace/testing" }) + const b = createProject({ id: "legacy-1", workspace: "/workspace/testing" }) + + expect(a.id).toBe(b.id) + }) + + it("falls back to empty defaults when legacy workspace data is missing", () => { + const project = createProject() + + expect(project.worktree).toBe("") + expect(project.sandboxes).toEqual([]) + expect(project.timeCreated).toBe(0) + expect(project.timeUpdated).toBe(0) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/reasoning.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/reasoning.test.ts new file mode 100644 index 00000000000..f5c9a688849 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/reasoning.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, it } from "bun:test" +import { parsePartsFromConversation } from "../../../src/legacy-migration/sessions/lib/parts/parts" +import type { LegacyApiMessage } from "../../../src/legacy-migration/sessions/lib/legacy-types" + +type Reasoning = Extract[number]["data"], { type: "reasoning" }> + +function reasoning(list: ReturnType) { + return list + .filter((x): x is (typeof list)[number] & { data: Reasoning } => x.data.type === "reasoning") + .map((x) => x.data.text) +} + +const id = "legacy-reasoning-1" +const item = { + id, + ts: 1774861014564, + task: "Reasoning parsing test", + workspace: "/workspace/testing", + mode: "code", +} + +describe("legacy migration reasoning", () => { + it("converts type reasoning plus text into a reasoning part", () => { + const list = parsePartsFromConversation( + [ + { + role: "assistant", + type: "reasoning", + text: "I should inspect the files first.", + content: [], + ts: 1774861015000, + } as unknown as LegacyApiMessage, + ], + id, + item, + ) + + const out = reasoning(list) + expect(out).toEqual(["I should inspect the files first."]) + }) + + it("extracts reasoning from reasoning_content", () => { + const list = parsePartsFromConversation( + [ + { + role: "assistant", + content: [], + reasoning_content: "I should read the markdown before answering.", + ts: 1774861015000, + } as unknown as LegacyApiMessage, + ], + id, + item, + ) + + const out = reasoning(list) + expect(out).toEqual(["I should read the markdown before answering."]) + }) + + it("extracts readable reasoning text from reasoning_details", () => { + const list = parsePartsFromConversation( + [ + { + role: "assistant", + content: [], + reasoning_details: [{ text: "First inspect the repo." }, { reasoning: "Then summarize the structure." }], + ts: 1774861015000, + } as unknown as LegacyApiMessage, + ], + id, + item, + ) + + const out = reasoning(list) + expect(out).toHaveLength(1) + expect(out[0]).toContain("First inspect the repo.") + expect(out[0]).toContain("Then summarize the structure.") + }) + + it("prefers explicit reasoning entries over provider-specific reasoning fields when both exist", () => { + const list = parsePartsFromConversation( + [ + { + role: "assistant", + type: "reasoning", + text: "I should inspect the files first.", + reasoning_content: "I should inspect the files first.", + content: [], + ts: 1774861015000, + } as unknown as LegacyApiMessage, + ], + id, + item, + ) + + const out = reasoning(list) + expect(out).toEqual(["I should inspect the files first."]) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/session.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/session.test.ts new file mode 100644 index 00000000000..7764b88ea4d --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/session.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "bun:test" +import { createSession } from "../../../src/legacy-migration/sessions/lib/session" + +describe("legacy migration session", () => { + it("builds a session from legacy task metadata and the resolved project id", () => { + const session = createSession( + "019d3df5-d5d9-73dc-bc2c-43a6304ac62c", + { + id: "019d3df5-d5d9-73dc-bc2c-43a6304ac62c", + ts: 1774861014564, + workspace: "/workspace/testing", + task: "Understand this project", + mode: "code", + }, + "project-1", + ) + + expect(session.projectID).toBe("project-1") + expect(session.directory).toBe("/workspace/testing") + expect(session.title).toBe("Understand this project") + expect(session.version).toBe("v2") + expect(session.timeCreated).toBe(1774861014564) + expect(session.timeUpdated).toBe(1774861014564) + }) + + it("creates a deterministic session id from the legacy task id", () => { + const a = createSession("legacy-task-1", undefined, "project-1") + const b = createSession("legacy-task-1", undefined, "project-1") + + expect(a.id).toBe(b.id) + expect(a.id.startsWith("ses_migrated_")).toBe(true) + }) + + it("falls back to the legacy id as title when task metadata is missing", () => { + const session = createSession("legacy-task-1", undefined, "project-1") + + expect(session.slug).toBe("legacy-task-1") + expect(session.title).toBe("legacy-task-1") + expect(session.directory).toBe("") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/legacy-migration/tools.test.ts b/packages/kilo-vscode/tests/unit/legacy-migration/tools.test.ts new file mode 100644 index 00000000000..e1262a352be --- /dev/null +++ b/packages/kilo-vscode/tests/unit/legacy-migration/tools.test.ts @@ -0,0 +1,134 @@ +import { describe, expect, it } from "bun:test" +import { parsePartsFromConversation } from "../../../src/legacy-migration/sessions/lib/parts/parts" +import type { LegacyApiMessage } from "../../../src/legacy-migration/sessions/lib/legacy-types" + +type Tool = Extract[number]["data"], { type: "tool" }> + +function tools(list: ReturnType) { + return list.filter((x): x is (typeof list)[number] & { data: Tool } => x.data.type === "tool").map((x) => x.data) +} + +const id = "legacy-task-1" +const item = { + id, + ts: 1774861014564, + task: "Tool parsing test", + workspace: "/workspace/testing", + mode: "code", +} + +function fallback(): LegacyApiMessage[] { + return [ + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "toolu_fallback_1", + name: "read_file", + input: { path: "app.py" }, + }, + ], + ts: 1774861015000, + }, + ] +} + +function merged(): LegacyApiMessage[] { + return [ + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "toolu_merge_1", + name: "read_file", + input: { path: "app.py" }, + }, + ], + ts: 1774861015000, + }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_merge_1", + content: [ + { + type: "text", + text: "File: app.py\nHello world", + }, + ], + }, + ], + ts: 1774861016000, + }, + ] +} + +function noTextResult(): LegacyApiMessage[] { + return [ + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "toolu_merge_2", + name: "read_file", + input: { path: "app.py" }, + }, + ], + ts: 1774861015000, + }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_merge_2", + content: [], + }, + ], + ts: 1774861016000, + }, + ] +} + +describe("legacy migration tools", () => { + it("creates a fallback tool part from tool_use when there is no matching tool_result", () => { + const list = parsePartsFromConversation(fallback(), id, item) + const out = tools(list) + + expect(out).toHaveLength(1) + expect(out[0]?.tool).toBe("read_file") + expect(out[0]?.state.status).toBe("completed") + }) + + it("merges tool_use and tool_result into one completed tool part using the correct tool id", () => { + const list = parsePartsFromConversation(merged(), id, item) + const out = tools(list) + + expect(out).toHaveLength(1) + expect(out[0]?.callID).toBe("toolu_merge_1") + expect(out[0]?.tool).toBe("read_file") + if (out[0]?.state.status !== "completed") throw new Error("tool was not completed") + expect(out[0].state.output).toContain("File: app.py") + }) + + it("does not duplicate a tool part when a matching tool_result exists later in the conversation", () => { + const list = parsePartsFromConversation(merged(), id, item) + const out = tools(list) + + expect(out).toHaveLength(1) + }) + + it("falls back to the tool name when tool_result has no readable text content", () => { + const list = parsePartsFromConversation(noTextResult(), id, item) + const out = tools(list) + + expect(out).toHaveLength(1) + if (out[0]?.state.status !== "completed") throw new Error("tool was not completed") + expect(out[0].state.output).toBe("read_file") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/mode-io.test.ts b/packages/kilo-vscode/tests/unit/mode-io.test.ts new file mode 100644 index 00000000000..bb6b99c1ce9 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/mode-io.test.ts @@ -0,0 +1,222 @@ +import { describe, it, expect } from "bun:test" +import { parseImport, buildExport, MAX_IMPORT_SIZE } from "../../webview-ui/src/components/settings/mode-io" + +describe("parseImport", () => { + it("parses a valid full definition", () => { + const json = JSON.stringify({ + name: "reviewer", + description: "Reviews code", + prompt: "You review code.", + model: "anthropic/claude-sonnet-4-20250514", + mode: "primary", + temperature: 0.7, + top_p: 0.9, + steps: 10, + }) + const result = parseImport(json, []) + expect(result).toEqual({ + ok: true, + name: "reviewer", + config: { + description: "Reviews code", + prompt: "You review code.", + model: "anthropic/claude-sonnet-4-20250514", + mode: "primary", + temperature: 0.7, + top_p: 0.9, + steps: 10, + }, + }) + }) + + it("defaults mode to primary when omitted", () => { + const json = JSON.stringify({ name: "my-agent" }) + const result = parseImport(json, []) + expect(result).toEqual({ + ok: true, + name: "my-agent", + config: { mode: "primary" }, + }) + }) + + it("rejects invalid JSON", () => { + expect(parseImport("not json", [])).toEqual({ ok: false, error: "invalidJson" }) + }) + + it("rejects JSON null", () => { + expect(parseImport("null", [])).toEqual({ ok: false, error: "invalidJson" }) + }) + + it("rejects JSON array", () => { + expect(parseImport("[]", [])).toEqual({ ok: false, error: "invalidJson" }) + }) + + it("rejects JSON string", () => { + expect(parseImport('"hello"', [])).toEqual({ ok: false, error: "invalidJson" }) + }) + + it("rejects JSON number", () => { + expect(parseImport("42", [])).toEqual({ ok: false, error: "invalidJson" }) + }) + + it("rejects missing name", () => { + expect(parseImport("{}", [])).toEqual({ ok: false, error: "invalidName" }) + }) + + it("rejects name starting with number", () => { + expect(parseImport(JSON.stringify({ name: "1agent" }), [])).toEqual({ ok: false, error: "invalidName" }) + }) + + it("rejects name with uppercase", () => { + expect(parseImport(JSON.stringify({ name: "MyAgent" }), [])).toEqual({ ok: false, error: "invalidName" }) + }) + + it("rejects name with spaces", () => { + expect(parseImport(JSON.stringify({ name: "my agent" }), [])).toEqual({ ok: false, error: "invalidName" }) + }) + + it("rejects duplicate name", () => { + const json = JSON.stringify({ name: "existing" }) + expect(parseImport(json, ["existing", "other"])).toEqual({ ok: false, error: "nameTaken" }) + }) + + it("ignores invalid mode values", () => { + const json = JSON.stringify({ name: "test", mode: "bogus" }) + const result = parseImport(json, []) + expect(result).toEqual({ + ok: true, + name: "test", + config: { mode: "primary" }, + }) + }) + + it("accepts all valid mode values", () => { + for (const mode of ["subagent", "primary", "all"] as const) { + const json = JSON.stringify({ name: "test", mode }) + const result = parseImport(json, []) + expect(result).toEqual({ ok: true, name: "test", config: { mode } }) + } + }) + + it("ignores non-string and non-number fields", () => { + const json = JSON.stringify({ + name: "test", + description: 123, + prompt: true, + model: [], + temperature: "hot", + top_p: null, + steps: "many", + }) + const result = parseImport(json, []) + expect(result).toEqual({ ok: true, name: "test", config: { mode: "primary" } }) + }) + + it("trims whitespace from name", () => { + const json = JSON.stringify({ name: " trimmed " }) + // "trimmed" doesn't have hyphens or digits so it should be valid + const result = parseImport(json, []) + expect(result).toEqual({ ok: true, name: "trimmed", config: { mode: "primary" } }) + }) + + it("preserves valid permission entries", () => { + const json = JSON.stringify({ + name: "reviewer", + permission: { read: "allow", bash: "allow", edit: "deny", mcp: "ask" }, + }) + const result = parseImport(json, []) + expect(result).toEqual({ + ok: true, + name: "reviewer", + config: { + mode: "primary", + permission: { read: "allow", bash: "allow", edit: "deny", mcp: "ask" }, + }, + }) + }) + + it("drops invalid permission values", () => { + const json = JSON.stringify({ + name: "test", + permission: { read: "allow", bad: "nope", num: 42, arr: [] }, + }) + const result = parseImport(json, []) + expect(result).toEqual({ + ok: true, + name: "test", + config: { mode: "primary", permission: { read: "allow" } }, + }) + }) + + it("preserves nested per-pattern permission rules", () => { + const json = JSON.stringify({ + name: "test", + permission: { bash: { "*": "ask", uname: "allow" }, read: "allow" }, + }) + const result = parseImport(json, []) + expect(result).toEqual({ + ok: true, + name: "test", + config: { + mode: "primary", + permission: { bash: { "*": "ask", uname: "allow" }, read: "allow" }, + }, + }) + }) + + it("ignores non-object permission field", () => { + const json = JSON.stringify({ name: "test", permission: "allow" }) + const result = parseImport(json, []) + expect(result).toEqual({ ok: true, name: "test", config: { mode: "primary" } }) + }) + + it("round-trips permission through export and import", () => { + const cfg = { + mode: "primary" as const, + prompt: "Review code", + permission: { read: "allow" as const, edit: "deny" as const }, + } + const exported = buildExport("reviewer", cfg) + const json = JSON.stringify(exported) + const result = parseImport(json, []) + expect(result).toEqual({ + ok: true, + name: "reviewer", + config: { mode: "primary", prompt: "Review code", permission: { read: "allow", edit: "deny" } }, + }) + }) +}) + +describe("buildExport", () => { + it("includes name and all config fields", () => { + const result = buildExport("reviewer", { + description: "Reviews code", + prompt: "You review code.", + model: "anthropic/claude-sonnet-4-20250514", + mode: "primary", + temperature: 0.7, + top_p: 0.9, + steps: 10, + }) + expect(result).toEqual({ + name: "reviewer", + description: "Reviews code", + prompt: "You review code.", + model: "anthropic/claude-sonnet-4-20250514", + mode: "primary", + temperature: 0.7, + top_p: 0.9, + steps: 10, + }) + }) + + it("handles empty config", () => { + expect(buildExport("minimal", {})).toEqual({ name: "minimal" }) + }) +}) + +describe("MAX_IMPORT_SIZE", () => { + it("is 1 MB", () => { + expect(MAX_IMPORT_SIZE).toBe(1_048_576) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/model-price-format.test.ts b/packages/kilo-vscode/tests/unit/model-price-format.test.ts new file mode 100644 index 00000000000..9203210f352 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/model-price-format.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from "bun:test" +import { fmtPrice } from "../../webview-ui/src/components/shared/model-preview-utils" + +// Prices arriving at fmtPrice are already in $/M tokens (converted by parseApiPrice). +// This test suite guards against the double-multiplication bug where fmtPrice was +// incorrectly multiplying by 1_000_000 again, turning $3.00/1M into $3,000,000.00/1M. + +describe("fmtPrice", () => { + it("formats Claude Sonnet 4.6 input price correctly ($3/1M)", () => { + expect(fmtPrice(3)).toBe("$3.00/1M") + }) + + it("formats Claude Sonnet 4.6 output price correctly ($15/1M)", () => { + expect(fmtPrice(15)).toBe("$15.00/1M") + }) + + it("returns 'Free' for zero price", () => { + expect(fmtPrice(0)).toBe("Free") + }) + + it("uses 4 decimal places for sub-cent prices", () => { + expect(fmtPrice(0.005)).toBe("$0.0050/1M") + }) + + it("uses 2 decimal places at the $0.01 boundary", () => { + expect(fmtPrice(0.01)).toBe("$0.01/1M") + }) + + it("formats a typical cheap model price ($0.50/1M)", () => { + expect(fmtPrice(0.5)).toBe("$0.50/1M") + }) + + it("formats a high-cost model price ($75/1M)", () => { + expect(fmtPrice(75)).toBe("$75.00/1M") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/session-model-store.test.ts b/packages/kilo-vscode/tests/unit/session-model-store.test.ts new file mode 100644 index 00000000000..9d8461be16e --- /dev/null +++ b/packages/kilo-vscode/tests/unit/session-model-store.test.ts @@ -0,0 +1,149 @@ +import { describe, expect, it } from "bun:test" +import { + type ModelStore, + type ResolveEnv, + applyModel, + getSessionModel, + getSelected, +} from "../../webview-ui/src/context/session-model-store" +import type { ModelSelection, Provider } from "../../webview-ui/src/types/messages" + +function makeProvider(id: string, models: string[]): Provider { + const result: Provider = { id, name: id, models: {} } + for (const m of models) { + result.models[m] = { id: m, name: m } + } + return result +} + +const KILO_AUTO: ModelSelection = { providerID: "kilo", modelID: "kilo-auto/free" } + +const providers: Record = { + kilo: makeProvider("kilo", ["kilo-auto/free"]), + anthropic: makeProvider("anthropic", ["claude-sonnet-4"]), + openai: makeProvider("openai", ["gpt-4.1"]), +} + +function env(): ResolveEnv { + return { + providers, + connected: ["kilo", "anthropic", "openai"], + fallback: KILO_AUTO, + getModeModel: () => null, + getGlobalModel: () => null, + } +} + +function emptyStore(): ModelStore { + return { + modelSelections: {}, + sessionOverrides: {}, + agentSelections: {}, + recentModels: [], + } +} + +const claude: ModelSelection = { providerID: "anthropic", modelID: "claude-sonnet-4" } +const gpt: ModelSelection = { providerID: "openai", modelID: "gpt-4.1" } + +describe("per-session model selection", () => { + it("selecting a model in session A does not affect session B", () => { + const store = emptyStore() + const e = env() + + // User picks claude in session A + const after = applyModel(store, "code", claude, "session-a") + const updated: ModelStore = { ...store, ...after } + + // Session A should see claude + expect(getSessionModel(updated, e, "session-a", "code")).toEqual(claude) + + // Session B should NOT see claude — it should fall back to the default + const sessionB = getSessionModel(updated, e, "session-b", "code") + expect(sessionB).toEqual(KILO_AUTO) + }) + + it("each session preserves its own model independently", () => { + let store = emptyStore() + const e = env() + + // User picks claude in session A + const a = applyModel(store, "code", claude, "session-a") + store = { ...store, ...a } + + // User picks gpt in session B + const b = applyModel(store, "code", gpt, "session-b") + store = { ...store, ...b } + + // Both sessions should keep their own model + expect(getSessionModel(store, e, "session-a", "code")).toEqual(claude) + expect(getSessionModel(store, e, "session-b", "code")).toEqual(gpt) + }) + + it("getSelected returns per-session override when session is active", () => { + let store = emptyStore() + const e = env() + + const a = applyModel(store, "code", claude, "session-a") + store = { ...store, ...a } + + expect(getSelected(store, e, "session-a", "code")).toEqual(claude) + }) + + it("getSelected returns global model when no session is active", () => { + let store = emptyStore() + const e = env() + + // Sidebar mode (no session) — writes globally + const result = applyModel(store, "code", claude, undefined) + store = { ...store, ...result } + + expect(getSelected(store, e, undefined, "code")).toEqual(claude) + }) + + it("sidebar model selection writes globally and is visible to new sessions without overrides", () => { + let store = emptyStore() + const e = env() + + // User picks claude in sidebar (no session) + const result = applyModel(store, "code", claude, undefined) + store = { ...store, ...result } + + // A new session without an override should see the global model + expect(getSessionModel(store, e, "session-new", "code")).toEqual(claude) + }) + + it("setSessionModel (compare mode) only writes per-session override", () => { + const store = emptyStore() + + // Simulate setSessionModel — writes only to sessionOverrides + store.sessionOverrides["session-a"] = claude + store.sessionOverrides["session-b"] = gpt + + const e = env() + expect(getSessionModel(store, e, "session-a", "code")).toEqual(claude) + expect(getSessionModel(store, e, "session-b", "code")).toEqual(gpt) + }) + + it("switching sessions preserves model selection after multiple changes", () => { + let store = emptyStore() + const e = env() + + // Simulate: user in session A picks claude + let result = applyModel(store, "code", claude, "session-a") + store = { ...store, ...result } + + // Switch to session B — picks gpt + result = applyModel(store, "code", gpt, "session-b") + store = { ...store, ...result } + + // Switch back to session A — picks gpt this time + result = applyModel(store, "code", gpt, "session-a") + store = { ...store, ...result } + + // Switch back to session B — should still have gpt + expect(getSessionModel(store, e, "session-b", "code")).toEqual(gpt) + // Session A was updated to gpt + expect(getSessionModel(store, e, "session-a", "code")).toEqual(gpt) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/session-status.test.ts b/packages/kilo-vscode/tests/unit/session-status.test.ts new file mode 100644 index 00000000000..62478587521 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/session-status.test.ts @@ -0,0 +1,218 @@ +import { describe, it, expect } from "bun:test" +import { seedSessionStatuses, getBusySessionCount } from "../../src/session-status" +import type { SessionStatus } from "@kilocode/sdk/v2/client" + +/** + * Minimal fake client that satisfies the KiloClient.session.status() call. + * Returns controlled data or throws to simulate server errors. + */ +function createClient(response: { data: Record | null } | Error) { + return { + session: { + status: async (_params: { directory: string }) => { + if (response instanceof Error) throw response + return response + }, + }, + } as Parameters[0] +} + +function collect() { + const msgs: unknown[] = [] + return { msgs, post: (msg: unknown) => msgs.push(msg) } +} + +// --------------------------------------------------------------------------- +// seedSessionStatuses +// --------------------------------------------------------------------------- + +describe("seedSessionStatuses", () => { + it("seeds map and posts messages for non-idle sessions", async () => { + const client = createClient({ + data: { + s1: { type: "busy" }, + s2: { type: "retry", attempt: 3, message: "rate limited", next: 5000 }, + }, + }) + const map = new Map() + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + expect(map.get("s1")).toBe("busy") + expect(map.get("s2")).toBe("retry") + expect(msgs).toEqual([ + { type: "sessionStatus", sessionID: "s1", status: "busy" }, + { type: "sessionStatus", sessionID: "s2", status: "retry", attempt: 3, message: "rate limited", next: 5000 }, + ]) + }) + + // ---- THE BUG: stale entries not cleared on reconnect ---- + + it("clears stale busy entries absent from server response", async () => { + const client = createClient({ data: {} }) + const map = new Map([["s1", "busy"]]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + expect(map.get("s1")).toBe("idle") + expect(msgs).toEqual([{ type: "sessionStatus", sessionID: "s1", status: "idle" }]) + }) + + it("clears stale retry entries absent from server response", async () => { + const client = createClient({ data: {} }) + const map = new Map([["s1", "retry"]]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + expect(map.get("s1")).toBe("idle") + expect(msgs).toEqual([{ type: "sessionStatus", sessionID: "s1", status: "idle" }]) + }) + + it("preserves entries that server confirms as still active", async () => { + const client = createClient({ data: { s1: { type: "busy" } } }) + const map = new Map([["s1", "busy"]]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + expect(map.get("s1")).toBe("busy") + expect(msgs).toEqual([{ type: "sessionStatus", sessionID: "s1", status: "busy" }]) + }) + + it("does not send redundant idle for already-idle entries", async () => { + const client = createClient({ data: {} }) + const map = new Map([["s1", "idle"]]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + // Already idle — no message should be posted + expect(msgs).toEqual([]) + expect(map.get("s1")).toBe("idle") + }) + + it("handles mixed: some stale, some confirmed, some new", async () => { + const client = createClient({ + data: { + confirmed: { type: "busy" }, + fresh: { type: "busy" }, + }, + }) + const map = new Map([ + ["stale", "busy"], + ["confirmed", "retry"], + ]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + // stale: was busy locally, absent from server → idle + expect(map.get("stale")).toBe("idle") + // confirmed: was retry locally, server says busy → busy + expect(map.get("confirmed")).toBe("busy") + // fresh: new from server → busy + expect(map.get("fresh")).toBe("busy") + // Messages: server entries first (confirmed, fresh), then stale reconciliation + expect(msgs).toEqual([ + { type: "sessionStatus", sessionID: "confirmed", status: "busy" }, + { type: "sessionStatus", sessionID: "fresh", status: "busy" }, + { type: "sessionStatus", sessionID: "stale", status: "idle" }, + ]) + }) + + it("handles server error gracefully — no map changes", async () => { + const client = createClient(new Error("connection refused")) + const map = new Map([["s1", "busy"]]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + // Map unchanged on error — conservative behavior + expect(map.get("s1")).toBe("busy") + expect(msgs).toEqual([]) + }) + + it("handles null data response — no map changes", async () => { + const client = createClient({ data: null }) + const map = new Map([["s1", "busy"]]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post) + + // Null data could mean a non-2xx response (401/500), not "no active sessions". + // Conservative: leave map unchanged to avoid false-clearing busy sessions. + expect(map.get("s1")).toBe("busy") + expect(msgs).toEqual([]) + }) + + // ---- reconcile=false (SSE reconnect) ---- + + it("skips reconciliation when reconcile=false", async () => { + const client = createClient({ data: {} }) + const map = new Map([["s1", "busy"]]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post, false) + + // Session stays busy — reconciliation skipped on SSE reconnect + expect(map.get("s1")).toBe("busy") + expect(msgs).toEqual([]) + }) + + it("still seeds server entries when reconcile=false", async () => { + const client = createClient({ + data: { s1: { type: "busy" }, s2: { type: "retry", attempt: 1, message: "err", next: 1000 } }, + }) + const map = new Map() + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post, false) + + expect(map.get("s1")).toBe("busy") + expect(map.get("s2")).toBe("retry") + expect(msgs).toEqual([ + { type: "sessionStatus", sessionID: "s1", status: "busy" }, + { type: "sessionStatus", sessionID: "s2", status: "retry", attempt: 1, message: "err", next: 1000 }, + ]) + }) + + it("does not reset stale entries when reconcile=false but updates confirmed ones", async () => { + const client = createClient({ data: { confirmed: { type: "busy" } } }) + const map = new Map([ + ["stale", "busy"], + ["confirmed", "retry"], + ]) + const { msgs, post } = collect() + + await seedSessionStatuses(client, "/repo", map, post, false) + + // stale: stays busy (no reconciliation) + expect(map.get("stale")).toBe("busy") + // confirmed: updated to busy from server + expect(map.get("confirmed")).toBe("busy") + expect(msgs).toEqual([{ type: "sessionStatus", sessionID: "confirmed", status: "busy" }]) + }) +}) + +// --------------------------------------------------------------------------- +// getBusySessionCount +// --------------------------------------------------------------------------- + +describe("getBusySessionCount", () => { + it("returns 0 for empty map", () => { + expect(getBusySessionCount(new Map())).toBe(0) + }) + + it("counts only busy entries, not idle or retry", () => { + const map = new Map([ + ["a", "busy"], + ["b", "idle"], + ["c", "retry"], + ["d", "busy"], + ]) + expect(getBusySessionCount(map)).toBe(2) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/session-utils.test.ts b/packages/kilo-vscode/tests/unit/session-utils.test.ts index bd18a874ef7..07a873c08f9 100644 --- a/packages/kilo-vscode/tests/unit/session-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/session-utils.test.ts @@ -1,5 +1,12 @@ import { describe, it, expect } from "bun:test" -import { computeStatus, calcTotalCost, calcContextUsage } from "../../webview-ui/src/context/session-utils" +import { + computeStatus, + calcTotalCost, + calcContextUsage, + buildFamilyCosts, + buildFamilyLabels, + buildCostBreakdown, +} from "../../webview-ui/src/context/session-utils" import type { Part } from "../../webview-ui/src/types/messages" const t = (key: string) => key @@ -125,3 +132,154 @@ describe("calcContextUsage", () => { expect(result.percentage).toBe(10) }) }) + +// ── Cost breakdown helpers ────────────────────────────────────────────── + +function msg(id: string, role: string, cost?: number) { + return { id, role, cost } +} + +function toolPart(tool: string, sessionId?: string, input?: { subagent_type?: string; description?: string }) { + return { + type: "tool" as const, + tool, + state: { + input: input ?? {}, + metadata: sessionId ? { sessionId } : {}, + }, + } +} + +describe("buildFamilyCosts", () => { + it("returns empty map for empty family", () => { + expect(buildFamilyCosts(new Set(), {}).size).toBe(0) + }) + + it("sums costs per session, skipping zero-cost sessions", () => { + const family = new Set(["s1", "s2", "s3"]) + const messages = { + s1: [msg("m1", "assistant", 0.05), msg("m2", "assistant", 0.03)], + s2: [msg("m3", "user", 999), msg("m4", "assistant", 0)], + s3: [msg("m5", "assistant", 0.1)], + } + const costs = buildFamilyCosts(family, messages) + expect(costs.size).toBe(2) + expect(costs.get("s1")).toBeCloseTo(0.08) + expect(costs.has("s2")).toBe(false) + expect(costs.get("s3")).toBeCloseTo(0.1) + }) + + it("handles missing messages for a family member", () => { + const family = new Set(["s1", "s2"]) + const messages = { s1: [msg("m1", "assistant", 0.01)] } + const costs = buildFamilyCosts(family, messages) + expect(costs.size).toBe(1) + expect(costs.get("s1")).toBeCloseTo(0.01) + }) +}) + +describe("buildFamilyLabels", () => { + it("returns empty map when no task tool parts exist", () => { + const family = new Set(["s1"]) + const messages = { s1: [msg("m1", "assistant")] } + const parts = { m1: [{ type: "text" }] } + expect(buildFamilyLabels(family, messages as any, parts as any).size).toBe(0) + }) + + it("extracts label from subagent_type", () => { + const family = new Set(["s1", "child1"]) + const messages = { s1: [msg("m1", "assistant")] } + const parts = { + m1: [toolPart("task", "child1", { subagent_type: "explore" })], + } + const labels = buildFamilyLabels(family, messages as any, parts as any) + expect(labels.get("child1")).toBe("explore") + }) + + it("falls back to description when subagent_type is absent", () => { + const family = new Set(["s1", "child1"]) + const messages = { s1: [msg("m1", "assistant")] } + const parts = { + m1: [toolPart("task", "child1", { description: "Fix the bug" })], + } + const labels = buildFamilyLabels(family, messages as any, parts as any) + expect(labels.get("child1")).toBe("Fix the bug") + }) + + it("falls back to tool name when no input fields", () => { + const family = new Set(["s1", "child1"]) + const messages = { s1: [msg("m1", "assistant")] } + const parts = { + m1: [toolPart("task", "child1")], + } + const labels = buildFamilyLabels(family, messages as any, parts as any) + expect(labels.get("child1")).toBe("task") + }) + + it("truncates labels longer than 24 chars", () => { + const family = new Set(["s1", "child1"]) + const messages = { s1: [msg("m1", "assistant")] } + const parts = { + m1: [toolPart("task", "child1", { description: "A very long description that exceeds the cap" })], + } + const labels = buildFamilyLabels(family, messages as any, parts as any) + const label = labels.get("child1")! + expect(label.length).toBeLessThanOrEqual(24) + expect(label.endsWith("…")).toBe(true) + }) + + it("ignores child sessions not in the family set", () => { + const family = new Set(["s1"]) + const messages = { s1: [msg("m1", "assistant")] } + const parts = { + m1: [toolPart("task", "orphan", { subagent_type: "general" })], + } + const labels = buildFamilyLabels(family, messages as any, parts as any) + expect(labels.size).toBe(0) + }) + + it("uses first label when multiple parts reference same child", () => { + const family = new Set(["s1", "child1"]) + const messages = { s1: [msg("m1", "assistant"), msg("m2", "assistant")] } + const parts = { + m1: [toolPart("task", "child1", { subagent_type: "first" })], + m2: [toolPart("task", "child1", { subagent_type: "second" })], + } + const labels = buildFamilyLabels(family, messages as any, parts as any) + expect(labels.get("child1")).toBe("first") + }) +}) + +describe("buildCostBreakdown", () => { + it("returns empty array for empty costs", () => { + expect(buildCostBreakdown("s1", new Map(), new Map(), "This session")).toEqual([]) + }) + + it("labels root session with the provided rootLabel", () => { + const costs = new Map([["s1", 0.05]]) + const result = buildCostBreakdown("s1", costs, new Map(), "This session") + expect(result).toEqual([{ label: "This session", cost: 0.05 }]) + }) + + it("labels child sessions from the labels map", () => { + const costs = new Map([ + ["s1", 0.05], + ["child1", 0.03], + ]) + const labels = new Map([["child1", "explore"]]) + const result = buildCostBreakdown("s1", costs, labels, "This session") + expect(result).toEqual([ + { label: "This session", cost: 0.05 }, + { label: "explore", cost: 0.03 }, + ]) + }) + + it("falls back to truncated session ID for unlabeled children", () => { + const costs = new Map([ + ["s1", 0.05], + ["abcdef1234567890", 0.02], + ]) + const result = buildCostBreakdown("s1", costs, new Map(), "This session") + expect(result[1].label).toBe("abcdef12") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/settings-io.test.ts b/packages/kilo-vscode/tests/unit/settings-io.test.ts new file mode 100644 index 00000000000..142f1e82ad1 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/settings-io.test.ts @@ -0,0 +1,397 @@ +import { describe, it, expect } from "bun:test" +import { + buildExport, + parseImport, + mergeConfig, + MAX_IMPORT_SIZE, + KNOWN_KEYS, + META_VERSION, +} from "../../webview-ui/src/components/settings/settings-io" +import type { Config } from "../../webview-ui/src/types/messages" + +// --------------------------------------------------------------------------- +// buildExport +// --------------------------------------------------------------------------- +describe("buildExport", () => { + it("wraps config in _meta envelope", () => { + const cfg: Config = { model: "anthropic/claude-sonnet-4-20250514" } + const result = buildExport(cfg) + expect(result._meta).toBeDefined() + expect(result._meta.version).toBe(META_VERSION) + expect(typeof result._meta.exportedAt).toBe("string") + expect(result.model).toBe("anthropic/claude-sonnet-4-20250514") + }) + + it("preserves provider fields including secrets", () => { + const cfg: Config = { + provider: { + openai: { name: "OpenAI", api_key: "sk-secret-123" }, + custom: { name: "Custom", options: { apiKey: "secret", baseURL: "https://example.com" } }, + }, + } + const result = buildExport(cfg) + expect(result.provider.openai.api_key).toBe("sk-secret-123") + expect(result.provider.openai.name).toBe("OpenAI") + expect(result.provider.custom.options.apiKey).toBe("secret") + expect(result.provider.custom.options.baseURL).toBe("https://example.com") + }) + + it("preserves mcp fields including env and headers", () => { + const cfg: Config = { + mcp: { + github: { + type: "local" as const, + command: "npx", + env: { GITHUB_TOKEN: "ghp_secret123" }, + enabled: true, + }, + server: { + type: "local" as const, + command: "node", + environment: { SECRET: "val" }, + }, + remote: { + type: "remote" as const, + url: "https://mcp.example.com", + headers: { Authorization: "Bearer secret-token" }, + enabled: true, + }, + }, + } + const result = buildExport(cfg) + expect(result.mcp.github.env.GITHUB_TOKEN).toBe("ghp_secret123") + expect(result.mcp.github.command).toBe("npx") + expect(result.mcp.server.environment.SECRET).toBe("val") + expect(result.mcp.remote.headers.Authorization).toBe("Bearer secret-token") + expect(result.mcp.remote.url).toBe("https://mcp.example.com") + }) + + it("preserves all config fields", () => { + const cfg: Config = { + model: "test-model", + small_model: "test-small", + default_agent: "coder", + agent: { coder: { mode: "primary", prompt: "Code stuff" } }, + permission: { read: "allow" }, + instructions: ["rule1.md"], + snapshot: true, + share: "manual", + } + const result = buildExport(cfg) + expect(result.model).toBe("test-model") + expect(result.small_model).toBe("test-small") + expect(result.default_agent).toBe("coder") + expect(result.agent).toEqual({ coder: { mode: "primary", prompt: "Code stuff" } }) + expect(result.permission).toEqual({ read: "allow" }) + expect(result.instructions).toEqual(["rule1.md"]) + expect(result.snapshot).toBe(true) + expect(result.share).toBe("manual") + }) + + it("handles empty config", () => { + const result = buildExport({}) + expect(result._meta).toBeDefined() + expect(Object.keys(result).length).toBe(1) // only _meta + }) + + it("handles config with no providers or mcp", () => { + const cfg: Config = { model: "test" } + const result = buildExport(cfg) + expect(result.model).toBe("test") + expect(result.provider).toBeUndefined() + expect(result.mcp).toBeUndefined() + }) +}) + +// --------------------------------------------------------------------------- +// parseImport +// --------------------------------------------------------------------------- +describe("parseImport", () => { + it("rejects non-JSON", () => { + const result = parseImport("not json at all") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON null", () => { + const result = parseImport("null") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON array", () => { + const result = parseImport("[]") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON string", () => { + const result = parseImport('"hello"') + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON number", () => { + const result = parseImport("42") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects empty object (no known keys)", () => { + const result = parseImport("{}") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidConfig") + }) + + it("rejects object with only unknown keys", () => { + const result = parseImport(JSON.stringify({ foo: "bar", baz: 42 })) + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidConfig") + }) + + it("accepts valid partial config (just agent)", () => { + const json = JSON.stringify({ + agent: { coder: { mode: "primary", prompt: "Code things" } }, + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.agent).toEqual({ coder: { mode: "primary", prompt: "Code things" } }) + } + }) + + it("accepts valid partial config (just model)", () => { + const json = JSON.stringify({ model: "test-model" }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) expect(result.config.model).toBe("test-model") + }) + + it("strips _meta before returning config", () => { + const json = JSON.stringify({ + _meta: { version: 1, exportedAt: "2026-01-01", secretsStripped: true }, + model: "test", + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect((result.config as Record)._meta).toBeUndefined() + expect(result.config.model).toBe("test") + } + }) + + it("strips unknown top-level keys", () => { + const json = JSON.stringify({ + model: "test", + unknownField: "should be stripped", + anotherUnknown: 42, + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.model).toBe("test") + expect((result.config as Record).unknownField).toBeUndefined() + expect((result.config as Record).anotherUnknown).toBeUndefined() + } + }) + + it("returns warning when _meta.version > current", () => { + const json = JSON.stringify({ + _meta: { version: 999 }, + model: "test", + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.warning).toBe("newerVersion") + } + }) + + it("returns no warning when _meta.version <= current", () => { + const json = JSON.stringify({ + _meta: { version: META_VERSION }, + model: "test", + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.warning).toBeUndefined() + } + }) + + it("returns no warning when _meta is absent", () => { + const json = JSON.stringify({ model: "test" }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.warning).toBeUndefined() + } + }) + + it("accepts config with all known keys", () => { + const cfg: Record = {} + for (const key of KNOWN_KEYS) { + cfg[key] = key === "instructions" ? ["rule.md"] : key === "snapshot" ? true : "value" + } + const json = JSON.stringify(cfg) + const result = parseImport(json) + expect(result.ok).toBe(true) + }) + + it("preserves provider and mcp fields as-is (including secrets on import)", () => { + const json = JSON.stringify({ + provider: { openai: { name: "OpenAI", api_key: "sk-123" } }, + mcp: { gh: { command: "npx", env: { TOKEN: "secret" } } }, + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.provider?.openai?.api_key).toBe("sk-123") + expect(result.config.mcp?.gh?.env?.TOKEN).toBe("secret") + } + }) +}) + +// --------------------------------------------------------------------------- +// mergeConfig +// --------------------------------------------------------------------------- +describe("mergeConfig", () => { + it("merges imported agents with existing agents", () => { + const existing: Config = { + agent: { + coder: { mode: "primary", prompt: "Code" }, + reviewer: { mode: "primary", prompt: "Review" }, + }, + } + const imported: Config = { + agent: { + reviewer: { mode: "primary", prompt: "Updated review" }, + planner: { mode: "primary", prompt: "Plan" }, + }, + } + const result = mergeConfig(existing, imported) + expect(result.agent?.coder?.prompt).toBe("Code") + expect(result.agent?.reviewer?.prompt).toBe("Updated review") + expect(result.agent?.planner?.prompt).toBe("Plan") + }) + + it("imported values override existing for same keys", () => { + const existing: Config = { model: "old-model", default_agent: "coder" } + const imported: Config = { model: "new-model" } + const result = mergeConfig(existing, imported) + expect(result.model).toBe("new-model") + expect(result.default_agent).toBe("coder") + }) + + it("existing values not in import are preserved", () => { + const existing: Config = { + model: "test", + permission: { read: "allow" }, + instructions: ["old.md"], + } + const imported: Config = { model: "updated" } + const result = mergeConfig(existing, imported) + expect(result.model).toBe("updated") + expect(result.permission).toEqual({ read: "allow" }) + expect(result.instructions).toEqual(["old.md"]) + }) + + it("merges providers without losing existing ones", () => { + const existing: Config = { + provider: { + openai: { name: "OpenAI", api_key: "sk-existing" }, + anthropic: { name: "Anthropic", api_key: "sk-ant" }, + }, + } + const imported: Config = { + provider: { + openai: { name: "OpenAI Updated", base_url: "https://new.api" }, + }, + } + const result = mergeConfig(existing, imported) + expect(result.provider?.openai?.name).toBe("OpenAI Updated") + expect(result.provider?.openai?.base_url).toBe("https://new.api") + expect(result.provider?.anthropic?.name).toBe("Anthropic") + expect(result.provider?.anthropic?.api_key).toBe("sk-ant") + }) + + it("handles empty existing config", () => { + const imported: Config = { model: "test", agent: { coder: { mode: "primary" } } } + const result = mergeConfig({}, imported) + expect(result.model).toBe("test") + expect(result.agent?.coder?.mode).toBe("primary") + }) + + it("handles empty imported config", () => { + const existing: Config = { model: "test" } + const result = mergeConfig(existing, {}) + expect(result.model).toBe("test") + }) +}) + +// --------------------------------------------------------------------------- +// Round-trip +// --------------------------------------------------------------------------- +describe("round-trip", () => { + it("export then import preserves all fields including secrets", () => { + const original: Config = { + model: "test-model", + agent: { coder: { mode: "primary", prompt: "Code" } }, + provider: { openai: { name: "OpenAI", api_key: "sk-secret" } }, + mcp: { gh: { command: "npx", env: { TOKEN: "secret" } } }, + permission: { read: "allow" }, + instructions: ["rules.md"], + } + const exported = buildExport(original) + const json = JSON.stringify(exported) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.model).toBe("test-model") + expect(result.config.agent).toEqual({ coder: { mode: "primary", prompt: "Code" } }) + expect(result.config.provider?.openai?.name).toBe("OpenAI") + expect(result.config.provider?.openai?.api_key).toBe("sk-secret") + expect(result.config.mcp?.gh?.command).toBe("npx") + expect(result.config.mcp?.gh?.env?.TOKEN).toBe("secret") + expect(result.config.permission).toEqual({ read: "allow" }) + expect(result.config.instructions).toEqual(["rules.md"]) + } + }) +}) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- +describe("constants", () => { + it("MAX_IMPORT_SIZE is 1 MB", () => { + expect(MAX_IMPORT_SIZE).toBe(1_048_576) + }) + + it("META_VERSION is 1", () => { + expect(META_VERSION).toBe(1) + }) + + it("KNOWN_KEYS includes core config keys", () => { + expect(KNOWN_KEYS).toContain("agent") + expect(KNOWN_KEYS).toContain("provider") + expect(KNOWN_KEYS).toContain("mcp") + expect(KNOWN_KEYS).toContain("permission") + expect(KNOWN_KEYS).toContain("model") + expect(KNOWN_KEYS).toContain("instructions") + }) + + it("KNOWN_KEYS matches all keys in the Config interface (drift guard)", async () => { + // Read the Config interface from messages.ts and extract its keys. + // If someone adds a new field to Config, this test fails as a reminder + // to also add it to KNOWN_KEYS in settings-io.ts. + const src = await Bun.file(require("path").join(__dirname, "../../webview-ui/src/types/messages.ts")).text() + const match = src.match(/export interface Config \{([^}]+)\}/) + expect(match).not.toBeNull() + const body = match![1] + const keys = [...body.matchAll(/^\s+(\w+)\??:/gm)].map((m) => m[1]) + expect(keys.length).toBeGreaterThan(0) + + const sorted = (arr: readonly string[]) => [...arr].sort() + expect(sorted(KNOWN_KEYS)).toEqual(sorted(keys)) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts b/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts index 8e0a18a7066..5b0c2af1be2 100644 --- a/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts +++ b/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts @@ -548,9 +548,8 @@ describe("WorktreeStateManager", () => { await manager.load() - // path.sep is / on unix, \ on windows — the rewrite uses path.sep - const expected = `C:${path.sep}.kilo${path.sep}worktrees\\fix` - expect(manager.getWorktrees()[0].path).toBe(expected) + // Separator style from the stored path is preserved (backslashes stay as backslashes) + expect(manager.getWorktrees()[0].path).toBe("C:\\.kilo\\worktrees\\fix") }) }) }) diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png index 78b20d2cc55..ff2af5838d7 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5ada07e94ff10490732ba4b27d8e204102816aa00e472324a31ee9f8c7e8484 -size 9470 +oid sha256:6442d82e137e36d165e5f10ca7dbf02445badf109d442eac05714c50593068fe +size 7819 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png index 9654bb3568b..e09f627f07c 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ec496479f7d18175e17a6a3da4637a1e6b341937350b27c8a34c77d81b581e7 -size 18780 +oid sha256:9dbdd4b71bea71529e8835def452bbb627837d5bb6dbe460d882b20c4add6e98 +size 17650 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png index 64504449b57..74a826d3453 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b971f7a2234c16abbeeda98d9ee4f024a4ff46e63f7a6e5cc900a1e893bde6e -size 18934 +oid sha256:65686e2d0978adcf09ae504d9414e2d81f304eb18ff2275be7a33a9c3579a5e5 +size 18209 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-agents-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-agents-chromium-linux.png index 18c47d09744..795674c1ad6 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-agents-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-agents-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb1fd55e0eb2c59616fe618a02fd91f8c7ab004d886a7ffa13e9f37b4f3ed8be -size 25128 +oid sha256:8e9f04f72f6227b08bff321ab4af7a794fa754dc58bd504eb88259cd244ba51b +size 27146 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-edit-custom-mode-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-edit-custom-mode-chromium-linux.png index b0285f84646..44b6c259a48 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-edit-custom-mode-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-edit-custom-mode-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84a03047098fe237bcd3e278e66c07d9e75c222c306e4e7ac9cf065cb58fb5b5 -size 40689 +oid sha256:efb6975f1abb96f56667d7efa907061bf9a30457cc077c6d6353fe6e46581170 +size 41301 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-workflows-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-workflows-chromium-linux.png new file mode 100644 index 00000000000..62e16e88ba8 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-workflows-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73e777b0e52be0bb66ae3dfdcf793a7496de4cdc0e49b1d20da2877a6632257b +size 23171 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-workflows-empty-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-workflows-empty-chromium-linux.png new file mode 100644 index 00000000000..c10b0839cf7 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/agent-behaviour-workflows-empty-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb348d4f380e432a92bfb43100f3b9949d4be97d2c9b62439f8862d5c1db1c2d +size 21344 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-local-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-local-chromium-linux.png new file mode 100644 index 00000000000..6a0acb42d64 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-local-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31d595b148c5efa68c023eff922e1eedf15b2d4e8af91efc18c99b7830238999 +size 26230 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-local-with-env-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-local-with-env-chromium-linux.png new file mode 100644 index 00000000000..8f6bf7613d5 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-local-with-env-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:220e3129994f8cb0cb0ead6fc51b9965e0e9cd2ed24269f1a65d065bea4c9d92 +size 26970 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-remote-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-remote-chromium-linux.png new file mode 100644 index 00000000000..40f6ee9d981 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mcp-edit-view-remote-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f90ee0aa1073be693836469cfd63e1bb3a2f55edcdc964dae1f68b73b12fc367 +size 12016 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mode-edit-export-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mode-edit-export-chromium-linux.png new file mode 100644 index 00000000000..f7c6fb041c5 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/mode-edit-export-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dcc9ddf87e9c5b6e5cf736bfa5d42005020d14e2bb6fe7cd99a82a149db3e5c +size 37073 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png index ed6030cc17a..9b54450a100 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ad2458e2fe4e735c09fe89292c238f4b3113d2fa7dab5353889f0fc885400a7 -size 30038 +oid sha256:b2b55baed16075ce7e53185de00d0661e2ba4850a4f7672ea771048898c3905a +size 25742 diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 9df33f467e8..08917a8ecc4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -72,6 +72,7 @@ import { VSCodeProvider, useVSCode } from "../src/context/vscode" import { ServerProvider } from "../src/context/server" import { ProviderProvider } from "../src/context/provider" import { ConfigProvider } from "../src/context/config" +import { NotificationsProvider } from "../src/context/notifications" import { SessionProvider, useSession } from "../src/context/session" import { WorktreeModeProvider } from "../src/context/worktree-mode" import { ChatView } from "../src/components/chat" @@ -1789,6 +1790,21 @@ const AgentManagerContent: Component = () => { vscode.postMessage({ type: "agentManager.promoteSession", sessionId }) } + const openLocally = (sid: string) => { + saveTabMemory() + const pending = activePendingId() + if (pending) { + setLocalSessionIDs((prev) => prev.map((id) => (id === pending ? sid : id))) + setActivePendingId(undefined) + } else { + setLocalSessionIDs((prev) => [...prev, sid]) + } + setSelection(LOCAL) + setReviewActive(false) + session.selectSession(sid) + vscode.postMessage({ type: "agentManager.openLocally", sessionId: sid }) + } + const handleAddSession = () => { const sel = selection() if (sel === LOCAL) { @@ -2433,6 +2449,10 @@ const AgentManagerContent: Component = () => { {t("agentManager.session.openInWorktree")} + openLocally(s.id)}> + + {t("agentManager.session.openLocally")} + @@ -2730,15 +2750,42 @@ const AgentManagerContent: Component = () => {
{ - // If on local and selecting a different session, keep local context - session.selectSession(id) + if (localSessionIDs().includes(id)) { + session.selectSession(id) + if (selection() === null) setSelection(LOCAL) + return + } + // Navigate to owning worktree instead of forcing into local mode + if (worktreeSessionIds().has(id)) { + const ms = managedSessions().find((s) => s.id === id) + if (ms?.worktreeId) { + selectWorktree(ms.worktreeId) + session.selectSession(id) + setReviewActive(false) + return + } + } + openLocally(id) }} readonly={readOnly()} + continueInWorktree={selection() === LOCAL} />
{t("agentManager.session.readonly")} + - - - +
+ + + + + + + + + + 1 ? "s" : ""} changed · +${session.worktreeStats()!.additions} -${session.worktreeStats()!.deletions}` + : "No file changes" + } + placement="top" + class="session-diff-wrapper" + > + + + +
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx index 7beffc2fc00..d6cd8121d1b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx @@ -6,6 +6,7 @@ import { useProvider } from "../../context/provider" import { useLanguage } from "../../context/language" import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model" import { TelemetryEventName } from "../../../../src/services/telemetry/types" +import { stripSubProviderPrefix } from "../shared/model-selector-utils" export const KiloNotifications: Component = () => { const { filteredNotifications, dismiss } = useNotifications() @@ -61,6 +62,14 @@ export const KiloNotifications: Component = () => { return true }) + const suggestedName = createMemo(() => { + const suggestion = suggestedModel() + if (!suggestion) return undefined + const model = provider.findModel(suggestion) + if (!model?.name) return undefined + return stripSubProviderPrefix(model.name) + }) + const handleTryModel = () => { const suggestion = suggestedModel() if (!suggestion) return @@ -88,7 +97,7 @@ export const KiloNotifications: Component = () => { -
- - - {language.t("session.cloud.repoOnly") ?? "Only this repository"} - - - -
-
+
items={sessions()} key={(s) => s.id} @@ -164,7 +133,18 @@ const CloudSessionList: Component = (props) => { onSelect={(s) => { if (s) props.onSelectSession?.(s.id) }} - search={{ placeholder: language.t("session.search.placeholder"), autofocus: false }} + search={{ + placeholder: language.t("session.search.placeholder"), + autofocus: false, + action: + gitUrl() !== null ? ( +
+ + {language.t("session.cloud.repoOnly") ?? "Only this repository"} + +
+ ) : undefined, + }} emptyMessage={ loading() ? (language.t("common.loading") ?? "Loading...") : (language.t("session.empty") ?? "No sessions") } @@ -183,9 +163,9 @@ const CloudSessionList: Component = (props) => {
- +
diff --git a/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx b/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx new file mode 100644 index 00000000000..3283d1a4ccc --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx @@ -0,0 +1,80 @@ +/** + * HistoryView component + * Unified panel for local and cloud session history. + * Contains a tab bar ("Local" | "Cloud") and an always-visible "Import session" button. + */ + +import { Component, createSignal } from "solid-js" +import { Button } from "@kilocode/kilo-ui/button" +import { useDialog } from "@kilocode/kilo-ui/context/dialog" +import { useLanguage } from "../../context/language" +import { useSession } from "../../context/session" +import { CloudImportDialog } from "../chat/CloudImportDialog" +import SessionList from "./SessionList" +import CloudSessionList from "./CloudSessionList" + +interface HistoryViewProps { + onSelectSession: (id: string) => void + onBack?: () => void +} + +const HistoryView: Component = (props) => { + const language = useLanguage() + const dialog = useDialog() + const session = useSession() + const [tab, setTab] = createSignal<"local" | "cloud">("local") + + function openImport() { + dialog.show(() => ( + { + selectCloudSession(id) + }} + /> + )) + } + + function selectCloudSession(id: string) { + session.selectCloudSession(id) + props.onBack?.() + } + + return ( +
+
+ +
+ + +
+ +
+ +
+ {tab() === "local" ? ( + + ) : ( + + )} +
+
+ ) +} + +export default HistoryView diff --git a/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx index 27722bb20d7..e2f84cfde37 100644 --- a/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx @@ -2,6 +2,7 @@ * SessionList component * Displays all sessions grouped by date, with context menu for rename/delete. * Uses kilo-ui List component for keyboard navigation and accessibility. + * Header/back button are owned by the parent HistoryView. */ import { Component, Show, createSignal, onMount, type JSX } from "solid-js" @@ -37,7 +38,6 @@ function dateGroupKey(iso: string): (typeof DATE_GROUP_KEYS)[number] { interface SessionListProps { onSelectSession: (id: string) => void - onBack?: () => void } const SessionList: Component = (props) => { @@ -131,11 +131,6 @@ const SessionList: Component = (props) => { return (
-
- -
items={session.sessions()} key={(s) => s.id} diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css index d3f96e688a2..bf56c52d0c4 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css @@ -214,6 +214,7 @@ display: flex; flex-direction: column; gap: 16px; + padding: 0 20px 20px; } .install-modal-section { @@ -247,7 +248,7 @@ .install-modal-result { text-align: center; - padding: 12px 0; + padding: 12px 20px 20px; } .install-modal-success { diff --git a/packages/kilo-vscode/webview-ui/src/components/migration/MigrationWizard.tsx b/packages/kilo-vscode/webview-ui/src/components/migration/MigrationWizard.tsx index 87a27fea7fb..deca1fdb570 100644 --- a/packages/kilo-vscode/webview-ui/src/components/migration/MigrationWizard.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/migration/MigrationWizard.tsx @@ -6,7 +6,9 @@ * 2. Migrate — grouped selection, live in-place progress, cleanup */ -import { Component, Show, createSignal, onMount, onCleanup, JSX } from "solid-js" +import { Show, createSignal, onMount, onCleanup } from "solid-js" +import type { Component, JSX } from "solid-js" +import { showToast } from "@kilocode/kilo-ui/toast" import { useVSCode } from "../../context/vscode" import { useLanguage } from "../../context/language" import type { @@ -153,7 +155,7 @@ const WarningSvg = (): JSX.Element => ( // --------------------------------------------------------------------------- type Screen = "whats-new" | "migrate" -type MigratePhase = "selecting" | "migrating" | "done" +type MigratePhase = "selecting" | "migrating" | "error" | "done" interface ProgressEntry { item: string @@ -182,6 +184,7 @@ const MigrationWizard: Component = (props) => { const [providers, setProviders] = createSignal([]) const [mcpServers, setMcpServers] = createSignal([]) const [customModes, setCustomModes] = createSignal([]) + const [sessions, setSessions] = createSignal([]) const [defaultModel, setDefaultModel] = createSignal<{ provider: string; model: string } | undefined>(undefined) const [legacySettings, setLegacySettings] = createSignal(undefined) @@ -189,6 +192,7 @@ const MigrationWizard: Component = (props) => { const [migrateProviders, setMigrateProviders] = createSignal(true) const [migrateMcpServers, setMigrateMcpServers] = createSignal(true) const [migrateModes, setMigrateModes] = createSignal(true) + const [migrateSessions, setMigrateSessions] = createSignal(true) const [migrateDefaultModel, setMigrateDefaultModel] = createSignal(true) const [migrateAutoApproval, setMigrateAutoApproval] = createSignal(true) const [migrateLanguage, setMigrateLanguage] = createSignal(true) @@ -213,6 +217,7 @@ const MigrationWizard: Component = (props) => { setProviders(data.providers) setMcpServers(data.mcpServers) setCustomModes(data.customModes) + setSessions(data.sessions ?? []) setDefaultModel(data.defaultModel) setLegacySettings(data.settings) @@ -220,6 +225,7 @@ const MigrationWizard: Component = (props) => { setMigrateProviders(data.providers.some((p) => p.supported && p.hasApiKey)) setMigrateMcpServers(data.mcpServers.length > 0) setMigrateModes(data.customModes.length > 0) + setMigrateSessions((data.sessions?.length ?? 0) > 0) setMigrateDefaultModel(Boolean(data.defaultModel)) const s = data.settings @@ -258,7 +264,11 @@ const MigrationWizard: Component = (props) => { if (msg?.type === "legacyMigrationComplete") { const complete = msg as LegacyMigrationCompleteMessage setResults(complete.results) - setPhase("done") + const hasErrors = complete.results.some((r) => r.status === "error") + setPhase(hasErrors ? "error" : "done") + if (!hasErrors) { + vscode.postMessage({ type: "loadSessions" }) + } } } @@ -318,6 +328,9 @@ const MigrationWizard: Component = (props) => { const mode = customModes().find((m) => m.slug === slug) return { item: mode?.name ?? slug, group: "customModes", status: "pending" as const } }), + ...(migrateSessions() + ? sessions().map((id) => ({ item: id, group: "sessions", status: "pending" as const })) + : []), ...(migrateDefaultModel() && defaultModel() ? [{ item: "Default model", group: "defaultModel", status: "pending" as const }] : []), @@ -356,6 +369,7 @@ const MigrationWizard: Component = (props) => { providers: selectedProviderNames, mcpServers: selectedMcpNames, customModes: selectedModesSlugs, + sessions: migrateSessions() ? sessions() : [], defaultModel: migrateDefaultModel(), settings: { autoApproval, @@ -373,6 +387,11 @@ const MigrationWizard: Component = (props) => { props.onComplete() } + const copySessionError = async (text: string) => { + await navigator.clipboard.writeText(text) + showToast({ variant: "success", title: language.t("migration.error.toast.copied") }) + } + // --------------------------------------------------------------------------- // Data helpers // --------------------------------------------------------------------------- @@ -398,11 +417,13 @@ const MigrationWizard: Component = (props) => { const hasLanguageData = () => Boolean(legacySettings()?.language) const hasAutocompleteData = () => Boolean(legacySettings()?.autocomplete) + const hasSessions = () => sessions().length > 0 const hasAnySelection = () => (migrateProviders() && supportedProviderCount() > 0) || (migrateMcpServers() && mcpServers().length > 0) || (migrateModes() && customModes().length > 0) || + (migrateSessions() && hasSessions()) || (migrateDefaultModel() && Boolean(defaultModel())) || (migrateAutoApproval() && hasAnyAutoApprovalData()) || (migrateLanguage() && hasLanguageData()) || @@ -412,6 +433,7 @@ const MigrationWizard: Component = (props) => { supportedProviderCount() === 0 && mcpServers().length === 0 && customModes().length === 0 && + !hasSessions() && !defaultModel() && !hasAnyAutoApprovalData() && !hasLanguageData() && @@ -419,17 +441,19 @@ const MigrationWizard: Component = (props) => { // Group-level status for progress display const groupStatus = (group: string): ProgressEntry["status"] => { - const entries = progressEntries().filter((e) => e.group === group) + const entries = progressEntries().filter((entry) => entry.group === group) if (entries.length === 0) return "pending" - if (entries.some((e) => e.status === "error")) return "error" - if (entries.some((e) => e.status === "warning")) return "warning" - if (entries.every((e) => e.status === "success")) return "success" - if (entries.some((e) => e.status === "migrating")) return "migrating" + if (entries.some((entry) => entry.status === "error")) return "error" + if (entries.some((entry) => entry.status === "warning")) return "warning" + if (entries.every((entry) => entry.status === "success")) return "success" + if (entries.some((entry) => entry.status === "migrating")) return "migrating" return "pending" } - const successCount = () => results().filter((r) => r.status === "success").length + const successCount = () => results().filter((result) => result.status === "success").length const totalCount = () => results().length + const groupMessage = (group: string) => + progressEntries().find((entry) => entry.group === group && entry.status === "error")?.message // --------------------------------------------------------------------------- // Status icon renderer @@ -477,10 +501,7 @@ const MigrationWizard: Component = (props) => {
-

- {language.t("migration.whatsNew.title")}{" "} - {language.t("migration.whatsNew.badge")} -

+

{language.t("migration.whatsNew.title")}

{language.t("migration.whatsNew.subtitle")}

@@ -653,6 +674,52 @@ const MigrationWizard: Component = (props) => {
+ +
+ }> + + +
+
{language.t("migration.migrate.chatHistory")}
+
+ {language.t("migration.migrate.sessionsDetected", { count: String(sessions().length) })} +
+ +
+
+
+ {language.t("migration.error.sessionFailed")} +
+ +
+
{groupMessage("sessions")}
+
+
+
+
+
+ {/* Default Model */}
@@ -737,21 +804,8 @@ const MigrationWizard: Component = (props) => {
- {/* Divider + Cannot be migrated */} -
- -
- -
-
{language.t("migration.migrate.chatHistory")}
-
{language.t("migration.migrate.chatHistoryDesc")}
-
-
- {/* Cleanup option after done */} + {/*
@@ -771,6 +825,7 @@ const MigrationWizard: Component = (props) => {
+ */}
@@ -802,6 +857,18 @@ const MigrationWizard: Component = (props) => { {language.t("migration.migrate.button")} + + +
-
+
= (props) => { const language = useLanguage() const vscode = useVSCode() + const { updateConfig } = useConfig() + const [importing, setImporting] = createSignal(false) + const [exporting, setExporting] = createSignal(false) + let epoch = 0 const open = (url: string) => { vscode.postMessage({ type: "openExternal", url }) } + // Listen for globalConfigLoaded response + const handler = (event: MessageEvent) => { + const msg = event.data as ExtensionMessage + if (msg.type !== "globalConfigLoaded" || !exporting()) return + setExporting(false) + epoch++ + const payload = buildExport(msg.config) + const json = JSON.stringify(payload, null, 2) + const blob = new Blob([json], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = "kilo-settings.json" + a.click() + URL.revokeObjectURL(url) + } + window.addEventListener("message", handler) + onCleanup(() => window.removeEventListener("message", handler)) + + // ----- Export ----- + const handleExport = () => { + if (exporting()) return + setExporting(true) + const token = ++epoch + vscode.postMessage({ type: "requestGlobalConfig" }) + setTimeout(() => { + if (epoch === token) setExporting(false) + }, 5000) + } + + // ----- Import ----- + const handleImport = () => { + if (importing()) return + const input = document.createElement("input") + input.type = "file" + input.accept = ".json" + input.style.display = "none" + input.addEventListener("change", () => { + const file = input.files?.[0] + if (!file) return + if (file.size > MAX_IMPORT_SIZE) { + showToast({ variant: "error", title: language.t("settings.aboutKiloCode.importSettings.tooLarge") }) + return + } + setImporting(true) + const reader = new FileReader() + reader.onload = () => { + setImporting(false) + const text = reader.result as string + const result = parseImport(text) + if (!result.ok) { + const key = + result.error === "invalidJson" + ? "settings.aboutKiloCode.importSettings.invalidJson" + : "settings.aboutKiloCode.importSettings.invalidConfig" + showToast({ variant: "error", title: language.t(key) }) + return + } + if (result.warning === "newerVersion") { + showToast({ + variant: "default", + title: language.t("settings.aboutKiloCode.importSettings.newerVersion"), + }) + } + updateConfig(result.config) + showToast({ + variant: "success", + title: language.t("settings.aboutKiloCode.importSettings.success"), + }) + } + reader.onerror = () => { + setImporting(false) + showToast({ variant: "error", title: language.t("settings.aboutKiloCode.importSettings.invalidJson") }) + } + reader.readAsText(file) + }) + document.body.appendChild(input) + input.click() + document.body.removeChild(input) + } + const getStatusColor = () => { switch (props.connectionState) { case "connected": @@ -158,6 +248,31 @@ const AboutKiloCodeTab: Component = (props) => {
+ {/* Settings Transfer */} +
+

{language.t("settings.aboutKiloCode.settingsTransfer.title")}

+

+ {language.t("settings.aboutKiloCode.settingsTransfer.description")} +

+
+ + +
+
+ {/* legacy-migration start */}

{language.t("settings.aboutKiloCode.legacyMigration.title")}

diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx index dc0e385e376..3210a800f44 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx @@ -6,13 +6,19 @@ import { Button } from "@kilocode/kilo-ui/button" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Dialog } from "@kilocode/kilo-ui/dialog" import { useDialog } from "@kilocode/kilo-ui/context/dialog" +import { Switch } from "@kilocode/kilo-ui/switch" import { useConfig } from "../../context/config" import { useSession } from "../../context/session" import { useLanguage } from "../../context/language" +import { useVSCode } from "../../context/vscode" import type { AgentInfo, SkillInfo } from "../../types/messages" import ModeEditView from "./ModeEditView" import ModeCreateView from "./ModeCreateView" +import McpEditView from "./McpEditView" +import WorkflowsTab from "./agent-behaviour/WorkflowsTab" +import { parseImport, MAX_IMPORT_SIZE } from "./mode-io" +import type { ImportError } from "./mode-io" type SubtabId = "agents" | "mcpServers" | "rules" | "workflows" | "skills" @@ -36,21 +42,6 @@ interface SelectOption { import SettingsRow from "./SettingsRow" -const Placeholder: Component<{ text: string }> = (props) => ( - -

- {useLanguage().t("settings.agentBehaviour.notImplemented")} {props.text} -

-
-) - // View states for the agents subtab type AgentView = "list" | "create" | "edit" @@ -59,15 +50,20 @@ const AgentBehaviourTab: Component = () => { const { config, updateConfig } = useConfig() const session = useSession() const dialog = useDialog() + const vscode = useVSCode() const [activeSubtab, setActiveSubtab] = createSignal("agents") const [newSkillPath, setNewSkillPath] = createSignal("") const [newSkillUrl, setNewSkillUrl] = createSignal("") const [newInstruction, setNewInstruction] = createSignal("") + const browse = () => vscode.postMessage({ type: "openMarketplacePanel" }) // Agent view state const [agentView, setAgentView] = createSignal("list") const [editingAgent, setEditingAgent] = createSignal("") + // MCP view state + const [editingMcp, setEditingMcp] = createSignal("") + // Fetch skills whenever the skills subtab becomes active createEffect(() => { if (activeSubtab() === "skills") { @@ -226,6 +222,41 @@ const AgentBehaviourTab: Component = () => { setEditingAgent("") } + const [importError, setImportError] = createSignal("") + + const errorKey = (tag: ImportError) => `settings.agentBehaviour.importMode.${tag}` as const + + const importMode = (file: File) => { + setImportError("") + if (file.size > MAX_IMPORT_SIZE) { + setImportError(language.t(errorKey("tooLarge"))) + return + } + const reader = new FileReader() + reader.onload = () => { + const result = parseImport(reader.result as string, agentNames()) + if (!result.ok) { + setImportError(language.t(errorKey(result.error))) + return + } + const existing = config().agent ?? {} + updateConfig({ agent: { ...existing, [result.name]: result.config } }) + setImportError("") + } + reader.readAsText(file) + } + + const triggerImport = () => { + const input = document.createElement("input") + input.type = "file" + input.accept = ".json" + input.onchange = () => { + const file = input.files?.[0] + if (file) importMode(file) + } + input.click() + } + const renderAgentsSubtab = () => { const view = agentView() if (view === "create") return @@ -269,11 +300,31 @@ const AgentBehaviourTab: Component = () => { }} >
{language.t("settings.agentBehaviour.availableAgents")}
- +
+ + + +
+ +
+ {importError()} +
+
+ {/* Agents list - clickable to edit */} 0} @@ -295,6 +346,10 @@ const AgentBehaviourTab: Component = () => { {(name, index) => { const agent = () => session.agents().find((a) => a.name === name) const isCustom = () => !agent()?.native + const agentCfg = () => config().agent?.[name] ?? {} + const disabled = () => agentCfg().disable ?? false + const hidden = () => agentCfg().hidden ?? false + const deprecated = () => agent()?.deprecated ?? false return (
{ "border-bottom": index() < agentNames().length - 1 ? "1px solid var(--border-weak-base)" : "none", "border-radius": "4px", cursor: "pointer", + opacity: disabled() ? "0.5" : "1", }} onClick={() => startEdit(name)} onMouseEnter={(e) => { @@ -330,6 +386,45 @@ const AgentBehaviourTab: Component = () => { custom + + + {language.t("settings.agentBehaviour.badge.hidden")} + + + + + {language.t("settings.agentBehaviour.badge.disabled")} + + + + + {language.t("settings.agentBehaviour.badge.deprecated")} + +
{ const renderMcpSubtab = () => { const mcpEntries = createMemo(() => Object.entries(config().mcp ?? {})) + const [expanded, setExpanded] = createSignal>({}) + + const toggle = (name: string) => { + setExpanded((prev) => ({ ...prev, [name]: !prev[name] })) + } + + const statusColor = (name: string) => { + const s = session.mcpStatus()[name]?.status + if (s === "connected") return "var(--vscode-testing-iconPassed, #4caf50)" + if (s === "failed") return "var(--vscode-testing-iconFailed, #f44336)" + if (s === "needs_auth" || s === "needs_client_registration") + return "var(--vscode-editorWarning-foreground, #ff9800)" + if (s === "disabled") return "var(--vscode-disabledForeground, #888)" + return "var(--vscode-disabledForeground, #888)" + } + + const statusLabel = (name: string) => { + const s = session.mcpStatus()[name]?.status + if (!s) return "" + const key = { + connected: "mcp.status.connected", + failed: "mcp.status.failed", + needs_auth: "mcp.status.needs_auth", + disabled: "mcp.status.disabled", + needs_client_registration: "mcp.status.needs_registration", + }[s] + return key ? language.t(key) : s + } + + const isConnected = (name: string) => session.mcpStatus()[name]?.status === "connected" + + if (editingMcp()) { + return ( + setEditingMcp("")} + onRemove={(name) => { + confirmRemoveMcp(name) + setEditingMcp("") + }} + /> + ) + } return (
+
+ +
0} fallback={ @@ -418,50 +568,183 @@ const AgentBehaviourTab: Component = () => { > - {([name, mcp], index) => ( -
-
-
{name}
+ {([name, mcp], index) => { + const open = () => expanded()[name] ?? false + const env = () => Object.entries(mcp.environment ?? mcp.env ?? {}) + const error = () => { + const s = session.mcpStatus()[name] + if (s?.status === "failed") return s.error + if (s?.status === "needs_client_registration") return s.error + return undefined + } + return ( +
+ {/* Header row */}
toggle(name)} > - -
- command:{" "} - {Array.isArray(mcp.command) - ? mcp.command.join(" ") - : `${mcp.command} ${(mcp.args ?? []).join(" ")}`} +
+ { + e.stopPropagation() + toggle(name) + }} + /> + {/* Status dot */} +
+
{name}
+ + {statusLabel(name) || (mcp.url ? "remote" : "stdio")} + +
+
+
e.stopPropagation()}> + { + if (isConnected(name)) { + session.disconnectMcp(name) + } else { + session.connectMcp(name) + } + }} + hideLabel + > + {name} +
- - -
url: {mcp.url}
-
+ { + e.stopPropagation() + confirmRemoveMcp(name) + }} + /> + { + e.stopPropagation() + setEditingMcp(name) + }} + /> +
+ + {/* Error message */} + +
+ {error()} +
+
+ + {/* Expandable detail */} + +
+ +
+ + {language.t("settings.agentBehaviour.mcpDetail.command")}:{" "} + + + {Array.isArray(mcp.command) ? mcp.command[0] : mcp.command} + +
+ 1) || + (!Array.isArray(mcp.command) && mcp.args && mcp.args.length > 0) + } + > +
+ + {language.t("settings.agentBehaviour.mcpDetail.args")}:{" "} + + + {Array.isArray(mcp.command) + ? (mcp.command as string[]).slice(1).join(" ") + : (mcp.args ?? []).join(" ")} + +
+
+
+ +
+ URL: + + {mcp.url} + +
+
+ 0}> +
+ + {language.t("settings.agentBehaviour.mcpDetail.env")}: + +
+ + {([key, val]) => ( +
+ {key}={val} +
+ )} +
+
+
+
- { - e.stopPropagation() - confirmRemoveMcp(name) - }} - /> -
- )} + ) + }} @@ -471,6 +754,18 @@ const AgentBehaviourTab: Component = () => { const renderSkillsSubtab = () => (
+
+ +
{/* Discovered skills */}

{language.t("settings.agentBehaviour.discoveredSkills")} @@ -623,6 +918,18 @@ const AgentBehaviourTab: Component = () => { const renderRulesSubtab = () => (
+ {/* Description */} +
+ {language.t("settings.agentBehaviour.rules.description")} +
+
{ case "rules": return renderRulesSubtab() case "workflows": - return + return case "skills": return renderSkillsSubtab() default: @@ -728,11 +1035,12 @@ const AgentBehaviourTab: Component = () => { + + {/* Fetch error */} + + {(err) => ( + {err()} + )} + + + {/* Fetch status (success/info messages) */} + + {(status) => ( + + {status()} + + )} + + + {/* Model selection picker */} + + {(models) => ( +
+ {/* Header with count + toggle */} +
+ + + {language.t("provider.custom.models.fetch.showing", { + shown: String(filtered().length), + total: String(models().length), + })} + + +
+ + +
+
+ + {/* Search */} + 10}> + + + + {/* Model list */} +
+ + {(m) => ( + + )} + +
+ + {/* Actions */} +
+ + +
+
+ )} +
{/* Headers */} diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx index 394de4096ab..0ef3311b186 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx @@ -2,23 +2,25 @@ import { Component } from "solid-js" import { Select } from "@kilocode/kilo-ui/select" import { useLanguage, LOCALES, LOCALE_LABELS, type Locale } from "../../context/language" -const options = ["", ...LOCALES] as const -type Option = "" | Locale +const AUTO = "auto" +const options = [AUTO, ...LOCALES] as const +type Option = typeof AUTO | Locale const LanguageTab: Component = () => { const language = useLanguage() + const current = () => language.userOverride() || AUTO return (

{language.t("settings.language.description")}