Merge branch 'main' into johnnyamancio/kilo-opencode-v1.2.25

This commit is contained in:
Johnny Amancio
2026-04-06 13:48:11 +02:00
228 changed files with 11007 additions and 815 deletions
+4 -1
View File
@@ -4,7 +4,10 @@ node_modules
.worktrees
.sst
.env
.idea
.idea/*
!.idea/gradle.xml
!.idea/.run
.idea/*.iml
.vscode/*
!.vscode/extensions.json
!.vscode/launch.json
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/kilo-jetbrains" />
<option name="gradleJvm" value="#JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$/packages/kilo-jetbrains" />
<option value="$PROJECT_DIR$/packages/kilo-jetbrains/backend" />
<option value="$PROJECT_DIR$/packages/kilo-jetbrains/frontend" />
<option value="$PROJECT_DIR$/packages/kilo-jetbrains/shared" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
+27
View File
@@ -0,0 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run JetBrains Plugin" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/kilo-jetbrains" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runIde" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<GradleProfilingDisabled>false</GradleProfilingDisabled>
<GradleCoverageDisabled>false</GradleCoverageDisabled>
<method v="2" />
</configuration>
</component>
+6 -6
View File
@@ -1,9 +1,9 @@
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=kilocode.Kilo-Code"><img src="https://raster.shields.io/badge/VS_Code_Marketplace-007ACC?style=flat&logo=visualstudiocode&logoColor=white" alt="VS Code Marketplace"></a>
<a href="https://x.com/kilocode"><img src="https://raster.shields.io/badge/kilocode-000000?style=flat&logo=x&logoColor=white" alt="X (Twitter)"></a>
<a href="https://blog.kilo.ai"><img src="https://raster.shields.io/badge/Blog-555?style=flat&logo=substack&logoColor=white" alt="Substack Blog"></a>
<a href="https://kilo.ai/discord"><img src="https://raster.shields.io/badge/Join%20Discord-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
<a href="https://www.reddit.com/r/kilocode/"><img src="https://raster.shields.io/badge/Join%20r%2Fkilocode-D84315?style=flat&logo=reddit&logoColor=white" alt="Reddit"></a>
<a href="https://marketplace.visualstudio.com/items?itemName=kilocode.Kilo-Code"><img src="https://raster.shields.io/badge/VS_Code_Marketplace-007ACC?style=flat&logo=visualstudiocode&logoColor=white" alt="VS Code Marketplace" height="20"></a>
<a href="https://x.com/kilocode"><img src="https://raster.shields.io/badge/kilocode-000000?style=flat&logo=x&logoColor=white" alt="X (Twitter)" height="20"></a>
<a href="https://blog.kilo.ai"><img src="https://raster.shields.io/badge/Blog-555?style=flat&logo=substack&logoColor=white" alt="Substack Blog" height="20"></a>
<a href="https://kilo.ai/discord"><img src="https://raster.shields.io/badge/Join%20Discord-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord" height="20"></a>
<a href="https://www.reddit.com/r/kilocode/"><img src="https://raster.shields.io/badge/Join%20r%2Fkilocode-D84315?style=flat&logo=reddit&logoColor=white" alt="Reddit" height="20"></a>
</p>
# 🚀 Kilo
@@ -41,7 +41,7 @@
2. Create your account to access 500+ cutting-edge AI models including Gemini 3 Pro, Claude 4.5 Sonnet & Opus, and GPT-5 with transparent pricing that matches provider rates exactly.
3. Start coding with AI that adapts to your workflow. Watch our quick-start guide to see Kilo in action:
[![Watch the video](https://img.youtube.com/vi/pqGfYXgrhig/maxresdefault.jpg)](https://youtu.be/pqGfYXgrhig)
<a href="https://youtu.be/pqGfYXgrhig"><img src="https://img.youtube.com/vi/pqGfYXgrhig/maxresdefault.jpg" alt="Watch the video" width="640" height="360"></a>
## Get Started with the CLI
+31 -16
View File
@@ -27,7 +27,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@kilocode/kilo-i18n": "workspace:*",
"@kilocode/kilo-ui": "workspace:*",
@@ -80,7 +80,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -113,7 +113,7 @@
},
"packages/desktop-electron": {
"name": "@opencode-ai/desktop-electron",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -144,7 +144,7 @@
},
"packages/kilo-docs": {
"name": "@kilocode/kilo-docs",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@docsearch/css": "^4",
"@docsearch/js": "^4",
@@ -173,7 +173,7 @@
},
"packages/kilo-gateway": {
"name": "@kilocode/kilo-gateway",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@ai-sdk/anthropic": "2.0.65",
"@ai-sdk/openai": "2.0.101",
@@ -208,7 +208,7 @@
},
"packages/kilo-i18n": {
"name": "@kilocode/kilo-i18n",
"version": "7.1.11",
"version": "7.1.21",
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
@@ -216,9 +216,12 @@
"typescript": "catalog:",
},
},
"packages/kilo-jetbrains": {
"name": "@kilocode/kilo-jetbrains",
},
"packages/kilo-telemetry": {
"name": "@kilocode/kilo-telemetry",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@kilocode/kilo-gateway": "workspace:*",
"@opentelemetry/api": "1.9.0",
@@ -238,7 +241,7 @@
},
"packages/kilo-ui": {
"name": "@kilocode/kilo-ui",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@kobalte/core": "0.13.11",
"@opencode-ai/util": "workspace:*",
@@ -273,7 +276,7 @@
},
"packages/kilo-vscode": {
"name": "kilo-code",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@kilocode/kilo-i18n": "workspace:*",
@@ -326,7 +329,7 @@
},
"packages/opencode": {
"name": "@kilocode/cli",
"version": "7.1.11",
"version": "7.1.21",
"bin": {
"kilo": "./bin/kilo",
"kilocode": "./bin/kilo",
@@ -404,9 +407,11 @@
"opentui-spinner": "0.0.6",
"partial-json": "0.1.7",
"remeda": "catalog:",
"rotating-file-stream": "3.2.9",
"semver": "^7.6.3",
"simple-git": "3.31.1",
"solid-js": "catalog:",
"stream-chat": "9.38.0",
"strip-ansi": "7.1.2",
"tree-sitter-bash": "0.25.0",
"turndown": "7.2.0",
@@ -452,7 +457,7 @@
},
"packages/plugin": {
"name": "@kilocode/plugin",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"zod": "catalog:",
@@ -466,7 +471,7 @@
},
"packages/script": {
"name": "@opencode-ai/script",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"semver": "^7.6.3",
},
@@ -477,7 +482,7 @@
},
"packages/sdk/js": {
"name": "@kilocode/sdk",
"version": "7.1.11",
"version": "7.1.21",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -488,7 +493,7 @@
},
"packages/storybook": {
"name": "@opencode-ai/storybook",
"version": "7.1.11",
"version": "7.1.21",
"devDependencies": {
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
@@ -511,7 +516,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "catalog:",
@@ -558,7 +563,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "7.1.11",
"version": "7.1.21",
"dependencies": {
"zod": "catalog:",
},
@@ -1224,6 +1229,8 @@
"@kilocode/kilo-i18n": ["@kilocode/kilo-i18n@workspace:packages/kilo-i18n"],
"@kilocode/kilo-jetbrains": ["@kilocode/kilo-jetbrains@workspace:packages/kilo-jetbrains"],
"@kilocode/kilo-telemetry": ["@kilocode/kilo-telemetry@workspace:packages/kilo-telemetry"],
"@kilocode/kilo-ui": ["@kilocode/kilo-ui@workspace:packages/kilo-ui"],
@@ -2008,6 +2015,8 @@
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
"@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="],
"@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
@@ -3296,6 +3305,8 @@
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
"linkifyjs": ["linkifyjs@4.3.2", "", {}, "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA=="],
"load-json-file": ["load-json-file@7.0.1", "", {}, "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
@@ -3842,6 +3853,8 @@
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"rotating-file-stream": ["rotating-file-stream@3.2.9", "", {}, "sha512-i9i0KkHh12ryl4xtELg+0gyoFre2PJ9RcQQLzquWsiqygyYsrZLckrqqYrthhnJZGZb4g+KUHtcoWYVq34gaug=="],
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
@@ -4022,6 +4035,8 @@
"storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.9", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="],
"stream-chat": ["stream-chat@9.38.0", "", { "dependencies": { "@types/jsonwebtoken": "^9.0.8", "@types/ws": "^8.5.14", "axios": "^1.12.2", "base64-js": "^1.5.1", "form-data": "^4.0.4", "isomorphic-ws": "^5.0.0", "jsonwebtoken": "^9.0.3", "linkifyjs": "^4.3.2", "ws": "^8.18.1" } }, "sha512-nyTFKHnhGfk1Op/xuZzPKzM9uNTy4TBma69+ApwGj/UtrK2pT6rSaU0Qy/oAqub+Bh7jR2/5vlV/8FWJ2BObFg=="],
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+44 -18
View File
@@ -150,28 +150,54 @@
'';
in
pkgs.mkShell {
packages = with pkgs; [
bun
nodejs_20
pkg-config
openssl
git
gh
playwright-driver.browsers
vsce
unzip
gnutar
gzip
patchelf
ripgrep
kilo-dev
kilo-install-bin
kilo-bin
];
packages =
with pkgs;
[
bun
nodejs_20
pkg-config
openssl
git
gh
playwright-driver.browsers
vsce
unzip
gnutar
gzip
patchelf
ripgrep
jetbrains.jdk
jdk21
kilo-dev
kilo-install-bin
kilo-bin
]
++ lib.optionals stdenv.isLinux [
libX11
libXext
libXrender
libXtst
libXi
fontconfig
freetype
];
shellHook = ''
export KILO_ROOT="$PWD"
export PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}"
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
''
+ pkgs.lib.optionalString pkgs.stdenv.isLinux ''
export LD_LIBRARY_PATH="${
pkgs.lib.makeLibraryPath [
pkgs.libX11
pkgs.libXext
pkgs.libXrender
pkgs.libXtst
pkgs.libXi
pkgs.fontconfig
pkgs.freetype
]
}:$LD_LIBRARY_PATH"
'';
};
});
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-YjB3YfaA79nztpZoYEAlFBakS//G/Guprlqk4BYFgOA=",
"aarch64-linux": "sha256-ru470M8RVwyOPNGr6I4VfApQRHhYFFMPjOBneTRBJtc=",
"aarch64-darwin": "sha256-M6LIPBHnYH9AtIRAKyW44VN71Kb28nztzFteCkL0588=",
"x86_64-darwin": "sha256-HURoot79BFHYwoEp0XqVdI6cRdV7KGvmRRqOf+hUa9Q="
"x86_64-linux": "sha256-4yd8H4UN7YIhdIQ607UoxSqwsQEKqUWkOifjQ2PEvn0=",
"aarch64-linux": "sha256-EuzzDc/j63eRV8QEFgDmQRIsiSq4ZPr9RJSdB9dH+kI=",
"aarch64-darwin": "sha256-BLCZubMC/n06HsUeO3FsvREwfsmjtiTBseSMDPPZNYM=",
"x86_64-darwin": "sha256-gU1uhExpYJhhVHbspCwCry/DwUiuddRvYl72RZqWzNo="
}
}
+1 -1
View File
@@ -113,6 +113,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.11",
"version": "7.1.21",
"peerDependencies": {}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "7.1.11",
"version": "7.1.21",
"description": "",
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop-electron",
"private": true,
"version": "7.1.11",
"version": "7.1.21",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "7.1.11",
"version": "7.1.21",
"type": "module",
"license": "MIT",
"scripts": {
+6 -6
View File
@@ -1,7 +1,7 @@
id = "kilo"
name = "Kilo"
description = "The open source coding agent."
version = "7.1.11"
version = "7.1.21"
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.11/opencode-darwin-arm64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.21/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.11/opencode-darwin-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.21/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.11/opencode-linux-arm64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.21/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.11/opencode-linux-x64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.21/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.11/opencode-windows-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.21/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+17 -2
View File
@@ -1,5 +1,7 @@
import React, { useState, useEffect, Children, isValidElement, ReactNode, ReactElement } from "react"
const TAB_SYNC_EVENT = "kilo-tab-select"
interface TabProps {
label: string
children: ReactNode
@@ -39,13 +41,26 @@ export function Tabs({ children }: TabsProps) {
setActiveIndex(indexFromHash())
const onHashChange = () => setActiveIndex(indexFromHash())
window.addEventListener("hashchange", onHashChange)
return () => window.removeEventListener("hashchange", onHashChange)
const onSync = (e: Event) => {
const label = (e as CustomEvent<string>).detail
const found = tabs.findIndex((tab) => tab.props.label === label)
if (found >= 0) setActiveIndex(found)
}
window.addEventListener(TAB_SYNC_EVENT, onSync)
return () => {
window.removeEventListener("hashchange", onHashChange)
window.removeEventListener(TAB_SYNC_EVENT, onSync)
}
}, [])
const selectTab = (index: number) => {
setActiveIndex(index)
const slug = slugify(tabs[index].props.label)
const label = tabs[index].props.label
const slug = slugify(label)
history.replaceState(null, "", `#${slug}`)
window.dispatchEvent(new CustomEvent(TAB_SYNC_EVENT, { detail: label }))
}
return (
+3 -2
View File
@@ -336,8 +336,9 @@ export function TopNav({ onMobileMenuToggle, isMobileMenuOpen = false, showMobil
{/* Announcement banner */}
<div className="announcement-banner">
<p>
We've <Link href="https://blog.kilo.ai/p/kilo-cli">replatformed our extensions on the new Kilo CLI</Link>.
Contribute at <Link href="https://github.com/Kilo-Org/kilocode">Kilo-Org/kilocode</Link>.
The all-new Kilo Code extension is here, rebuilt on the{" "}
<Link href="/code-with-ai/platforms/vscode/whats-new">Kilo CLI</Link> for speed, flexibility, and continued access to 500+ models via the Kilo Gateway
</p>
</div>
@@ -0,0 +1,92 @@
import React from "react"
interface VideoEmbedProps {
url: string
title?: string
caption?: string
}
function extractVideoId(url: string): string | null {
const patterns = [
/(?:youtube\.com\/watch\?v=)([^&\s]+)/,
/(?:youtube\.com\/embed\/)([^?\s]+)/,
/(?:youtu\.be\/)([^?\s]+)/,
/(?:youtube\.com\/v\/)([^?\s]+)/,
]
for (const pattern of patterns) {
const match = url.match(pattern)
if (match) {
return match[1] ?? null
}
}
return null
}
export function VideoEmbed({ url, title = "Video", caption }: VideoEmbedProps) {
const videoId = extractVideoId(url)
if (!videoId) {
return (
<div
style={{
padding: "1rem",
backgroundColor: "var(--red-100, #fee2e2)",
color: "var(--red-700, #b91c1c)",
borderRadius: "0.5rem",
margin: "1.5rem 0",
}}
>
Invalid YouTube URL: {url}
</div>
)
}
return (
<div
style={{
maxWidth: "640px",
margin: "1.5rem 0",
}}
>
<div
style={{
position: "relative",
paddingBottom: "56.25%",
height: 0,
overflow: "hidden",
borderRadius: "0.5rem",
}}
>
<iframe
src={`https://www.youtube.com/embed/${videoId}`}
title={title}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
border: "none",
borderRadius: "0.5rem",
}}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
{caption && (
<figcaption
style={{
fontStyle: "italic",
textAlign: "center",
marginTop: "0.5rem",
color: "var(--gray-600, #6b7280)",
}}
>
{caption}
</figcaption>
)}
</div>
)
}
+1
View File
@@ -14,4 +14,5 @@ export * from "./TableOfContents"
export * from "./Tabs"
export * from "./ThemeToggle"
export * from "./TopNav"
export * from "./VideoEmbed"
export * from "./YouTube"
+10 -1
View File
@@ -5,7 +5,11 @@ export const CodeWithAiNav: NavSection[] = [
title: "Platforms",
links: [
{ href: "/code-with-ai", children: "Overview" },
{ href: "/code-with-ai/platforms/vscode", children: "VS Code Extension" },
{
href: "/code-with-ai/platforms/vscode",
children: "VS Code Extension",
subLinks: [{ href: "/code-with-ai/platforms/vscode/whats-new", children: "What's New" }],
},
{
href: "/code-with-ai/platforms/jetbrains",
children: "JetBrains Extension",
@@ -36,6 +40,11 @@ export const CodeWithAiNav: NavSection[] = [
href: "/code-with-ai/agents/auto-model",
children: "Auto Model",
},
{
href: "/code-with-ai/agents/custom-models",
children: "Custom Models",
platform: "new",
},
{
href: "/code-with-ai/agents/free-and-budget-models",
children: "Free & Budget Models",
+10
View File
@@ -7,6 +7,7 @@ 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/end-to-end", children: "End to End Config" },
{
href: "/kiloclaw/control-ui/overview",
children: "Control UI",
@@ -36,6 +37,15 @@ export const KiloClawNav: NavSection[] = [
{ href: "/kiloclaw/development-tools/google", children: "Google Workspace" },
],
},
{
href: "/kiloclaw/triggers",
children: "Triggers",
subLinks: [
{ href: "/kiloclaw/triggers", children: "Overview" },
{ href: "/kiloclaw/triggers/webhooks", children: "Webhooks" },
{ href: "/kiloclaw/triggers/scheduled", children: "Scheduled" },
],
},
{
href: "/kiloclaw/tools",
children: "Tools",
+5
View File
@@ -34,4 +34,9 @@ exclude = [
'^https?://vercel\.link/',
# API base URL, returns 404 when fetched directly
'^https?://api\.apertis\.ai/v1/?$',
# Redirects to authenticated Google Cloud console
'^https?://console\.cloud\.google\.com',
# Consistently times out in CI
'^https?://opncd\.ai',
'^https?://zod\.dev/v4/changelog',
]
+2
View File
@@ -5,4 +5,6 @@ export * from "./tabs.markdoc"
export * from "./icon.markdoc"
export * from "./image.markdoc"
export * from "./kilo-code-icon.markdoc"
export * from "./video.markdoc"
export * from "./youtube.markdoc"
@@ -0,0 +1,21 @@
import { VideoEmbed } from "../../components"
export const video = {
render: VideoEmbed,
selfClosing: true,
attributes: {
url: {
type: String,
required: true,
description: "The YouTube video URL",
},
title: {
type: String,
description: "Accessible title for the video iframe",
},
caption: {
type: String,
description: "Optional caption to display below the video",
},
},
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-docs",
"version": "7.1.11",
"version": "7.1.21",
"private": true,
"scripts": {
"dev": "next dev --webpack --port 3002",
@@ -71,6 +71,44 @@ Then set your default model:
{% /tab %}
{% /tabs %}
## Using Custom or Unlisted Models
If the model you loaded in LM Studio doesn't appear in the Kilo model picker, you can register it as a custom model in your config file:
```jsonc
{
"model": "lmstudio/my-custom-model",
"provider": {
"lmstudio": {
"models": {
"my-custom-model": {
"name": "My Custom Model",
},
},
},
},
}
```
The model key (`my-custom-model`) must match the model identifier that LM Studio serves. If the display name you want differs from the API identifier, use the `id` field to set the API-facing name separately:
```jsonc
{
"provider": {
"lmstudio": {
"models": {
"my-llama": {
"id": "meta-llama-3.1-8b-instruct",
"name": "Llama 3.1 8B (Local)",
},
},
},
},
}
```
See [Custom Models](/docs/code-with-ai/agents/custom-models) for the full list of configuration fields and more examples.
## 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.
@@ -116,6 +116,32 @@ Then set your default model:
{% /tab %}
{% /tabs %}
## Using Custom or Unlisted Models
If your Ollama model doesn't appear in the Kilo model picker, register it as a custom model in your config file:
```jsonc
{
"model": "ollama/my-finetune:latest",
"provider": {
"ollama": {
"models": {
"my-finetune:latest": {
"name": "My Fine-tuned Model",
"tool_call": true,
"limit": {
"context": 32768,
"output": 8192,
},
},
},
},
},
}
```
See [Custom Models](/docs/code-with-ai/agents/custom-models) for the full list of configuration fields and more examples.
## Further Reading
Refer to the [Ollama documentation](https://ollama.com/docs) for more information on installing, configuring and using Ollama.
@@ -40,9 +40,25 @@ You'll find these settings in the Kilo Code settings panel (click the {% codicon
{% /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.
1. Open **Settings** (gear icon) and go to the **Providers** tab.
2. Scroll to the bottom and click **Custom 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.
![Custom provider button](/docs/img/custom-models/custom-provider-button.png)
3. Fill in the custom provider dialog:
![Custom provider configuration dialog](/docs/img/custom-models/custom-provider-details.png)
- **Provider ID** — A unique identifier (e.g., `my-provider`).
- **Display name** — A human-readable name shown in the UI.
- **Base URL** — The provider's OpenAI-compatible API endpoint (e.g., `https://api.your-provider.com/v1`). Kilo auto-fetches available models when a valid URL is entered.
- **API key** — Your API key. Optional — leave empty if authentication is handled via headers.
- **Models** — Add models manually or select from the auto-fetched list.
- **Headers** (optional) — Custom HTTP headers as key-value pairs.
4. Click **Submit** to save. The provider's models appear in the model picker.
For additional model configuration (token limits, tool calling, variants), edit the `kilo.jsonc` config file directly — see the **CLI** tab or the [Custom Models](/docs/code-with-ai/agents/custom-models) guide.
{% /tab %}
{% tab label="CLI" %}
@@ -33,7 +33,7 @@ Each Agent Manager session runs in an isolated git worktree on a separate branch
### Creating a New Worktree Session
1. Click **New Worktree** or press `Cmd+Shift+W` to create a new worktree
1. Click **New Worktree** or press `Cmd+N` (macOS) / `Ctrl+N` (Windows/Linux) to create a new worktree
2. Enter a branch name (or let Kilo generate one)
3. Type your first message to start the agent
@@ -63,7 +63,7 @@ You can run up to 4 parallel implementations of the same prompt across separate
## 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.
Press `Cmd+D` (macOS) / `Ctrl+D` (Windows/Linux) 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
@@ -71,11 +71,18 @@ Press `Cmd+Alt+D` to toggle the diff panel. It shows a live-updating diff betwee
## Terminals
Each session has a dedicated integrated terminal. Press `Cmd+Alt+T` to focus the terminal for the active session.
Each session has a dedicated integrated terminal rooted in the session's worktree directory. Press `Cmd+/` (macOS) / `Ctrl+/` (Windows/Linux) to focus the terminal for the active session.
### Switching Between Terminal and Agent Manager
A common workflow is letting the agent work, then switching to the terminal to run tests or inspect the worktree, then switching back to control the agent:
1. **Agent Manager → Terminal:** Press `Cmd+/` (macOS) / `Ctrl+/` (Windows/Linux) to open and focus the terminal for the current session. The terminal runs inside the session's worktree, so commands like `npm test` or `git status` operate on the agent's isolated branch.
2. **Terminal → Agent Manager:** Press `Cmd+Shift+M` (macOS) / `Ctrl+Shift+M` (Windows/Linux) to bring focus back to the Agent Manager panel and its prompt input. This works from anywhere in VS Code — the terminal, another editor tab, or the sidebar.
## 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.
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.). Root-level `.env` and `.env.*` files are also auto-copied from the main repo before the setup script runs.
## Session State and Persistence
@@ -83,26 +90,26 @@ Agent Manager state is persisted in `.kilo/agent-manager.json`. Sessions, worktr
## 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 |
| Shortcut (macOS) | Shortcut (Windows/Linux) | Action |
| ------------------------ | ------------------------- | ------------------------------------------------ |
| `Cmd+Shift+M` | `Ctrl+Shift+M` | Open / focus Agent Manager (works from anywhere) |
| `Cmd+N` | `Ctrl+N` | New worktree |
| `Cmd+Shift+N` | `Ctrl+Shift+N` | New worktree (advanced options) |
| `Cmd+Shift+O` | `Ctrl+Shift+O` | Import/open worktree |
| `Cmd+Shift+W` | `Ctrl+Shift+W` | Close current worktree |
| `Cmd+T` | `Ctrl+T` | New tab (session) in worktree |
| `Cmd+W` | `Ctrl+W` | Close current tab |
| `Cmd+Alt+Up` / `Down` | `Ctrl+Alt+Up` / `Down` | Previous / next worktree |
| `Cmd+Alt+Left` / `Right` | `Ctrl+Alt+Left` / `Right` | Previous / next tab in worktree |
| `Cmd+/` | `Ctrl+/` | Focus terminal for current session |
| `Cmd+D` | `Ctrl+D` | Toggle diff panel |
| `Cmd+Shift+/` | `Ctrl+Shift+/` | Show keyboard shortcuts |
| `Cmd+1``Cmd+9` | `Ctrl+1``Ctrl+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
- **Worktree creation fails** — ensure Git is installed and the workspace is a valid git repository. Open the main repository (where `.git` is a directory), not an existing worktree checkout.
{% /tab %}
{% tab label="VSCode (Legacy)" %}
@@ -56,19 +56,32 @@ Once configured, the Review Agent runs automatically on PR/MR events. For platfo
Code Reviewer is also available locally. This is valuable for developers who want to review their code before pushing a pull request to their team publicly, or for developers who want reviews and don't need to ship a pull request to GitHub.
### VS Code
{% tabs %}
{% tab label="VSCode" %}
Select 'Review' from the mode dropdown after making local changes, and click 'Send' for AI-powered feedback and suggestions.
![VS Code interface showing Review option in mode dropdown](/docs/img/code-reviewer/review-mode.png "Review Mode")
### CLI
The CLI provides two commands for local code reviews:
Two slash commands are available for local code reviews:
- **`/local-review`** — Review all changes on your current branch vs the base branch
- **`/local-review-uncommitted`** — Review uncommitted changes (staged + unstaged)
{% /tab %}
{% tab label="CLI" %}
Two slash commands are available for local code reviews:
- **`/local-review`** — Review all changes on your current branch vs the base branch
- **`/local-review-uncommitted`** — Review uncommitted changes (staged + unstaged)
{% /tab %}
{% tab label="VSCode (Legacy)" %}
Select 'Review' from the mode dropdown after making local changes, and click 'Send' for AI-powered feedback and suggestions.
{% image src="/docs/img/code-reviewer/review-mode.png" alt="VS Code interface showing Review option in mode dropdown" width="800" caption="Review Mode" /%}
{% /tab %}
{% /tabs %}
## How Code Reviews Work
When a pull request or merge request is opened or updated:
@@ -61,7 +61,7 @@ When you describe a task, the agent uses its tools — `read`, `grep`, `glob`, a
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.
- **Type `@` for file autocomplete** — In the TUI, type `@` followed by a filename to get autocomplete suggestions. Selecting a file attaches its contents to your message. You can limit how much is included by appending a line range, e.g. `@src/utils.ts#10-50`.
- **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.
@@ -0,0 +1,317 @@
---
title: "Custom Models"
description: "How to configure custom or unlisted models for any provider"
platform: new
---
# Custom Models
Kilo Code ships with a curated list of models for each provider, but you can use **any model** your provider supports — including models that aren't in the built-in list. This is useful for:
- Using a newly released model before it's added to the built-in catalog
- Running a custom or fine-tuned model via LM Studio, Ollama, or another local provider
- Connecting to a self-hosted model behind an OpenAI-compatible API
- Configuring model-specific options like token limits, pricing, or reasoning settings
## Defining a Custom Model
Add custom models under the `provider.<provider_id>.models` key in your config file. The model key becomes the model ID you reference elsewhere.
{% tabs %}
{% tab label="CLI" %}
**Config file** (`~/.config/kilo/kilo.jsonc` or `./kilo.jsonc`):
```jsonc
{
"$schema": "https://app.kilo.ai/config.json",
"model": "lmstudio/my-custom-model",
"provider": {
"lmstudio": {
"models": {
"my-custom-model": {
"name": "My Custom Model",
},
},
},
},
}
```
{% /tab %}
{% tab label="VSCode" %}
1. Open **Settings** (gear icon) and go to the **Providers** tab.
2. Scroll to the bottom of the provider list and click **Custom provider**.
![Custom provider button in the Providers tab](/docs/img/custom-models/custom-provider-button.png)
3. Fill in the custom provider dialog:
![Custom provider configuration dialog](/docs/img/custom-models/custom-provider-details.png)
- **Provider ID** — A unique identifier using lowercase letters, numbers, hyphens, or underscores (e.g., `myprovider`). This becomes the `provider_id` in the `provider_id/model_id` format.
- **Display name** — A human-readable name shown in the UI (e.g., `My AI Provider`).
- **Base URL** — The OpenAI-compatible API endpoint (e.g., `https://api.myprovider.com/v1`). When a valid URL is entered, Kilo automatically fetches available models from the endpoint.
- **API key** — Your provider's API key. Optional — leave empty if you manage authentication via headers.
- **Models** — Add models manually by ID and display name, or select from the auto-fetched list that appears after entering a valid base URL.
- **Headers** (optional) — Add custom HTTP headers as key-value pairs if your provider requires them.
4. Click **Submit** to save. Your custom provider appears in the provider list and its models become available in the model picker.
To edit an existing custom provider, click the **Edit provider** button next to it in the connected providers section.
For additional model configuration (token limits, tool calling, reasoning, variants), edit the `kilo.jsonc` config file directly — see the **CLI** tab for the format.
{% /tab %}
{% /tabs %}
The `model` key uses the format `provider_id/model_id`, where:
- **`provider_id`** is the key under `provider` (e.g., `lmstudio`, `ollama`, `openai`, `anthropic`, `openai-compatible`)
- **`model_id`** is the key under `provider.<provider_id>.models` (e.g., `my-custom-model`)
## Model Configuration Fields
All fields are optional. When a model ID matches one already in the built-in catalog, your values are merged on top of the defaults — you only need to specify what you want to override.
| Field | Type | Description |
| ------------- | --------- | ----------------------------------------------------------------------------- |
| `name` | `string` | Display name shown in the model picker |
| `id` | `string` | API-facing model ID sent to the provider. Defaults to the config key |
| `tool_call` | `boolean` | Whether the model supports tool/function calling |
| `reasoning` | `boolean` | Whether the model supports extended thinking |
| `temperature` | `boolean` | Whether the model supports the temperature parameter |
| `attachment` | `boolean` | Whether the model supports file attachments |
| `limit` | `object` | Token limits: `{ context, output, input? }` |
| `cost` | `object` | Pricing per million tokens: `{ input, output, cache_read?, cache_write? }` |
| `options` | `object` | Arbitrary provider-specific model options |
| `headers` | `object` | Custom HTTP headers to include in requests |
| `provider` | `object` | Override `{ npm?, api? }` — the AI SDK package or base API URL for this model |
| `variants` | `object` | Named variant configurations (e.g., different reasoning efforts) |
## Examples
### Local model with LM Studio
Register a model that LM Studio serves under a custom name:
```jsonc
{
"$schema": "https://app.kilo.ai/config.json",
"model": "lmstudio/deepseek-r1-0528",
"provider": {
"lmstudio": {
"models": {
"deepseek-r1-0528": {
"name": "DeepSeek R1 0528",
},
},
},
},
}
```
### Local model with Ollama
```jsonc
{
"$schema": "https://app.kilo.ai/config.json",
"model": "ollama/my-finetune:latest",
"provider": {
"ollama": {
"models": {
"my-finetune:latest": {
"name": "My Fine-tuned Model",
"tool_call": true,
"limit": {
"context": 32768,
"output": 8192,
},
},
},
},
},
}
```
### New or unlisted model from a cloud provider
Use a model that's not yet in the built-in catalog:
```jsonc
{
"$schema": "https://app.kilo.ai/config.json",
"model": "openai/gpt-6-preview",
"provider": {
"openai": {
"models": {
"gpt-6-preview": {
"name": "GPT-6 Preview",
"tool_call": true,
"reasoning": true,
"limit": {
"context": 200000,
"output": 32768,
},
},
},
},
},
}
```
### OpenAI-compatible provider with a custom endpoint
Connect to any provider that exposes an OpenAI-compatible API:
```jsonc
{
"$schema": "https://app.kilo.ai/config.json",
"model": "openai-compatible/my-model",
"provider": {
"openai-compatible": {
"options": {
"apiKey": "{env:MY_PROVIDER_API_KEY}",
"baseURL": "https://api.my-provider.com/v1",
},
"models": {
"my-model": {
"name": "My Custom Model",
"tool_call": true,
"limit": {
"context": 128000,
"output": 16384,
},
},
},
},
},
}
```
### Configuring model options and variants
Override options or define reasoning variants for a built-in model:
```jsonc
{
"$schema": "https://app.kilo.ai/config.json",
"provider": {
"anthropic": {
"models": {
"claude-sonnet-4-20250514": {
"options": {
"thinking": {
"type": "enabled",
"budgetTokens": 16000,
},
},
"variants": {
"thinking-high": {
"thinking": {
"type": "enabled",
"budgetTokens": 32000,
},
},
"fast": {
"disabled": true,
},
},
},
},
},
},
}
```
### Using the `id` field to map model names
If the model key in your config differs from what the provider expects, use the `id` field:
```jsonc
{
"$schema": "https://app.kilo.ai/config.json",
"model": "lmstudio/my-local-llama",
"provider": {
"lmstudio": {
"models": {
"my-local-llama": {
"id": "meta-llama-3.1-8b-instruct",
"name": "Llama 3.1 8B (Local)",
},
},
},
},
}
```
Here `my-local-llama` is the key you use in your config and model picker, while `meta-llama-3.1-8b-instruct` is the actual model identifier sent to the LM Studio API.
## Model Loading Priority
When Kilo starts, it resolves the active model in this order:
1. The `--model` (or `-m`) command-line flag
2. The `model` key in your config file
3. The last used model from your previous session
4. The first available model using an internal priority
The format for all of these is `provider_id/model_id`.
## Provider-Level Options
You can also set options that apply to all models from a provider:
```jsonc
{
"provider": {
"openai": {
"options": {
"apiKey": "{env:OPENAI_API_KEY}",
"baseURL": "https://my-proxy.example.com/v1",
"timeout": 120000,
},
},
},
}
```
| Option | Type | Description |
| --------- | ----------------- | ------------------------------------------------------ |
| `apiKey` | `string` | API key (supports `{env:VAR}` syntax) |
| `baseURL` | `string` | Override the provider's base API URL |
| `timeout` | `number \| false` | Request timeout in milliseconds, or `false` to disable |
## Filtering Available Models
Control which models appear in the model picker for a provider using allowlists and blocklists:
```jsonc
{
"provider": {
"openai": {
"whitelist": ["gpt-5", "gpt-5-mini"],
"blacklist": ["gpt-4-turbo"],
},
},
}
```
- **`whitelist`** — only these model IDs are available from this provider
- **`blacklist`** — these model IDs are hidden from this provider
## Troubleshooting
**Model doesn't appear in the model picker:**
- Verify the provider has valid credentials configured (API key, or local server running)
- Check that the model key matches what you set in `"model": "provider/model-key"`
- Run `kilo models` to list all available models and confirm your provider is active
**Model errors or unexpected behavior:**
- Set `tool_call: true` if you need the model to use tools (file editing, terminal, etc.)
- Set `limit.context` and `limit.output` to match the model's actual capabilities
- For local models, ensure your inference server is running and accessible at the configured URL
@@ -54,6 +54,8 @@ While the specifics change constantly, some principles stay consistent:
**For local/private work**: Ollama and LM Studio let you run models locally. The tradeoff is usually speed and capability for privacy and zero API costs.
**Using an unlisted model?** You can register any model — including fine-tunes, newly released models, or custom local models — by adding it to your config file. See [Custom Models](/docs/code-with-ai/agents/custom-models) for details.
## Context Windows Matter
One thing that doesn't change: context window size matters for your workflow.
@@ -19,7 +19,7 @@ Checkpoints let you:
{% 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`):
Checkpoints (called **snapshots** in the new extension) are enabled by default. They are configured via the `snapshot` key in your config file (`kilo.jsonc` or `~/.config/kilo/kilo.jsonc`):
```json
{
@@ -30,8 +30,12 @@ Checkpoints (called **snapshots** in the new extension) are configured via the `
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
2. Go to the **Checkpoints** tab
3. Toggle the snapshot setting on or off
{% callout type="info" %}
Unlike the legacy extension which used a separate shadow Git repository, the new extension uses a dedicated snapshot Git repository stored outside your project. Your project's `.git` history is never modified by the snapshot system.
{% /callout %}
{% /tab %}
{% tab label="CLI" %}
@@ -64,11 +68,24 @@ Access checkpoint settings in Kilo Code settings under the "Checkpoints" section
{% 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.
The new extension uses **git-based snapshots** to track your workspace state. A dedicated Git repository (with a detached work tree pointing at your project) is created outside your project directory to store snapshot data — your project's own `.git` history is never touched.
**Reverting changes:**
Snapshots are captured automatically at the boundaries of each model call within an agent turn:
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.
1. **Before** the model starts generating (step start)
2. **After** the model finishes and its tool calls have been executed (step finish)
A single user message can produce **multiple steps**. For example, if the agent edits a file, runs a command, sees the output, and then edits another file, each model call in that sequence gets its own snapshot pair. The system records which files changed in each step.
However, while snapshots are taken at each step boundary, **the revert UI operates at the user message level**. You can only revert to the point just before a user message was sent — you cannot revert to an intermediate step within a single agent response.
{% callout type="warning" %}
Revert granularity is **per user message**, not per individual step or file edit. If the agent makes changes across multiple steps within a single response, reverting will undo all of those changes at once.
{% /callout %}
{% callout type="info" %}
Snapshots respect your `.gitignore` rules. Files ignored by Git (such as `node_modules/`, `dist/`, or `.env`) are excluded from snapshots.
{% /callout %}
{% /tab %}
{% tab label="VSCode (Legacy)" %}
@@ -102,12 +119,50 @@ Checkpoints are stored as Git commits in the shadow repository, capturing:
{% 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:
Checkpoints are integrated directly into your chat interface. Each agent turn that modified files shows a collapsible diff summary listing the changed files with addition/deletion counts.
- 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
### Viewing Changes
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.
Click the diff summary on any agent turn to expand it and see which files were modified. Click an individual file to open a side-by-side diff in the VS Code editor.
### Reverting with "Revert to here"
Every user message in the chat that has a corresponding agent response shows a **Revert to here** button (a left-arrow icon) when you hover over it:
{% image src="/docs/img/checkpoints/revert-to-here-button.png" alt="Revert to here button shown on hover over a user message" width="350" /%}
The revert button appears on **user messages only** — these are the revert points in the conversation. You revert to the state your workspace was in just before a given user message was sent. There is no way to revert to a point partway through an agent response.
Clicking **Revert to here** does two things:
1. **Restores your workspace files** to the state they were in just before that message was sent
2. **Hides all subsequent messages** in the chat so you see the conversation as it was at that point
The button is only active when the agent is idle. While the agent is running, the button is disabled to prevent reverting mid-operation.
### The Revert Banner
After reverting, a **Revert Banner** appears at the bottom of the chat. The banner shows:
- The number of messages that were reverted (e.g. "1 message reverted" or "3 messages reverted")
- A per-file breakdown of the changes that were undone, with addition/deletion counts
- A hint: "Send a new message to make this permanent"
The banner provides two actions:
- **Redo** — Steps forward one message at a time, re-applying changes from the next reverted message
- **Redo All** — Restores the workspace to the latest state and un-hides all messages (only shown when more than one message is reverted)
### Making a Revert Permanent
While in a reverted state, you have two choices:
- **Redo / Redo All** to return to where you were
- **Send a new message** to branch off from the reverted point. When you send a new message while reverted, the reverted messages are permanently deleted from the session and the agent continues from the restored state. This is how you "undo" the agent's work and try a different approach.
{% callout type="tip" %}
Reverting is non-destructive until you send a new message. You can freely revert and redo to compare different states of your code without losing anything.
{% /callout %}
{% /tab %}
{% tab label="CLI" %}
@@ -183,7 +238,38 @@ To restore a project to a previous checkpoint state:
{% tabs %}
{% tab label="VSCode" %}
The new extension uses the underlying Git repository in your workspace to create snapshot commits. No shadow repository is used.
### Snapshot Architecture
The snapshot system consists of:
1. **Snapshot Git Repository**: A dedicated Git repository created outside your project at `~/.local/share/kilo/snapshot/<project-id>/<worktree-hash>/`. This stores all snapshot tree objects without affecting your project's Git history. Each worktree gets its own snapshot repository, identified by a hash of the worktree path.
2. **Step-level Snapshots**: The agent runtime automatically runs `git write-tree` against your workspace before and after each agent step. The resulting tree hashes are stored alongside the conversation messages.
3. **Patch Records**: After each step, the system records which files were modified. These patch records enable targeted file-level reverts rather than full-workspace restores.
### How Revert Works
When you click "Revert to here" on a message:
1. The system collects all patch records (file change lists) from messages after the revert point
2. A snapshot of the current workspace is taken so the operation can be undone
3. For each changed file, the system checks out the version from the pre-change snapshot using the stored tree hash
4. Files that were created by the agent (and didn't exist before) are deleted
5. The session records the revert state so the UI can show the Revert Banner
When you click "Redo All" (unrevert):
1. The workspace is fully restored from the snapshot taken in step 2 above using `git checkout-index`
2. The revert state is cleared from the session
### Storage and Cleanup
Snapshot data is stored per-project and is periodically cleaned up. A background process runs `git gc --prune=7.days` every hour, which removes unreachable snapshot objects older than 7 days. Because snapshots are stored as raw tree hashes (not refs or commits), older snapshots may be pruned by garbage collection even if a session still references them.
### Worktree Isolation
When using the Agent Manager with git worktrees, each worktree gets its own isolated snapshot repository. This prevents snapshot data from one worktree interfering with another while sharing underlying Git objects for storage efficiency.
{% /tab %}
{% tab label="VSCode (Legacy)" %}
@@ -320,14 +320,18 @@ Project-level configuration takes precedence over global settings.
Common configuration options include:
- **`model`** - Default model to use
- **`provider`** - Provider-specific settings (API keys, base URLs, custom models)
- **`model`** - Default model in `provider_id/model_id` format (e.g., `"anthropic/claude-sonnet-4-20250514"`)
- **`provider`** - Provider-specific settings (API keys, base URLs, [custom models](/docs/code-with-ai/agents/custom-models))
- **`mcp`** - MCP server configuration
- **`permission`** - Tool permission settings (`allow` or `ask`)
- **`instructions`** - Paths to instruction files (e.g., `["CONTRIBUTING.md", ".cursor/rules/*.md"]`)
- **`formatter`** - Code formatter configuration
- **`disabled_providers`** / **`enabled_providers`** - Control which providers are available
{% callout type="tip" %}
**Using a model that's not in the built-in list?** You can register any model by adding it under `provider.<provider_id>.models` in your config file. See [Custom Models](/docs/code-with-ai/agents/custom-models) for full details and examples.
{% /callout %}
### Environment Variables
Use `{env:VARIABLE_NAME}` syntax in config files to reference environment variables:
@@ -464,10 +468,20 @@ The CLI supports overriding config values with environment variables. The suppor
- For `kilocode` provider: `KILOCODE_<FIELD_NAME>` (e.g., `KILOCODE_MODEL``kilocodeModel`)
- For other providers: `KILO_<FIELD_NAME>` (e.g., `KILO_API_KEY``apiKey`)
## Switching into an Organization from the CLI
## Using the CLI in an Organization
Use the `/teams` command to see a list of all organizations you can switch into.
If you belong to a Kilo organization (Team or Enterprise), you can route CLI requests through that organization. The process differs slightly between interactive and non-interactive usage.
Use `/teams` and select a team to switch teams.
### Interactive Usage
The process is the same when switching into a Team or Enterprise organization.
In an interactive CLI session, use the `/teams` command to select an organization from your membership list.
Your selection is persisted locally so it carries over to future sessions.
### Non-Interactive Usage (`kilo run`)
There is no `--org` or `--team` flag on `kilo run`. Instead, the organization is determined from the following sources, in order of priority (highest first):
1. **`KILO_ORG_ID` environment variable** — Best for non-interactive and CI environments.
2. **`Persisted selection from the last `/teams` pick`** — If you've run an interactive session and selected an organization via `/teams`, that selection is stored in the CLI auth file and reused automatically.
@@ -112,30 +112,43 @@ Cloud Agents are great for:
- **Automated refactors or tech debt cleanup** driven by Kilo Code
- **Offloading CI-like tasks**, experiments, or batch updates
## Webhook Triggers
## Triggers
Webhook triggers allow you to initiate cloud agent sessions via HTTP requests. This enables integration with external services and automation workflows.
Triggers allow you to initiate cloud agent sessions automatically, either via HTTP requests (webhooks) or on a recurring schedule. This enables integration with external services and time-based automation workflows.
{% callout type="note" %}
Webhook triggers are currently in beta and subject to change.
Triggers are currently in beta and subject to change.
{% /callout %}
### Accessing Webhooks
### Accessing Triggers
Webhook triggers are accessible from the main sidebar with an entry named **Webhook** and link to [https://app.kilo.ai/cloud/webhooks](https://app.kilo.ai/cloud/webhooks) for personal accounts. Organization-level webhook configurations are available through your organization's sidebar.
Triggers are accessible from the main sidebar under **Webhooks / Triggers** and link to [https://app.kilo.ai/cloud/triggers](https://app.kilo.ai/cloud/triggers) for personal accounts. Organization-level trigger configurations are available through your organization's sidebar.
### Activation Modes
When creating a trigger, you choose an **activation mode** that cannot be changed after creation:
- **Webhook**: Fires when an external service sends an HTTP request to the trigger's URL
- **Scheduled**: Fires on a recurring schedule defined by a cron expression
### Configuration
Webhook triggers utilize [agent environment profiles](#agent-environment-profiles) to configure the execution environment for triggered sessions. The agent resolves the profile at runtime, so profile updates apply automatically to future executions. Profiles referenced by triggers cannot be deleted until those triggers are updated or removed.
Triggers utilize [agent environment profiles](#agent-environment-profiles) to configure the execution environment for triggered sessions. The agent resolves the profile at runtime, so profile updates apply automatically to future executions. Profiles referenced by triggers cannot be deleted until those triggers are updated or removed.
Webhook triggers do not support manual env var or setup command overrides at this time.
Triggers do not support manual env var or setup command overrides at this time.
### Scheduled Triggers
Scheduled triggers fire on a recurring schedule using cron expressions. You can configure them with a simple frequency picker (every 10 minutes, hourly, daily, weekly) or enter a raw cron expression for full control. Each trigger has a configurable timezone (default: UTC) and handles daylight saving time transitions automatically.
The minimum schedule interval is 10 minutes. Scheduled triggers use `{{scheduledTime}}` and `{{timestamp}}` as prompt template variables (webhook-specific variables like `{{body}}` are not available since there is no inbound HTTP request).
### Trigger Limits and Guidance
Webhook triggers are designed for low-volume invocations from trusted sources and are best suited for short-lived tasks.
Triggers are designed for low-volume invocations from trusted sources and are best suited for short-lived tasks.
- **Personal webhooks**: Execute in the same sandbox container as a user's Cloud Agent sessions. You can view/join invocations live.
- **Organization webhooks**: Execute in dedicated compute resources as a bot user, similar to Code Review sessions. You can share/fork the sessions when they're complete.
- **Personal triggers**: Execute in the same sandbox container as a user's Cloud Agent sessions. You can view/join invocations live.
- **Organization triggers**: Execute in dedicated compute resources as a bot user, similar to Code Review sessions. You can share/fork the sessions when they're complete.
Additional limits:
@@ -144,11 +157,13 @@ Additional limits:
- **Retention**: only the **most recent 100 requests per trigger** are retained
- **In-flight cap**: at most **20 requests per trigger** can be in `captured` or `inprogress` at once (returns `429`)
The webhook endpoint will return rate limit responses when the number of queued or processing requests exceeds system capacity.
The trigger endpoint will return rate limit responses when the number of queued or processing requests exceeds system capacity.
### Webhook Prompt Template Variables
### Prompt Template Variables
You can reference request data in a triggers prompt template using these placeholders:
You can reference data in a triggers prompt template using these placeholders.
**Webhook triggers:**
- `{{body}}` - raw request body (string)
- `{{bodyJson}}` - pretty-printed JSON if parseable, otherwise raw body
@@ -159,6 +174,11 @@ You can reference request data in a triggers prompt template using these plac
- `{{sourceIp}}` - client IP if provided (falls back to `unknown`)
- `{{timestamp}}` - capture timestamp (ISO string)
**Scheduled triggers:**
- `{{scheduledTime}}` - the time the schedule fired (ISO string)
- `{{timestamp}}` - capture timestamp (ISO string)
{% callout type="warning" title="Security Considerations" %}
Care should be taken when deciding to use webhooks as they are susceptible to prompt injection attacks. Especially in scenarios where webhook payloads may contain untrusted input. At this time we recommend using webhooks only for trusted sources.
{% /callout %}
@@ -0,0 +1,58 @@
---
title: "What's New in Kilo Code (April 2026)"
description: "The Kilo Code extension has been rebuilt from the ground up on the Kilo CLI — faster, more flexible, and with access to 500+ models."
---
# What's New in Kilo Code
The Kilo Code extension has been completely rebuilt on a portable, open-source core shared across VS Code, the CLI, and Cloud Agents. This is the biggest update since launch: faster execution with parallel tool calls and subagents, the new Agent Manager for running multiple agents side by side, inline code review with line-level comments, multi-model comparisons, and access to 500+ models.
Whether you're writing features in VS Code, debugging over SSH, or reviewing code on Slack, Kilo now goes with you. Read the [full announcement on the Kilo Blog](https://blog.kilo.ai/p/new-kilo-for-vs-code-is-live) for everything that's new.
---
## Adjusting to the new version
A lot has changed under the hood, and some things have moved around. If you're coming from the previous extension, you might have questions about where to find certain features or how things work now. We've collected the most common questions below.
Still stumped after reading this? Come find us in discord at #vscode.
### Where did code indexing go?
Code indexing is temporarily unavailable in the new extension. It is actively being worked on and is expected to return soon. Please follow [this issue](https://github.com/Kilo-Org/kilocode/issues/6144)
### How do checkpoints work in the new extension?
Checkpoints are now called **snapshots** in the new extension.
They use Git-based snapshots of your working directory, taken before and after agent edits.
You can revert any message's changes directly from the chat, and a revert banner appears when you're viewing an earlier state.
See the [Checkpoints documentation](/docs/code-with-ai/features/checkpoints) for details.
### Where is the auto-approve settings UI?
The old auto-confirm commands UI has been replaced by a granular per-tool permission system.
Open **Settings → Auto Approve** to configure each tool (bash, read, edit, glob, grep, etc.) with **Allow**, **Ask**, or **Deny**.
There is no longer a separate command allowlist — shell execution is controlled by the `bash` tool permission.
See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions) for more information.
### Is the context progress graph still available?
The context progress graph is being evaluated for the new extension.
This feature may be reintroduced in a future update.
### Where is the diff view for file changes?
Each message that caused file changes shows a **diff badge** in the chat — click it to open the Diff Viewer and review what changed.
The Agent Manager also includes a built-in diff reviewer that shows every change file by file, in unified or split view.
### How do I do code reviews in the new extension?
You can now trigger local AI-powered code reviews directly by using two commands: **`/local-review`** to review all changes on your current branch vs the base branch, and **`/local-review-uncommitted`** to review staged and unstaged changes.
See the [Code Reviews](/docs/automate/code-reviews/overview) documentation for the full setup and options.
### Where did orchestrator mode go?
Orchestrator mode is deprecated.
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.
You can also define your own [custom subagents](/docs/customize/custom-subagents).
See the [Orchestrator Mode](/docs/code-with-ai/agents/orchestrator-mode) page for the full details on what changed.
@@ -5,6 +5,8 @@ description: "Connect your KiloClaw agent to Slack"
# Slack
{% youtube url="https://youtu.be/Q5bt-qH-_pY" title="Slack Setup Guide" caption="How to connect your KiloClaw agent to 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
@@ -5,6 +5,8 @@ description: "Connect your KiloClaw agent to Telegram"
# Telegram
{% youtube url="https://youtu.be/hIfKz073hGw" title="Telegram Setup Guide" caption="How to connect your KiloClaw agent to 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)
@@ -42,6 +42,8 @@ Before you begin, make sure you have:
## Setup
{% youtube url="https://youtu.be/PX444_j3O4I" title="Google Workspace Setup Guide" caption="How to connect your Google account to KiloClaw" /%}
1. Go to the **Settings** tab on your [KiloClaw dashboard](/docs/kiloclaw/dashboard)
2. Find the **Google Account** section
3. Copy the provided `docker run` command — it includes a short-lived authentication token
@@ -0,0 +1,157 @@
---
title: "Setup walkthrough"
description: "Start-to-finish guide for configuring your KiloClaw instance"
---
# Setup walkthrough
This guide walks you through a full KiloClaw setup — from creating accounts to scheduling your first automated workflow. Plan for about 60 minutes.
## Planning your setup
For most users, a useful KiloClaw configuration involves:
1. A **chat platform** (called a "channel" in OpenClaw) so you can message your Claw
2. **Google services** for email, calendar, and Drive
3. **GitHub** for code and markdown syncing
### Use dedicated accounts for your Claw
We recommend creating **separate accounts** for your KiloClaw rather than connecting it to your personal accounts. This applies to Google, GitHub, and any other services you connect. A dedicated account improves isolation — your personal data stays separate, and you can control exactly what access the Claw has by sharing or delegating only what you want.
### Chat platform options
- **[Kilo Chat](https://app.kilo.ai)** — available in the web app and coming soon to iOS and Android; requires zero configuration
- **[Telegram](/docs/kiloclaw/chat-platforms/telegram)** — easy to set up, private by default
- **[Discord](/docs/kiloclaw/chat-platforms/discord)** — moderate setup
- **[Slack](/docs/kiloclaw/chat-platforms/slack)** — most involved setup
{% callout type="warning" title="Chain-of-connection security" %}
If your Claw has access to sensitive data (like your email), be careful which chat platform you connect it to. On broadly-accessible platforms like Slack or Discord, anyone on the server could potentially message your Claw and access that data. If you're connecting sensitive integrations, use a private platform like Kilo Chat or Telegram.
{% /callout %}
The steps below walk you through this configuration.
## Preflight Steps
Take these steps before configuring your Claw.
If you are doing a [1-1 configuration call with Kilo](https://kilo.ai/kiloclaw/config-service), please complete these steps before the call.
### Google
Configuring Google services is by far the most involved part of setting up your Claw.
Before configuring, take these preflight steps:
1. **Create a Google Account for your Claw** — Go to [google.com](https://www.google.com/) and create a new Google/Gmail account dedicated to your KiloClaw. Something like `yourname.bot@gmail.com` works well.
2. **Set up Google Cloud** — Visit [console.cloud.google.com](https://console.cloud.google.com). Accept the terms of service and click "Start my free tier". You may need to add a credit card for identity verification.
{% callout type="info" %}
Nothing KiloClaw does costs any money with Google.
{% /callout %}
3. **Install Docker** — KiloClaw configures Google by running a Docker container on your machine. Download Docker at [docker.com](https://www.docker.com/), then open it. You don't need to sign in or create a Docker account.
## Other Services
1. **Create a GitHub account for your Claw** — Using your new Gmail address, create a matching GitHub account for your Claw.
## Set up a messaging platform
Your Claw needs a way to communicate with you. **[Kilo Chat](https://app.kilo.ai)** requires no setup — just open the web app. For other platforms, follow the relevant guide:
- [Telegram](/docs/kiloclaw/chat-platforms/telegram) — about 2 minutes
- [Discord](/docs/kiloclaw/chat-platforms/discord) — about 10 minutes
- [Slack](/docs/kiloclaw/chat-platforms/slack) — about 15 minutes; always use the manifest
{% callout type="tip" %}
If you're not sure which to pick, Kilo Chat (no setup) or Telegram (2 minutes) are the easiest options.
{% /callout %}
## Set up Google OAuth
This lets your Claw act as the bot Google account — sending email, reading calendar, and more. Takes about 15 minutes.
Prerequisites: Docker is installed and running, and your bot Google account is already created.
1. In the KiloClaw dashboard, go to **Settings → Google Account** and copy the Docker command shown.
2. Open a terminal and run the command.
3. Follow the steps in the console:
- At each step, confirm you're logged in to the bot account (check the top-right corner of the screen).
- After project creation, confirm you're in the correct project.
- The last step may look like it failed — this is expected.
For full details, see the [Google setup guide](/docs/kiloclaw/development-tools/google).
## Set up GitHub
A dedicated bot GitHub account is strongly recommended. Takes about 7 minutes.
**Create a Personal Access Token (PAT):**
1. In GitHub, go to **Settings → Developer Settings → Personal Access Tokens → Classic → Generate new token**.
2. Select these scopes: `repo`, `workflow`, `write:org`, `read:user`.
For full details, see the [GitHub setup guide](/docs/kiloclaw/development-tools/github).
**Set up a private workspace repo:**
Once GitHub is connected, ask your Claw to back up its workspace:
> Use your GitHub access to back up your workspace. Make it a GitHub repo and push it as a private repo. Add me as a member so I can see it. Then set up a cron job to pull, rebase, and push any changes at least once an hour.
After sending that, redeploy from the dashboard to pick up the changes.
## Grant email and calendar access
After OAuth is set up, decide how much access to give your Claw to your personal accounts.
| Option | What it does | Best for | Configured from |
| ----------------------- | --------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------- |
| Forward select emails | A Gmail filter forwards specific senders or labels to the bot account | Targeted use cases like newsletter digests | Your personal account |
| Forward all email | Forwards your full inbox to the bot | Simpler setups where noise is acceptable | Bot account (destination) |
| Full account delegation | Gives the bot direct read/write access to your personal account | Maximum capability | Your personal account — Gmail Settings → Add a delegate |
{% callout type="info" %}
Email forwarding is configured from the **destination** (bot) account. Account delegation is configured from the **source** (personal) account.
{% /callout %}
**Push notifications:** by default, your Claw wakes up on every incoming email. If you'd prefer a digest (e.g., once at 7am), disable push notifications in **Settings → Google Account** on the [dashboard](/docs/kiloclaw/dashboard) — otherwise it processes each email as it arrives.
**Google Calendar:** share your personal calendar from your personal Google account. Go to **Google Calendar → Settings → Settings for my calendars → [your calendar] → Share with specific people**, and add the bot account.
## Enable auto-approval
By default, KiloClaw asks for confirmation before every tool call. To let it act freely, go to the [KiloClaw dashboard](https://app.kilo.ai/claw) and enable auto-approval in the **Default Permissions** section.
## Prompt and schedule work
### How to prompt your Claw
Just tell it in plain language what you want. Be specific. If you want it to remember something across sessions, tell it to write it down in a specific file.
### Scheduling jobs
Tell your Claw when and what to do — for example:
> Schedule a daily cron job at 7am to summarize my emails and send me a digest.
{% callout type="tip" %}
Mentioning "cron job" helps it understand you want a recurring scheduled task.
{% /callout %}
### Skills
Reusable capabilities that extend what your Claw can do — things like triaging email, summarizing documents, or managing GitHub issues. You can install a pre-built skill by asking your Claw:
> Install the [skill name] skill.
Or ask your Claw to build a custom skill from scratch — it has a built-in skill-builder skill for exactly this. You can explore popular skills and use case inspiration at the [KiloClaw Bytes library](https://kilo.ai/kiloclaw/bytes).
## Manage inference
**Model picker:** Balanced is a good starting point. Frontier is more capable but significantly more expensive.
You can also use your KiloPass credits — find this under **Profile** in the dashboard.
@@ -0,0 +1,171 @@
---
title: "Setup walkthrough"
description: "Start-to-finish guide for configuring your KiloClaw instance"
---
# Setup walkthrough
This guide walks you through a full KiloClaw setup — from creating accounts to scheduling your first automated workflow. Plan for about 60 minutes.
---
## Planning your setup
For most users, a useful KiloClaw configuration involves:
1. A **chat platform** (called a "channel" in OpenClaw) so you can message your Claw
2. **Google services** for email, calendar, and Drive
3. **GitHub** for code and markdown syncing
### Use dedicated accounts for your Claw
We recommend creating **separate accounts** for your KiloClaw rather than connecting it to your personal accounts. This applies to Google, GitHub, and any other services you connect. A dedicated account improves isolation — your personal data stays separate, and you can control exactly what access the Claw has by sharing or delegating only what you want.
### Chat platform options
- **[Kilo Chat](https://app.kilo.ai)** — available in the web app and coming soon to iOS and Android; requires zero configuration
- **[Telegram](/docs/kiloclaw/chat-platforms/telegram)** — easy to set up, private by default
- **[Discord](/docs/kiloclaw/chat-platforms/discord)** — moderate setup
- **[Slack](/docs/kiloclaw/chat-platforms/slack)** — most involved setup
{% callout type="warning" title="Chain-of-connection security" %}
If your Claw has access to sensitive data (like your email), be careful which chat platform you connect it to. On broadly-accessible platforms like Slack or Discord, anyone on the server could potentially message your Claw and access that data. If you're connecting sensitive integrations, use a private platform like Kilo Chat or Telegram.
{% /callout %}
The steps below walk you through this configuration.
---
## Preflight Steps
Take these steps before configuring your Claw.
If you are doing a [1-1 configuration call with Kilo](https://kilo.ai/kiloclaw/config-service), please complete these steps before the call.
### Google
Configuring Google services is by far the most involved part of setting up your Claw.
Before configuring, take these preflight steps:
1. **Create a Google Account for your Claw** — Go to [google.com](https://www.google.com/) and create a new Google/Gmail account dedicated to your KiloClaw. Something like `yourname.bot@gmail.com` works well.
2. **Set up Google Cloud** — Visit [console.cloud.google.com](https://console.cloud.google.com). Accept the terms of service and click "Start my free tier". You may need to add a credit card for identity verification.
{% callout type="info" %}
Nothing KiloClaw does costs any money with Google.
{% /callout %}
3. **Install Docker** — KiloClaw configures Google by running a Docker container on your machine. Download Docker at [docker.com](https://www.docker.com/), then open it. You don't need to sign in or create a Docker account.
## Other Services
1. **Create a GitHub account for your Claw** — Using your new Gmail address, create a matching GitHub account for your Claw.
---
## Set up a messaging platform
Your Claw needs a way to communicate with you. **[Kilo Chat](https://app.kilo.ai)** requires no setup — just open the web app. For other platforms, follow the relevant guide:
- [Telegram](/docs/kiloclaw/chat-platforms/telegram) — about 2 minutes
- [Discord](/docs/kiloclaw/chat-platforms/discord) — about 10 minutes
- [Slack](/docs/kiloclaw/chat-platforms/slack) — about 15 minutes; always use the manifest
{% callout type="tip" %}
If you're not sure which to pick, Kilo Chat (no setup) or Telegram (2 minutes) are the easiest options.
{% /callout %}
---
## Set up Google OAuth
This lets your Claw act as the bot Google account — sending email, reading calendar, and more. Takes about 15 minutes.
Prerequisites: Docker is installed and running, and your bot Google account is already created.
1. In the KiloClaw dashboard, go to **Settings → Google Account** and copy the Docker command shown.
2. Open a terminal and run the command.
3. Follow the steps in the console:
- At each step, confirm you're logged in to the bot account (check the top-right corner of the screen).
- After project creation, confirm you're in the correct project.
- The last step may look like it failed — this is expected.
For full details, see the [Google setup guide](/docs/kiloclaw/development-tools/google).
---
## Set up GitHub
A dedicated bot GitHub account is strongly recommended. Takes about 7 minutes.
**Create a Personal Access Token (PAT):**
1. In GitHub, go to **Settings → Developer Settings → Personal Access Tokens → Classic → Generate new token**.
2. Select these scopes: `repo`, `workflow`, `write:org`, `read:user`.
For full details, see the [GitHub setup guide](/docs/kiloclaw/development-tools/github).
**Set up a private workspace repo:**
Once GitHub is connected, ask your Claw to back up its workspace:
```
Use your GitHub access to back up your workspace. Make it a GitHub repo and push it as a private repo. Add me as a member so I can see it. Then set up a cron job to pull, rebase, and push any changes at least once an hour.
```
After sending that, redeploy from the dashboard to pick up the changes.
---
## Grant email and calendar access
After OAuth is set up, decide how much access to give your Claw to your personal accounts.
| Option | What it does | Best for | Configured from |
| ----------------------- | --------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------- |
| Forward select emails | A Gmail filter forwards specific senders or labels to the bot account | Targeted use cases like newsletter digests | Your personal account |
| Forward all email | Forwards your full inbox to the bot | Simpler setups where noise is acceptable | Bot account (destination) |
| Full account delegation | Gives the bot direct read/write access to your personal account | Maximum capability | Your personal account — Gmail Settings → Add a delegate |
{% callout type="info" %}
Email forwarding is configured from the **destination** (bot) account. Account delegation is configured from the **source** (personal) account.
{% /callout %}
**Push notifications:** by default, your Claw wakes up on every incoming email. If you'd prefer a digest (e.g., once at 7am), disable push notifications in **Settings → Google Account** on the [dashboard](/docs/kiloclaw/dashboard) — otherwise it processes each email as it arrives.
**Google Calendar:** share your personal calendar from your personal Google account. Go to **Google Calendar → Settings → Settings for my calendars → [your calendar] → Share with specific people**, and add the bot account.
---
## Enable auto-approval
By default, KiloClaw asks for confirmation before every tool call. To let it act freely, go to the [KiloClaw dashboard](https://app.kilo.ai/claw) and enable auto-approval in the **Default Permissions** section.
---
## Prompt and schedule work
**How to prompt your Claw:** just tell it in plain language what you want. Be specific. If you want it to remember something across sessions, tell it to write it down in a specific file.
**Scheduling jobs:** tell your Claw when and what to do — for example:
```
Every day at 7am, summarize my emails and send me a digest.
```
Mentioning "cron job" helps it understand you want a recurring scheduled task.
**Skills:** the OpenClaw Skills hub has pre-built capabilities you can add. The easiest way to install a skill is to just ask your Claw by name:
```
Install the [skill name] skill.
```
---
## Manage inference
**Model picker:** Balanced is a good starting point. Frontier is more capable but significantly more expensive.
You can also use your KiloPass credits — find this under **Profile** in the dashboard.
@@ -0,0 +1,37 @@
---
title: "Triggers"
description: "Automate your KiloClaw agent with webhooks and scheduled triggers"
---
# Triggers
Triggers let external events and schedules drive your KiloClaw agent automatically. Instead of typing every instruction yourself, triggers deliver messages to your agent on your behalf. This lets it react to real-world events or run tasks on a schedule without polling.
All triggers are managed from the **Settings** page in the KiloClaw section of the sidebar.
## Trigger Types
| Type | Description |
| --- | --- |
| [**Webhooks**](/docs/kiloclaw/triggers/webhooks) | Receive HTTP requests from external services (GitHub, Stripe, monitoring tools, etc.) and deliver them as chat messages to your agent |
| [**Scheduled**](/docs/kiloclaw/triggers/scheduled) | Run tasks on a recurring schedule (e.g. every 15 minutes, daily at 9 AM, weekdays only) |
## How Triggers Work
1. A trigger fires
2. Your **prompt template** is rendered into a message
3. That message is delivered to your KiloClaw instance as a chat message
4. Your agent processes and responds like any other conversation
Each trigger type has its own set of template variables. See the [Webhooks](/docs/kiloclaw/triggers/webhooks) and [Scheduled](/docs/kiloclaw/triggers/scheduled) pages for details.
{% callout type="warning" title="Triggers send prompts directly to your agent" %}
When a trigger fires, the rendered message is sent directly to your KiloClaw agent as a prompt. If your instance is configured with a permission model that allows all actions, the agent will execute commands automatically without your explicit approval. This means triggers can cause your agent to take actions without you being aware. Review your instance's [permission settings](/docs/kiloclaw/control-ui/exec-approvals) and prompt templates carefully before enabling triggers.
{% /callout %}
## Related
- [Webhooks](/docs/kiloclaw/triggers/webhooks)
- [Scheduled Triggers](/docs/kiloclaw/triggers/scheduled)
- [KiloClaw Overview](/docs/kiloclaw/overview)
- [Dashboard Reference](/docs/kiloclaw/dashboard)
@@ -0,0 +1,123 @@
---
title: "Scheduled Triggers"
description: "Run tasks on a schedule using cron expressions"
---
# Scheduled Triggers
Scheduled triggers let your KiloClaw agent run tasks automatically on a recurring schedule. Instead of waiting for an external event, a scheduled trigger fires at the times you define using cron expressions. When it fires, the prompt template is rendered and delivered as a chat message to your KiloClaw instance, just like a webhook.
## Setup
1. Go to **Settings** under the KiloClaw section in the sidebar
2. Find the **Scheduled Triggers** section and click **Add Scheduled Trigger**
3. Give your trigger a name (minimum 8 characters)
4. Configure the schedule and prompt template
5. Click **Save**
Each KiloClaw instance supports up to **5 scheduled triggers** alongside its single webhook.
## Configuring a Schedule
The schedule builder defaults to a friendly picker view. For more control, click **<> Advanced** to switch to raw cron input.
### Simple Mode (default)
Pick a frequency, time, and (optionally) days of the week from dropdown menus. The builder generates the cron expression for you behind the scenes and shows a preview of the next 5 upcoming runs.
- **Repeat**: Every 10 minutes, every 15 minutes, every 30 minutes, hourly, daily, weekly
- **At**: Select the time of day (for daily and weekly frequencies)
- **Day of week**: Select which days the trigger should fire (for weekly frequency)
### Advanced Mode
Click **<> Advanced** to enter a raw cron expression directly. This gives you full control over the schedule. The expression is validated in real time with a preview of upcoming fire times.
Cron expressions use the standard five-field format:
```
┌───────── minute (0-59)
│ ┌───────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌───────── month (1-12)
│ │ │ │ ┌───────── day of week (0-7, where 0 and 7 are Sunday)
│ │ │ │ │
* * * * *
```
**Examples:**
| Expression | Meaning |
| --- | --- |
| `*/15 * * * *` | Every 15 minutes |
| `0 9 * * 1-5` | 9:00 AM on weekdays |
| `0 0 1 * *` | Midnight on the first of each month |
| `30 14 * * 3` | 2:30 PM every Wednesday |
{% callout type="note" title="Minimum interval" %}
The minimum interval between scheduled trigger runs is 10 minutes. Schedules more frequent than that are rejected.
{% /callout %}
### Timezone
Select a timezone for your schedule. The default is UTC. All fire times are calculated relative to the selected timezone, including automatic handling of daylight saving time transitions.
## Prompt Template
The prompt template controls what message your agent receives when the schedule fires. You can customize it from the trigger's settings.
**Default template:**
```
Run your scheduled task. Triggered at {{scheduledTime}}.
```
**Available variables:**
| Variable | Description |
| --- | --- |
| `{{scheduledTime}}` | The time the schedule fired (ISO string) |
| `{{timestamp}}` | Capture timestamp (ISO string) |
{% callout type="note" title="Webhook variables are not available" %}
Since scheduled triggers do not receive an HTTP request, variables like `{{body}}`, `{{bodyJson}}`, `{{headers}}`, `{{method}}`, `{{path}}`, and `{{query}}` are not populated. Use `{{scheduledTime}}` and `{{timestamp}}` instead.
{% /callout %}
## Managing Scheduled Triggers
### Pause and Resume
Toggle the **Active/Paused** switch to temporarily stop a trigger from firing. When paused, the schedule is suspended but the configuration is preserved. Resume at any time to restart the schedule.
### Edit
You can update the cron expression, timezone, and prompt template of an existing scheduled trigger at any time. The activation mode (webhook vs. scheduled) cannot be changed after creation.
### Delete
Remove a scheduled trigger from the inline controls in the Settings panel. A confirmation dialog is shown before deletion.
## Viewing Scheduled Trigger Activity
Scheduled trigger invocations appear in the same request history as webhooks. The **Source** column shows a **Scheduled** badge to distinguish them from webhook-triggered requests. Click into a request to see the scheduled fire time and other details.
## Example: Daily Standup Summary
Create a scheduled trigger that fires every weekday morning and asks your agent to summarize overnight activity:
1. Add a scheduled trigger in your KiloClaw Settings
2. Set the frequency to **Weekly** on **Monday through Friday** at **9:00 AM** in your local timezone
3. Customize the prompt template:
```
Good morning! Please summarize any overnight activity in the #engineering Slack channel and list open pull requests that need review today. Triggered at {{scheduledTime}}.
```
Your agent will receive this message every weekday at 9:00 AM and respond with the summary.
## Related
- [Webhooks](/docs/kiloclaw/triggers/webhooks)
- [Triggers Overview](/docs/kiloclaw/triggers)
- [KiloClaw Overview](/docs/kiloclaw/overview)
- [Dashboard Reference](/docs/kiloclaw/dashboard)
@@ -0,0 +1,112 @@
---
title: "Webhooks"
description: "Trigger your KiloClaw agent from external events using webhooks"
---
# Webhooks
KiloClaw supports inbound webhooks so external events can trigger your agent automatically. Form submissions, alerts, calendar updates, ecommerce orders, IoT sensor data; anything that can send an HTTP request can kick off a conversation with your agent. When a webhook fires, the payload is rendered through a prompt template and delivered as a chat message to your KiloClaw instance. The agent processes and responds as if you typed it yourself.
## Setup
1. Go to **Settings** under the KiloClaw section in the sidebar
2. Find the **Webhook Integration** card and click **Manage**
3. Click **Set Up Webhook**
KiloClaw generates a unique webhook URL for your instance. Copy it and configure it as the destination in whatever service you want to receive events from (GitHub, Stripe, a monitoring tool, etc.).
{% callout type="warning" title="Treat the URL as a secret" %}
The webhook URL contains 128 bits of entropy and acts as its own credential (similar to Slack webhook URLs). Anyone with the URL can send messages to your instance. Do not commit it to public repositories or share it in public channels.
{% /callout %}
## How It Works
1. An external service sends an HTTP POST to your webhook URL
2. The webhook worker validates the request (and optionally checks authentication)
3. The payload is rendered through your **prompt template** (see below)
4. The rendered message is delivered to your KiloClaw instance as a chat message
5. Your agent receives and responds to the message like any other conversation
## Prompt Template
The prompt template controls how webhook payloads are presented to your agent. You can customize it from the **Webhook Integration** section in Settings.
**Default template:**
```
You received a webhook event. Here is the payload:
{{bodyJson}}
```
**Available variables:**
| Variable | Description |
| --- | --- |
| `{{body}}` | Raw request body |
| `{{bodyJson}}` | Pretty-printed JSON body |
| `{{method}}` | HTTP method (e.g., `POST`) |
| `{{headers}}` | Request headers |
| `{{path}}` | Request path |
| `{{query}}` | Query string parameters |
| `{{timestamp}}` | Time the webhook was received |
You can tailor the template to give your agent more context. For example:
```
A GitHub push event just arrived. Summarize the changes and open a PR if any tests are affected.
Payload:
{{bodyJson}}
```
## Managing Your Webhook
Once set up, the Webhook Integration card in Settings gives you several controls:
### Pause and Resume
Toggle the **Active/Paused** switch to temporarily stop accepting webhooks without deleting the URL. When paused, incoming requests are rejected. Resume at any time to start accepting them again.
### Rotate URL
If your webhook URL is compromised, click **Rotate URL** to generate a new one. This immediately invalidates the old URL, so you will need to update your integrations with the new URL afterward. A confirmation dialog is shown before rotation.
### Webhook Authentication (Optional)
For additional security, you can require inbound requests to include a shared secret header. This is useful when the sending service supports webhook signing.
1. Toggle **Webhook Authentication** to enabled
2. Set the **Secret Header** name (default: `x-webhook-secret`)
3. Enter a **Shared Secret** value
4. Click **Save**
Requests missing the header or providing an incorrect secret are rejected.
{% callout type="note" title="Authentication is optional" %}
The webhook URL itself is already a credential (128-bit entropy). Authentication adds a second layer and is only needed if your sending service requires or supports it.
{% /callout %}
## Viewing Webhook Activity
KiloClaw webhooks also appear in the **Webhooks** page under Cloud (read only). From there you can click **View Captured Requests** to inspect recent payloads, response codes, and timing. This is useful for debugging integration issues.
## Example: GitHub Push Notifications
1. Set up a webhook in your KiloClaw Settings
2. In your GitHub repository, go to **Settings > Webhooks > Add webhook**
3. Paste your KiloClaw webhook URL as the **Payload URL**
4. Set **Content type** to `application/json`
5. Select the events you want to trigger on (e.g., **Just the push event**)
6. Click **Add webhook**
Now every push to that repository sends a payload to your agent. Customize the prompt template to tell the agent what to do with it. You could have it summarize commits, run checks, notify a channel, or anything else.
## Related
- [Scheduled Triggers](/docs/kiloclaw/triggers/scheduled)
- [Triggers Overview](/docs/kiloclaw/triggers)
- [KiloClaw Overview](/docs/kiloclaw/overview)
- [Dashboard Reference](/docs/kiloclaw/dashboard)
- [GitHub Integration](/docs/kiloclaw/development-tools/github)
- [Connecting Chat Platforms](/docs/kiloclaw/chat-platforms)
+9
View File
@@ -182,6 +182,15 @@ a:hover {
color: #dcdcaa;
}
/* Blockquotes */
.article-content blockquote {
border-left: 3px solid var(--border-color);
margin: 1em 0;
padding: 0.25em 1em;
color: var(--text-secondary);
font-style: italic;
}
/* Lists - Override Tailwind's reset to restore default list styling */
.article-content ul,
.article-content ol {
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

+7 -2
View File
@@ -1,7 +1,7 @@
# Source Code Links
<!-- Auto-generated by script/extract-source-links.ts — DO NOT EDIT -->
<!-- 77 unique URLs extracted from extension and CLI source -->
<!-- 79 unique URLs extracted from extension and CLI source -->
- <https://api.apertis.ai/v1>
<!-- packages/opencode/src/provider/model-cache.ts -->
@@ -10,6 +10,9 @@
<!-- packages/opencode/src/cli/cmd/github.ts -->
- <https://app.kilo.ai>
<!-- packages/opencode/src/kilocode/kilo-commands.tsx -->
- <https://app.kilo.ai/claw>
<!-- packages/opencode/src/kilocode/components/dialog-claw-setup.tsx -->
<!-- packages/opencode/src/kilocode/components/dialog-claw-upgrade.tsx -->
- <https://app.kilo.ai/config.json>
<!-- packages/opencode/src/config/config.ts -->
- <https://app.kilo.ai/credits>
@@ -18,7 +21,7 @@
<!-- packages/kilo-vscode/webview-ui/src/components/profile/ProfileView.tsx -->
- <https://app.kilo.ai/usage>
<!-- packages/opencode/src/kilocode/components/dialog-kilo-profile.tsx -->
- <https://blog.kilo.ai/we-completely-rebuilt-the-kilo-vs-code-extension>
- <https://blog.kilo.ai/p/new-kilo-for-vs-code-is-live>
<!-- packages/kilo-vscode/webview-ui/src/components/migration/MigrationWizard.tsx -->
- <https://chatgpt.com/explore/plus>
<!-- packages/opencode/src/provider/error.ts -->
@@ -84,6 +87,8 @@
<!-- packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx -->
- <https://kilo.ai/gateway>
<!-- packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx -->
- <https://kilo.ai/kiloclaw>
<!-- packages/opencode/src/kilocode/components/dialog-claw-setup.tsx -->
- <https://kilo.ai/support>
<!-- packages/kilo-vscode/webview-ui/src/components/chat/FeedbackDialog.tsx -->
<!-- packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx -->
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-gateway",
"version": "7.1.11",
"version": "7.1.21",
"type": "module",
"license": "MIT",
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
+2 -2
View File
@@ -27,8 +27,8 @@ export const DEFAULT_FREE_MODEL = "kilo-auto/free"
/** Token expiration duration in milliseconds (1 year) */
export const TOKEN_EXPIRATION_MS = 365 * 24 * 60 * 60 * 1000
/** User-Agent header value for requests */
export const USER_AGENT = "opencode-kilo-provider"
/** User-Agent header base value for requests */
export const USER_AGENT_BASE = "opencode-kilo-provider"
/** Content-Type header value for requests */
export const CONTENT_TYPE = "application/json"
+2 -2
View File
@@ -1,6 +1,6 @@
import { z } from "zod"
import { getKiloUrlFromToken } from "../auth/token.js"
import { DEFAULT_HEADERS } from "../headers.js"
import { getDefaultHeaders } from "../headers.js"
import { KILO_API_BASE, KILO_OPENROUTER_BASE, MODELS_FETCH_TIMEOUT_MS, PROMPTS, AI_SDK_PROVIDERS } from "./constants.js"
/**
@@ -88,7 +88,7 @@ export async function fetchKiloModels(options?: {
// Fetch models with timeout
const response = await fetch(modelsURL, {
headers: {
...DEFAULT_HEADERS,
...getDefaultHeaders(),
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
signal: AbortSignal.timeout(MODELS_FETCH_TIMEOUT_MS),
+2 -2
View File
@@ -1,6 +1,6 @@
import { z } from "zod"
import { KILO_API_BASE, MODELS_FETCH_TIMEOUT_MS } from "./constants.js"
import { DEFAULT_HEADERS } from "../headers.js"
import { getDefaultHeaders } from "../headers.js"
/**
* Group entry in an organization mode config.
@@ -70,7 +70,7 @@ export async function fetchOrganizationModes(token: string, organizationId: stri
const url = `${KILO_API_BASE}/api/organizations/${encodeURIComponent(organizationId)}/modes`
const response = await fetch(url, {
headers: {
...DEFAULT_HEADERS,
...getDefaultHeaders(),
Authorization: `Bearer ${token}`,
},
signal: AbortSignal.timeout(MODELS_FETCH_TIMEOUT_MS),
@@ -1,4 +1,4 @@
import open from "open"
import { execFile } from "child_process"
import type { DeviceAuthInitiateResponse, DeviceAuthPollResponse } from "../types.js"
import { poll } from "./polling.js"
import { getKiloProfile, getKiloDefaultModel } from "../api/profile.js"
@@ -70,10 +70,14 @@ export async function authenticateWithDeviceAuthTUI(inputs?: Record<string, stri
const authData = await initiateDeviceAuth()
const { code, verificationUrl, expiresIn } = authData
// Step 2: Open browser
await open(verificationUrl).catch(() => {
// Silently fail if browser can't be opened - user can manually open URL
})
// Step 2: Open browser (windowsHide: true prevents cmd.exe flash on Windows)
const [cmd, ...args] =
process.platform === "darwin"
? ["open", verificationUrl]
: process.platform === "win32"
? ["cmd", "/c", "start", "", verificationUrl]
: ["xdg-open", verificationUrl]
execFile(cmd, args, { windowsHide: true })
// Step 3: Return instructions and callback for TUI to handle
return {
+15 -4
View File
@@ -6,7 +6,7 @@ import {
HEADER_EDITORNAME,
HEADER_MACHINEID,
HEADER_FEATURE,
USER_AGENT,
USER_AGENT_BASE,
CONTENT_TYPE,
DEFAULT_EDITOR_NAME,
ENV_EDITOR_NAME,
@@ -36,12 +36,23 @@ export function getFeatureHeader(): string | undefined {
return process.env[ENV_FEATURE] || undefined
}
/**
* Get User-Agent header value.
* Appends the version from KILOCODE_VERSION when available.
*/
export function getUserAgent(): string {
const version = process.env[ENV_VERSION]
return version ? `${USER_AGENT_BASE}/${version}` : USER_AGENT_BASE
}
/**
* Default headers for KiloCode requests
*/
export const DEFAULT_HEADERS = {
"User-Agent": USER_AGENT,
"Content-Type": CONTENT_TYPE,
export function getDefaultHeaders(): Record<string, string> {
return {
"User-Agent": getUserAgent(),
"Content-Type": CONTENT_TYPE,
}
}
/**
+2 -2
View File
@@ -9,7 +9,7 @@ export { KiloAuthPlugin, default } from "./plugin.js"
export { createKilo } from "./provider.js"
export { createKiloDebug } from "./provider-debug.js"
export { kiloCustomLoader } from "./loader.js"
export { buildKiloHeaders, getEditorNameHeader, getFeatureHeader, DEFAULT_HEADERS } from "./headers.js"
export { buildKiloHeaders, getEditorNameHeader, getFeatureHeader, getDefaultHeaders, getUserAgent } from "./headers.js"
// ============================================================================
// Auth
@@ -86,7 +86,7 @@ export {
DEFAULT_MODEL,
DEFAULT_FREE_MODEL,
TOKEN_EXPIRATION_MS,
USER_AGENT,
USER_AGENT_BASE,
CONTENT_TYPE,
DEFAULT_PROVIDER_NAME,
ANONYMOUS_API_KEY,
+2 -2
View File
@@ -2,7 +2,7 @@ import { createOpenRouter } from "@openrouter/ai-sdk-provider"
import type { Provider as SDK } from "ai"
import type { KiloProviderOptions } from "./types.js"
import { getKiloUrlFromToken, getApiKey } from "./auth/token.js"
import { buildKiloHeaders, DEFAULT_HEADERS } from "./headers.js"
import { buildKiloHeaders, getDefaultHeaders } from "./headers.js"
import { KILO_API_BASE, ANONYMOUS_API_KEY } from "./api/constants.js"
/**
@@ -32,7 +32,7 @@ export function createKiloDebug(options: KiloProviderOptions = {}): SDK {
// Merge custom headers with defaults
const customHeaders = {
...DEFAULT_HEADERS,
...getDefaultHeaders(),
...buildKiloHeaders(undefined, {
kilocodeOrganizationId: options.kilocodeOrganizationId,
kilocodeTesterWarningsDisabledUntil: undefined,
+2 -2
View File
@@ -4,7 +4,7 @@ import { createOpenAI } from "@ai-sdk/openai"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
import type { KiloProvider, KiloProviderOptions } from "./types.js"
import { getKiloUrlFromToken, getApiKey } from "./auth/token.js"
import { buildKiloHeaders, DEFAULT_HEADERS } from "./headers.js"
import { buildKiloHeaders, getDefaultHeaders } from "./headers.js"
import { KILO_API_BASE, ANONYMOUS_API_KEY } from "./api/constants.js"
/**
@@ -39,7 +39,7 @@ export function createKilo(options: KiloProviderOptions = {}): KiloProvider {
// Merge custom headers with defaults
const customHeaders = {
...DEFAULT_HEADERS,
...getDefaultHeaders(),
...buildKiloHeaders(undefined, {
kilocodeOrganizationId: options.kilocodeOrganizationId,
kilocodeTesterWarningsDisabledUntil: undefined,
+121 -1
View File
@@ -8,7 +8,7 @@
import { fetchProfile, fetchBalance } from "../api/profile.js"
import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js"
import { fetchOrganizationModes, clearModesCache } from "../api/modes.js"
import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js"
import { KILO_API_BASE, HEADER_FEATURE, HEADER_ORGANIZATIONID } from "../api/constants.js"
import { buildKiloHeaders } from "../headers.js"
import type { ImportDeps, DrizzleDb } from "../cloud-sessions.js"
import { fetchCloudSession, fetchCloudSessionForImport, importSessionToDb } from "../cloud-sessions.js"
@@ -496,6 +496,126 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
}
},
)
.get(
"/claw/status",
describeRoute({
summary: "Get KiloClaw instance status",
description: "Fetch the user's KiloClaw instance status via the KiloClaw worker",
operationId: "kilo.claw.status",
responses: {
200: {
description: "Instance status",
content: {
"application/json": {
schema: resolver(
z.object({
status: z
.enum(["provisioned", "starting", "restarting", "running", "stopped", "destroying"])
.nullable(),
sandboxId: z.string().optional(),
flyRegion: z.string().optional(),
machineSize: z.object({ cpus: z.number(), memory_mb: z.number() }).optional(),
openclawVersion: z.string().nullable().optional(),
lastStartedAt: z.string().nullable().optional(),
lastStoppedAt: z.string().nullable().optional(),
channelCount: z.number().optional(),
secretCount: z.number().optional(),
userId: z.string().optional(),
}),
),
},
},
},
...errors(401, 502),
},
}),
async (c: any) => {
try {
const auth = await Auth.get("kilo")
if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json({ error: "No valid token found" }, 401)
const organizationId = auth.type === "oauth" ? auth.accountId : undefined
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}
if (organizationId) {
headers[HEADER_ORGANIZATIONID] = organizationId
}
const response = await fetch(`${KILO_API_BASE}/api/kiloclaw/status`, { headers })
if (!response.ok) {
const text = await response.text()
return c.json({ error: `KiloClaw request failed: ${response.status} ${text}` }, response.status as any)
}
return c.json(await response.json())
} catch (err: any) {
console.error("[Kilo Gateway] claw/status: error", err?.message ?? err)
return c.json({ error: "Failed to reach KiloClaw" }, 502)
}
},
)
.get(
"/claw/chat-credentials",
describeRoute({
summary: "Get KiloClaw chat credentials",
description: "Fetch Stream Chat credentials for the user's KiloClaw instance",
operationId: "kilo.claw.chatCredentials",
responses: {
200: {
description: "Stream Chat credentials or null",
content: {
"application/json": {
schema: resolver(
z
.object({
apiKey: z.string(),
userId: z.string(),
userToken: z.string(),
channelId: z.string(),
})
.nullable(),
),
},
},
},
...errors(401, 502),
},
}),
async (c: any) => {
try {
const auth = await Auth.get("kilo")
if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json({ error: "No valid token found" }, 401)
const organizationId = auth.type === "oauth" ? auth.accountId : undefined
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}
if (organizationId) {
headers[HEADER_ORGANIZATIONID] = organizationId
}
const response = await fetch(`${KILO_API_BASE}/api/kiloclaw/chat-credentials`, { headers })
if (!response.ok) {
const text = await response.text()
return c.json({ error: `KiloClaw request failed: ${response.status} ${text}` }, response.status as any)
}
return c.json(await response.json())
} catch (err: any) {
console.error("[Kilo Gateway] claw/chat-credentials: error", err?.message ?? err)
return c.json({ error: "Failed to reach KiloClaw" }, 502)
}
},
)
.get(
"/cloud-sessions",
describeRoute({
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-i18n",
"version": "7.1.11",
"version": "7.1.21",
"type": "module",
"license": "MIT",
"description": "Kilo-specific i18n translations and overrides",
+72
View File
@@ -0,0 +1,72 @@
export const dict = {
// Kilo Gateway provider translations
"provider.connect.kiloGateway.line1":
"Kilo Gateway надає доступ до добірки надійних оптимізованих моделей для агентів кодування.",
"provider.connect.kiloGateway.line2":
"За допомогою одного API-ключа ви отримаєте доступ до таких моделей, як Claude, GPT, Gemini, GLM та інших.",
"provider.connect.kiloGateway.visit.prefix": "Відвідайте ",
"provider.connect.kiloGateway.visit.link": "kilo.ai",
"provider.connect.kiloGateway.visit.suffix": " щоб отримати свій API-ключ.",
// Provider dialog translations
"dialog.provider.group.recommended": "Рекомендовані",
"dialog.provider.kilo.note": "Доступ до 500+ моделей ШІ",
// Reasoning block label
"ui.permission.run": "Виконати",
"ui.reasoning.label": "Міркування",
// Marketplace
"marketplace.tab.skills": "Навички",
"marketplace.tab.mcpServers": "MCP-сервери",
"marketplace.tab.modes": "Режими",
"marketplace.category.all": "Усі",
"marketplace.placeholder": "Буде реалізовано",
"marketplace.card.installed": "Встановлено",
"marketplace.card.install": "Встановити",
"marketplace.card.remove": "Видалити",
"marketplace.card.removeScope": "Видалити ({{scope}})",
"marketplace.card.showMore": "Показати більше",
"marketplace.card.showLess": "Показати менше",
"marketplace.install.title": "Встановити {{name}}",
"marketplace.install.scope": "Область",
"marketplace.install.scope.project": "Проєкт",
"marketplace.install.scope.global": "Глобально",
"marketplace.install.prerequisites": "Передумови",
"marketplace.install.installing": "Встановлення...",
"marketplace.install.cancel": "Скасувати",
"marketplace.install.success": "Успішно встановлено!",
"marketplace.install.failed": "Встановлення не вдалося",
"marketplace.install.done": "Готово",
"marketplace.install.close": "Закрити",
"marketplace.remove.title": "Видалити {{name}}?",
"marketplace.remove.confirm":
"Ви впевнені, що хочете видалити цей {{type}}? Це видалить його з вашої конфігурації {{scope}}.",
"marketplace.remove.cancel": "Скасувати",
"marketplace.remove.confirm.button": "Видалити",
"marketplace.tab.mcp": "MCP",
"marketplace.search": "Пошук...",
"marketplace.filter.all": "Усі елементи",
"marketplace.filter.notInstalled": "Не встановлено",
"marketplace.empty": "Елементів не знайдено",
"marketplace.badge.mcpServer": "MCP-сервер",
"marketplace.badge.mode": "Режим",
"marketplace.card.by": "від {{author}}",
"marketplace.install.method": "Метод встановлення",
"marketplace.install.parameters": "Параметри",
"marketplace.install.optional": "(необов'язково)",
"marketplace.install.required": "{{name}} є обов'язковим",
"marketplace.scope.project": "проєкт",
"marketplace.scope.global": "глобально",
"marketplace.remove.type.mcp": "MCP-сервер",
"marketplace.remove.type.skill": "навичка",
"marketplace.remove.type.mode": "режим",
"marketplace.remove.failed": "Не вдалося видалити {{name}}",
"marketplace.install": "Встановити",
"marketplace.filter.installed": "Встановлено",
"marketplace.error.dismiss": "Закрити",
"marketplace.warning.busyOne": "Виконується одна сесія, її буде перервано",
"marketplace.warning.busyMany": "Виконується кілька сесій, їх буде перервано",
"marketplace.warning.installAnyway": "Встановити все одно",
"marketplace.warning.cancel": "Скасувати",
}
+5
View File
@@ -0,0 +1,5 @@
.gradle/
build/
.intellijPlatform/
.ai/
!gradle/wrapper/gradle-wrapper.jar
+71
View File
@@ -0,0 +1,71 @@
# Kilo JetBrains
AI coding agent plugin for JetBrains IDEs.
---
## Set up your environment
### Prerequisites
- **Bun** -- used to build CLI binaries and run build scripts
- **JDK 21+** -- required by Gradle and the IntelliJ Platform SDK
- **IntelliJ IDEA** -- to run the plugin in a sandboxed IDE
---
## Open in IntelliJ
When you open the monorepo root in IntelliJ IDEA, the Gradle project at `packages/kilo-jetbrains/` should be auto-detected via `.idea/gradle.xml`. If not, link it manually: **File > Settings > Build Tools > Gradle > +** and select `packages/kilo-jetbrains/settings.gradle.kts`.
---
## Build locally
From `packages/kilo-jetbrains/`:
```
bun run build
```
This builds the CLI binary for your current OS/arch only, copies it into the backend module resources, and runs `./gradlew buildPlugin`. The plugin archive is output to `build/distributions/`.
Or via Turbo from the repo root:
```
bun turbo build --filter=@kilocode/kilo-jetbrains
```
---
## Build for production
From `packages/kilo-jetbrains/`:
```
bun run build:production
```
This builds CLI binaries for all 6 desktop platforms (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64, windows-arm64), copies them all into the backend jar, and fails if any are missing. Gradle also validates all platforms are present via `-Pproduction=true`.
The built plugin archive is at `build/distributions/kilo.jetbrains-<version>.zip`. This zip can be installed in any JetBrains IDE via **Settings > Plugins > Install Plugin from Disk**.
---
## Run the plugin
After building, use the `runIde` Gradle task (available in the Gradle tool window or via the "Run JetBrains Plugin" run configuration) to launch a sandboxed IntelliJ instance with the plugin installed.
Note: `runIde` does not build CLI binaries -- run `bun run build` at least once before using it. Subsequent Kotlin/Gradle changes can be iterated with `runIde` directly.
---
## Run Gradle directly
You can run `./gradlew buildPlugin` directly if CLI binaries are already in `backend/build/generated/cli/`. Gradle will fail with a clear error if they are missing.
For production verification:
```
./gradlew buildPlugin -Pproduction=true
```
@@ -0,0 +1,68 @@
plugins {
alias(libs.plugins.kotlin)
}
kotlin {
jvmToolchain(21)
}
sourceSets {
main {
resources.srcDir(layout.buildDirectory.dir("generated/cli"))
}
}
val cliDir = layout.buildDirectory.dir("generated/cli/cli")
val production = providers.gradleProperty("production").map { it.toBoolean() }.orElse(false)
val requiredPlatforms = listOf(
"darwin-arm64",
"darwin-x64",
"linux-arm64",
"linux-x64",
"windows-x64",
"windows-arm64",
)
val checkCli by tasks.registering {
description = "Verify CLI binaries exist before building"
val dir = cliDir.map { it.asFile }
val prod = production.get()
val platforms = requiredPlatforms.toList()
doLast {
val resolved = dir.get()
if (!resolved.exists() || resolved.listFiles()?.isEmpty() != false) {
throw GradleException(
"CLI binaries not found at ${resolved.absolutePath}.\n" +
"Run 'bun run build' from packages/kilo-jetbrains/ to build CLI and plugin together."
)
}
if (prod) {
val missing = platforms.filter { platform ->
val dir = File(resolved, platform)
val exe = if (platform.startsWith("windows")) "kilo.exe" else "kilo"
!File(dir, exe).exists()
}
if (missing.isNotEmpty()) {
throw GradleException(
"Production build requires all platform CLI binaries.\n" +
"Missing: ${missing.joinToString(", ")}\n" +
"Run 'bun run build:production' to build all platforms."
)
}
}
}
}
tasks.processResources {
dependsOn(checkCli)
}
dependencies {
intellijPlatform {
intellijIdea(libs.versions.intellij.platform)
bundledModule("intellij.platform.backend")
}
implementation(project(":shared"))
}
@@ -0,0 +1,6 @@
<idea-plugin>
<dependencies>
<module name="intellij.platform.backend"/>
<module name="kilo.jetbrains.shared"/>
</dependencies>
</idea-plugin>
+60
View File
@@ -0,0 +1,60 @@
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware.SplitModeTarget
group = "ai.kilocode.jetbrains"
version = "7.0.1"
plugins {
application
id("java")
alias(libs.plugins.intellij.platform)
alias(libs.plugins.kotlin) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.compose.compiler) apply false
}
subprojects {
apply(plugin = "org.jetbrains.intellij.platform.module")
}
allprojects {
repositories {
mavenCentral()
intellijPlatform {
defaultRepositories()
}
maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies/")
}
}
dependencies {
intellijPlatform {
intellijIdea(libs.versions.intellij.platform)
pluginModule(implementation(project(":shared")))
pluginModule(implementation(project(":frontend")))
pluginModule(implementation(project(":backend")))
testFramework(TestFrameworkType.Platform)
}
}
intellijPlatform {
splitMode = true
splitModeTarget = SplitModeTarget.BOTH
pluginVerification {
ides {
create(IntelliJPlatformType.IntellijIdeaCommunity, libs.versions.intellij.platform)
}
}
}
tasks.named<JavaExec>("runIde") {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf("-Dnosplash=true")
}
}
@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.kotlin)
alias(libs.plugins.compose.compiler)
}
kotlin {
jvmToolchain(21)
}
dependencies {
intellijPlatform {
intellijIdea(libs.versions.intellij.platform)
bundledModule("intellij.platform.frontend")
}
implementation(project(":shared"))
}
@@ -0,0 +1,6 @@
<idea-plugin>
<dependencies>
<module name="intellij.platform.frontend"/>
<module name="kilo.jetbrains.shared"/>
</dependencies>
</idea-plugin>
@@ -0,0 +1,4 @@
kotlin.stdlib.default.dependency=false
org.gradle.configuration-cache=true
org.gradle.caching=true
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m
@@ -0,0 +1,12 @@
[versions]
intellij-platform = "2025.3"
intellij-gradle-plugin = "2.10.5"
kotlin-jvm-plugin = "2.1.20"
kotlin-serialization-plugin = "2.1.20"
kotlin-serialization = "1.7.3"
[plugins]
intellij-platform = { id = "org.jetbrains.intellij.platform", version.ref = "intellij-gradle-plugin" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-jvm-plugin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin-serialization-plugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-jvm-plugin" }
Binary file not shown.
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+234
View File
@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+8
View File
@@ -0,0 +1,8 @@
{
"name": "@kilocode/kilo-jetbrains",
"private": true,
"scripts": {
"build": "bun script/build.ts",
"build:production": "bun script/build.ts --production"
}
}
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env bun
/**
* Build the Kilo JetBrains plugin.
*
* Usage:
* bun script/build.ts # Local build only current platform binary required
* bun script/build.ts --production # Production build all 6 platform binaries required
*
* Steps:
* 1. Builds CLI binaries (or uses prebuilt ones from dist/).
* Local: builds only current platform (--single).
* Production: builds all platforms.
* 2. Copies them into backend/build/generated/cli/cli/{os}/kilo[.exe]
* so they end up inside the backend jar at /cli/{os}/kilo.
* 3. Invokes Gradle to build the plugin.
*/
import { $ } from "bun"
import { join, relative } from "node:path"
import { existsSync, mkdirSync, chmodSync, cpSync, rmSync } from "node:fs"
const production = process.argv.includes("--production")
const root = join(import.meta.dir, "..")
const packages = join(root, "..")
const opencodeDir = join(packages, "opencode")
const distDir = join(opencodeDir, "dist")
const cliDir = join(root, "backend", "build", "generated", "cli", "cli")
/** All desktop platforms. */
const platforms = [
{ os: "darwin-arm64", exe: "kilo" },
{ os: "darwin-x64", exe: "kilo" },
{ os: "linux-arm64", exe: "kilo" },
{ os: "linux-x64", exe: "kilo" },
{ os: "windows-x64", exe: "kilo.exe" },
{ os: "windows-arm64", exe: "kilo.exe" },
] as const
function localPlatformTag(): string {
const os = process.platform === "win32" ? "windows" : process.platform
return `${os}-${process.arch}`
}
function log(msg: string) {
console.log(`[jetbrains-build] ${msg}`)
}
function distBinPath(os: string, exe: string): string {
return join(distDir, `@kilocode/cli-${os}`, "bin", exe)
}
function hasDist(): boolean {
if (production) {
return platforms.every((p) => existsSync(distBinPath(p.os, p.exe)))
}
const tag = localPlatformTag()
const local = platforms.find((p) => p.os === tag)
return local ? existsSync(distBinPath(local.os, local.exe)) : false
}
async function prepareCli() {
const mode = production ? "production" : "local"
log(`Mode: ${mode}`)
if (!hasDist()) {
log("Building CLI binaries via opencode...")
if (!existsSync(join(opencodeDir, "package.json"))) {
throw new Error(`Expected opencode package at ${opencodeDir}`)
}
const args = production ? [] : ["--single"]
await $`bun run build ${args}`.cwd(opencodeDir)
} else {
log("Found prebuilt CLI binaries in opencode/dist/, skipping CLI build")
}
if (existsSync(cliDir)) {
rmSync(cliDir, { recursive: true })
}
const missing: string[] = []
let copied = 0
for (const p of platforms) {
const src = distBinPath(p.os, p.exe)
if (!existsSync(src)) {
missing.push(p.os)
continue
}
const dir = join(cliDir, p.os)
mkdirSync(dir, { recursive: true })
const dest = join(dir, p.exe)
cpSync(src, dest)
chmodSync(dest, 0o755)
copied++
log(`Copied ${relative(root, src)} -> ${relative(root, dest)}`)
}
if (copied === 0) {
throw new Error("No CLI binaries were copied — the build cannot proceed")
}
if (production && missing.length > 0) {
throw new Error(`Production build requires all platform binaries. Missing: ${missing.join(", ")}`)
}
if (missing.length > 0) {
log(`Skipped ${missing.length} platforms (not needed for local build): ${missing.join(", ")}`)
}
log(`Copied ${copied}/${platforms.length} platform binaries`)
}
const gradlew = process.platform === "win32" ? "gradlew.bat" : "./gradlew"
async function buildPlugin() {
log("Building JetBrains plugin via Gradle...")
const args = production ? ["-Pproduction=true"] : []
await $`${gradlew} buildPlugin ${args}`.cwd(root)
log("Done. Plugin archive is in build/distributions/")
}
try {
await prepareCli()
await buildPlugin()
} catch (err) {
console.error(`[jetbrains-build] ERROR: ${err instanceof Error ? err.message : String(err)}`)
process.exit(1)
}
@@ -0,0 +1,13 @@
rootProject.name = "kilo.jetbrains"
include("shared")
include("frontend")
include("backend")
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies/")
}
}
@@ -0,0 +1,13 @@
plugins {
alias(libs.plugins.kotlin)
}
kotlin {
jvmToolchain(21)
}
dependencies {
intellijPlatform {
intellijIdea(libs.versions.intellij.platform)
}
}
@@ -0,0 +1,2 @@
<idea-plugin>
</idea-plugin>
@@ -0,0 +1,17 @@
<idea-plugin>
<name>Kilo Code</name>
<vendor url="https://kilo.ai">Kilo Code</vendor>
<description><![CDATA[
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.
]]></description>
<content>
<module name="kilo.jetbrains.shared" loading="required"/>
<module name="kilo.jetbrains.frontend"/>
<module name="kilo.jetbrains.backend"/>
</content>
</idea-plugin>
@@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M512 0H0V512H512V0Z" fill="black"/>
<path d="M512 512H0V0H512V512ZM322.783 322.784H278.261V392.747L308.472 422.958H378.435V378.437H322.782L322.783 322.784ZM422.957 308.474L392.746 278.263H322.783V322.784H378.435L378.435 378.437H422.957L422.957 308.474ZM233.739 278.263H189.217V322.784H233.739V278.263ZM89.0435 392.747L119.254 422.958H233.739V378.437H133.565V278.263H89.043L89.0435 392.747ZM372.538 189.217V119.254L342.327 89.0435H278.261V133.565H328.017V189.217H278.261V233.739H422.957V189.217H372.538ZM133.565 89.0435H89.0435V233.739H133.565V183.652H189.218V233.739H233.74V183.652L189.218 139.13H133.565V89.0435ZM233.739 89.0435H189.217L189.218 139.13H233.739V89.0435Z" fill="#FAF74F"/>
</svg>

After

Width:  |  Height:  |  Size: 810 B

+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-telemetry",
"version": "7.1.11",
"version": "7.1.21",
"type": "module",
"license": "MIT",
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-ui",
"version": "7.1.11",
"version": "7.1.21",
"type": "module",
"license": "MIT",
"exports": {
+1
View File
@@ -0,0 +1 @@
export * from "@opencode-ai/ui/i18n/uk"
+6 -6
View File
@@ -1,9 +1,9 @@
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=kilocode.Kilo-Code"><img src="https://raster.shields.io/badge/VS_Code_Marketplace-007ACC?style=flat&logo=visualstudiocode&logoColor=white" alt="VS Code Marketplace"></a>
<a href="https://x.com/kilocode"><img src="https://raster.shields.io/badge/kilocode-000000?style=flat&logo=x&logoColor=white" alt="X (Twitter)"></a>
<a href="https://blog.kilo.ai"><img src="https://raster.shields.io/badge/Blog-555?style=flat&logo=substack&logoColor=white" alt="Substack Blog"></a>
<a href="https://kilo.ai/discord"><img src="https://raster.shields.io/badge/Join%20Discord-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
<a href="https://www.reddit.com/r/kilocode/"><img src="https://raster.shields.io/badge/Join%20r%2Fkilocode-D84315?style=flat&logo=reddit&logoColor=white" alt="Reddit"></a>
<a href="https://marketplace.visualstudio.com/items?itemName=kilocode.Kilo-Code"><img src="https://raster.shields.io/badge/VS_Code_Marketplace-007ACC?style=flat&logo=visualstudiocode&logoColor=white" alt="VS Code Marketplace" height="20"></a>
<a href="https://x.com/kilocode"><img src="https://raster.shields.io/badge/kilocode-000000?style=flat&logo=x&logoColor=white" alt="X (Twitter)" height="20"></a>
<a href="https://blog.kilo.ai"><img src="https://raster.shields.io/badge/Blog-555?style=flat&logo=substack&logoColor=white" alt="Substack Blog" height="20"></a>
<a href="https://kilo.ai/discord"><img src="https://raster.shields.io/badge/Join%20Discord-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord" height="20"></a>
<a href="https://www.reddit.com/r/kilocode/"><img src="https://raster.shields.io/badge/Join%20r%2Fkilocode-D84315?style=flat&logo=reddit&logoColor=white" alt="Reddit" height="20"></a>
</p>
# 🚀 Kilo
@@ -40,7 +40,7 @@
2. Create your account to access 500+ cutting-edge AI models including Gemini 3 Pro, Claude 4.5 Sonnet & Opus, and GPT-5 with transparent pricing that matches provider rates exactly.
3. Start coding with AI that adapts to your workflow. Watch our quick-start guide to see Kilo in action:
[![Watch the video](https://img.youtube.com/vi/pqGfYXgrhig/maxresdefault.jpg)](https://youtu.be/pqGfYXgrhig)
<a href="https://youtu.be/pqGfYXgrhig"><img src="https://img.youtube.com/vi/pqGfYXgrhig/maxresdefault.jpg" alt="Watch the video" width="640" height="360"></a>
## Developer Setup
+30 -3
View File
@@ -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.11",
"version": "7.1.21",
"icon": "assets/icons/logo-outline-black.png",
"galleryBanner": {
"color": "#FFFFFF",
@@ -373,6 +373,26 @@
"command": "kilo-code.new.openInTab",
"group": "navigation",
"when": "true"
},
{
"command": "kilo-code.new.plusButtonClicked",
"group": "navigation@0",
"when": "activeWebviewPanelId == kilo-code.new.TabPanel"
},
{
"command": "kilo-code.new.historyButtonClicked",
"group": "navigation@1",
"when": "activeWebviewPanelId == kilo-code.new.TabPanel"
},
{
"command": "kilo-code.new.profileButtonClicked",
"group": "navigation@2",
"when": "activeWebviewPanelId == kilo-code.new.TabPanel"
},
{
"command": "kilo-code.new.settingsButtonClicked",
"group": "navigation@3",
"when": "activeWebviewPanelId == kilo-code.new.TabPanel"
}
],
"editor/context": [
@@ -633,7 +653,8 @@
"th",
"bs",
"tr",
"nl"
"nl",
"uk"
],
"enumDescriptions": [
"Auto (VS Code language)",
@@ -654,7 +675,8 @@
"ภาษาไทย",
"Bosanski",
"Türkçe",
"Nederlands"
"Nederlands",
"Українська"
]
},
"kilo-code.new.model.providerID": {
@@ -682,6 +704,11 @@
"default": false,
"description": "Enable chat textarea autocomplete"
},
"kilo-code.new.claudeCodeCompat": {
"type": "boolean",
"default": false,
"description": "Load CLAUDE.md instructions and skills from your Claude Code configuration directory into Kilo sessions. Enable this if you want Kilo to use your Claude Code instructions and skills."
},
"kilo-code.new.browserAutomation.enabled": {
"type": "boolean",
"default": false,
+2 -11
View File
@@ -36,10 +36,9 @@ 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, outDirProd]) {
for (const dir of [binDir, distDir, outDir]) {
if (existsSync(dir)) {
rmSync(dir, { recursive: true, force: true })
console.log(` ✓ Cleaned ${dir}`)
@@ -48,7 +47,6 @@ for (const dir of [binDir, distDir, outDir, outDirProd]) {
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`
@@ -84,18 +82,11 @@ for (const config of targets) {
console.log(` 📦 Packaging .vsix for ${config.target}...`)
const vsixPath = join(outDir, `kilo-vscode-${config.target}.vsix`)
await $`vsce package --pre-release --no-dependencies --skip-license --target ${config.target} -o ${vsixPath}`.env({
await $`vsce package --no-dependencies --skip-license --target ${config.target} -o ${vsixPath}`.env({
...process.env,
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!")
+3 -5
View File
@@ -7,7 +7,6 @@ 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}`)
@@ -40,13 +39,12 @@ 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}`
await $`vsce publish --packagePath ${vsixPath}`
console.log(` ✅ Published ${target} to VS Code Marketplace`)
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`)
await $`npx ovsx publish --pat ${process.env.OPENVSX_TOKEN} --packagePath ${vsixPath}`
console.log(` ✅ Published ${target} to Open VSX`)
}
if (Script.release) {
+137 -11
View File
@@ -39,11 +39,13 @@ import { MarketplaceService } from "./services/marketplace"
import { resolveProjectDirectory } from "./project-directory"
import { getBusySessionCount, seedSessionStatuses } from "./session-status"
import { slimPart, slimParts } from "./kilo-provider/slim-metadata"
import { matchFollowup, recordFollowup, type Followup } from "./kilo-provider/followup-session"
// legacy-migration start
import {
checkAndShowMigrationWizard,
handleRequestLegacyMigrationData,
handleStartLegacyMigration,
handleFinalizeLegacyMigration,
handleSkipLegacyMigration,
handleClearLegacyData,
type MigrationContext,
@@ -74,6 +76,7 @@ import {
computeDefaultSelection,
fetchProviderData,
validateRecents,
validateFavorites,
connectProvider as connectProviderAction,
authorizeProviderOAuth as authorizeOAuthAction,
completeProviderOAuth as completeOAuthAction,
@@ -143,7 +146,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
private unsubscribeNotificationDismiss: (() => void) | null = null
private unsubscribeLanguageChange: (() => void) | null = null
private unsubscribeProfileChange: (() => void) | null = null
private unsubscribeFavoritesChange: (() => void) | null = null
private unsubscribeMigrationComplete: (() => void) | null = null // legacy-migration
private unsubscribeClearPendingPrompts: (() => void) | null = null
private unsubscribeDirectoryProvider: (() => void) | null = null
private initConnectionPromise: Promise<void> | null = null
private webviewMessageDisposable: vscode.Disposable | null = null
@@ -154,6 +160,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
private chatAutocomplete: ChatTextAreaAutocomplete | null = null
private projectDirectory: string | null | undefined
private slimEditMetadata = true
private pendingFollowup: Followup | null = null
/** Worktree diff stats poller for the sidebar badge — reuses GitStatsPoller (local stats only) */
private statsPoller: GitStatsPoller | null = null
private cachedStats: unknown = null
@@ -175,6 +183,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
) {
this.projectDirectory = options?.projectDirectory
this.slimEditMetadata = options?.slimEditMetadata ?? true
TelemetryProxy.getInstance().setProvider(this)
}
@@ -468,6 +477,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
z.object({
mime: z.string(),
url: z.string().refine((u) => u.startsWith("file://") || u.startsWith("data:")),
filename: z.string().optional(),
}),
)
.optional()
@@ -477,6 +487,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
message.text,
typeof message.messageID === "string" ? message.messageID : undefined,
message.sessionID,
typeof message.draftID === "string" ? message.draftID : undefined,
message.providerID,
message.modelID,
message.agent,
@@ -491,6 +502,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
z.object({
mime: z.string(),
url: z.string().refine((u) => u.startsWith("file://") || u.startsWith("data:")),
filename: z.string().optional(),
}),
)
.optional()
@@ -501,6 +513,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
message.arguments,
typeof message.messageID === "string" ? message.messageID : undefined,
message.sessionID,
typeof message.draftID === "string" ? message.draftID : undefined,
message.providerID,
message.modelID,
message.agent,
@@ -580,6 +593,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "openSettingsPanel":
vscode.commands.executeCommand("kilo-code.new.settingsButtonClicked", message.tab)
break
case "openVSCodeSettings":
vscode.commands.executeCommand("workbench.action.openSettings", message.query)
break
case "openMarketplacePanel":
vscode.commands.executeCommand("kilo-code.new.marketplaceButtonClicked", this.projectDirectory)
break
@@ -675,10 +691,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
break
case "questionReply":
await handleQuestionReply(this.questionCtx, message.requestID, message.answers)
this.noteFollowup(message.answers, message.sessionID)
if (!(await handleQuestionReply(this.questionCtx, message.requestID, message.answers, message.sessionID))) {
this.pendingFollowup = null
}
break
case "questionReject":
await handleQuestionReject(this.questionCtx, message.requestID)
this.pendingFollowup = null
await handleQuestionReject(this.questionCtx, message.requestID, message.sessionID)
break
case "requestConfig":
this.fetchAndSendConfig().catch((e) => console.error("[Kilo New] fetchAndSendConfig failed:", e))
@@ -767,6 +787,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "requestBrowserSettings":
this.sendBrowserSettings()
break
case "requestClaudeCompatSetting":
this.sendClaudeCompatSetting()
break
case "requestNotificationSettings":
this.sendNotificationSettings()
break
@@ -840,6 +863,25 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.postMessage({ type: "recentsLoaded", recents })
break
}
case "toggleFavorite": {
const current = validateFavorites(this.extensionContext?.globalState.get("favoriteModels"))
const key = `${message.providerID}/${message.modelID}`
const exists = current.some((f) => `${f.providerID}/${f.modelID}` === key)
const favorites =
message.action === "add" && !exists
? [...current, { providerID: message.providerID, modelID: message.modelID }]
: message.action === "remove" && exists
? current.filter((f) => `${f.providerID}/${f.modelID}` !== key)
: current
await this.extensionContext?.globalState.update("favoriteModels", favorites)
this.connectionService.notifyFavoritesChanged(favorites)
break
}
case "requestFavorites": {
const favorites = validateFavorites(this.extensionContext?.globalState.get("favoriteModels"))
this.postMessage({ type: "favoritesLoaded", favorites })
break
}
// legacy-migration start
case "requestLegacyMigrationData":
void handleRequestLegacyMigrationData(this.migrationCtx)
@@ -853,6 +895,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "clearLegacyData":
void handleClearLegacyData(this.migrationCtx)
break
case "finalizeLegacyMigration":
void handleFinalizeLegacyMigration(this.migrationCtx)
break
// legacy-migration end
case "enhancePrompt": {
const sdkClient = this.client
@@ -955,6 +1000,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.unsubscribeNotificationDismiss?.()
this.unsubscribeLanguageChange?.()
this.unsubscribeProfileChange?.()
this.unsubscribeFavoritesChange?.()
this.unsubscribeClearPendingPrompts?.()
this.unsubscribeDirectoryProvider?.()
try {
const workspaceDir = this.getWorkspaceDirectory()
@@ -972,6 +1020,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
return event.type !== "message.part.updated" && event.type !== "message.part.delta"
}
if (event.type === "session.created" && this.matchesPendingFollowup(event.properties.info)) {
return true
}
// session.status must always pass through — even for sessions not tracked by this
// KiloProvider instance. The Settings panel is a separate provider with no tracked
// sessions, but it needs session.status to populate sessionStatusMap and allStatusMap
@@ -1026,6 +1078,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.postMessage({ type: "profileData", data })
})
// Subscribe to favorites change broadcast from other KiloProvider instances
this.unsubscribeFavoritesChange = this.connectionService.onFavoritesChanged((favorites) => {
this.postMessage({ type: "favoritesLoaded", favorites })
})
// legacy-migration start
// Subscribe to migration-complete broadcast from any KiloProvider instance
this.unsubscribeMigrationComplete = this.connectionService.onMigrationComplete(() => {
@@ -1033,6 +1090,16 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
})
// legacy-migration end
// Subscribe to clear-pending-prompts broadcast (fired after config save drains prompts)
this.unsubscribeClearPendingPrompts = this.connectionService.onClearPendingPrompts(() => {
this.postMessage({ type: "clearPendingPrompts" })
})
// Register this provider's directories so drainPendingPrompts() covers all instances
this.unsubscribeDirectoryProvider = this.connectionService.registerDirectoryProvider(() => {
return [this.getWorkspaceDirectory(), ...this.sessionDirectories.values()]
})
// Get current state and push to webview
const serverInfo = this.connectionService.getServerInfo()
this.connectionState = this.connectionService.getConnectionState()
@@ -1476,6 +1543,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
const method = typeof msg.method === "number" ? msg.method : 0
const key = typeof msg.apiKey === "string" ? msg.apiKey : undefined
const keyChanged = msg.apiKeyChanged === true
const code = typeof msg.code === "string" ? msg.code : undefined
const config = msg.config && typeof msg.config === "object" ? (msg.config as Record<string, unknown>) : undefined
if (msg.type === "connectProvider" && key) return connectProviderAction(ctx, rid, pid, key)
@@ -1483,7 +1551,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
if (msg.type === "completeProviderOAuth") return completeOAuthAction(ctx, rid, pid, method, code)
if (msg.type === "disconnectProvider") return disconnectProviderAction(ctx, rid, pid, this.cachedConfigMessage, set)
if (msg.type === "saveCustomProvider" && config)
return saveCustomProviderAction(ctx, rid, pid, config, key, this.cachedConfigMessage, set)
return saveCustomProviderAction(ctx, rid, pid, config, key, keyChanged, this.cachedConfigMessage, set)
}
private async handleFetchCustomProviderModels(msg: Record<string, unknown>): Promise<void> {
@@ -1988,6 +2056,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
// races with the async config.update() write on the CLI backend).
this.pending++
try {
// Reject all pending permissions and questions across every provider
// so their CLI-side Promises resolve before disposeAll() wipes
// Instance state. Throws on failure to abort the config save.
await this.connectionService.drainPendingPrompts()
await this.client.global.config.update({ config: partial }, { throwOnError: true })
// Re-fetch the full merged config (global + project + all layers) so the
@@ -2019,17 +2092,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}
/**
* Handle sending a message from the webview.
* Uses promptAsync (fire-and-forget) the server queues concurrent prompts
* internally, so the client doesn't need to block on busy sessions.
*/
/**
* Ensure a session exists, creating one if needed. Returns the resolved
* session ID and workspace directory, or undefined when the client is
* disconnected.
*/
private async resolveSession(sessionID?: string): Promise<{ sid: string; dir: string } | undefined> {
private async resolveSession(
sessionID?: string,
draftID?: string,
): Promise<{ sid: string; dir: string } | undefined> {
if (!this.client) return undefined
const dir = sessionID ? this.getWorkspaceDirectory(sessionID) : this.getContextDirectory()
@@ -2040,9 +2111,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.contextSessionID = session.id
this.trackDirectory(session.id, dir)
this.trackedSessionIds.add(session.id)
if (draftID) this.contextSessionID = session.id
this.postMessage({
type: "sessionCreated",
session: this.sessionToWebview(session),
draftID,
})
}
@@ -2056,6 +2129,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
text: string,
messageID?: string,
sessionID?: string,
draftID?: string,
providerID?: string,
modelID?: string,
agent?: string,
@@ -2068,6 +2142,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
error: "Not connected to CLI backend",
text,
sessionID,
draftID,
messageID,
files,
})
@@ -2076,7 +2151,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
let resolved: { sid: string; dir: string } | undefined
try {
resolved = await this.resolveSession(sessionID)
resolved = await this.resolveSession(sessionID, draftID)
const parts: Array<TextPartInput | FilePartInput> = []
if (files) {
@@ -2112,6 +2187,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
error: getErrorMessage(error) || "Failed to send message",
text,
sessionID: resolved?.sid ?? sessionID,
draftID,
messageID,
files,
})
@@ -2123,6 +2199,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
args: string,
messageID?: string,
sessionID?: string,
draftID?: string,
providerID?: string,
modelID?: string,
agent?: string,
@@ -2135,6 +2212,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
error: "Not connected to CLI backend",
text: `/${command} ${args}`.trim(),
sessionID,
draftID,
messageID,
files,
})
@@ -2143,7 +2221,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
let resolved: { sid: string; dir: string } | undefined
try {
resolved = await this.resolveSession(sessionID)
resolved = await this.resolveSession(sessionID, draftID)
if (messageID) {
this.connectionService.recordMessageSessionId(messageID, resolved!.sid)
@@ -2172,6 +2250,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
error: getErrorMessage(error) || "Failed to send command",
text: `/${command} ${args}`.trim(),
sessionID: resolved?.sid ?? sessionID,
draftID,
messageID,
files,
})
@@ -2479,6 +2558,17 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
})
}
/**
* Read the current Claude Code compatibility setting and push it to the webview.
*/
private sendClaudeCompatSetting(): void {
const enabled = vscode.workspace.getConfiguration("kilo-code.new").get<boolean>("claudeCodeCompat", false)
this.postMessage({
type: "claudeCompatSettingLoaded",
enabled: enabled ?? false,
})
}
/** Re-fetch all server-side state after an auth change. */
private async reloadAfterAuthChange(): Promise<void> {
await Promise.all([
@@ -2515,6 +2605,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
// Extract sessionID from the event
if (event.type === "session.created" && this.adoptPendingFollowup(event.properties.info)) {
return
}
const sessionID = this.connectionService.resolveEventSessionId(event)
// Events without sessionID (server.connected, server.heartbeat) → always forward
@@ -2773,6 +2867,35 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.sessionDirectories.set(sessionId, dir)
}
private noteFollowup(answers: string[][], sessionID?: string) {
const dir = this.getWorkspaceDirectory(sessionID)
this.pendingFollowup = recordFollowup({ answers, dir, now: Date.now() }) ?? null
}
private matchesPendingFollowup(session: Session) {
return matchFollowup({ pending: this.pendingFollowup, dir: session.directory, now: Date.now() })
}
private adoptPendingFollowup(session: Session) {
const now = Date.now()
const match = this.matchesPendingFollowup(session)
if (!match) {
if (
this.pendingFollowup &&
!matchFollowup({ pending: this.pendingFollowup, dir: this.pendingFollowup.dir, now })
) {
this.pendingFollowup = null
}
return false
}
this.pendingFollowup = null
this.trackDirectory(session.id, session.directory)
this.registerSession(session)
void this.handleLoadMessages(session.id)
return true
}
private getProjectDirectory(sessionId?: string): string | undefined {
return resolveProjectDirectory(this.projectDirectory, () => this.getWorkspaceDirectory(sessionId))
}
@@ -2860,7 +2983,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.unsubscribeNotificationDismiss?.()
this.unsubscribeLanguageChange?.()
this.unsubscribeProfileChange?.()
this.unsubscribeFavoritesChange?.()
this.unsubscribeMigrationComplete?.()
this.unsubscribeClearPendingPrompts?.()
this.unsubscribeDirectoryProvider?.()
this.webviewMessageDisposable?.dispose()
this.trackedSessionIds.clear()
this.syncedChildSessions.clear()
@@ -19,6 +19,7 @@ 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"
@@ -58,11 +59,11 @@ export class AgentManagerProvider implements Disposable {
private cachedWorktreeStats: AgentManagerOutMessage | undefined
private cachedLocalStats: AgentManagerOutMessage | undefined
private applyingWorktreeId: string | undefined
/** Session ID most recently loaded via a `loadMessages` message from the webview.
* Updated synchronously unlike the session provider's currentSession which depends on
* an async `session.get` round-trip and can be stale during rapid tab switches. */
private activeSessionId: string | undefined
constructor(
private readonly host: Host,
private readonly connectionService: KiloConnectionService,
@@ -172,9 +173,17 @@ export class AgentManagerProvider implements Disposable {
return
}
await state.load()
const migration = await state.load()
manager.cleanupOrphanedTempDirs()
// When the .kilocode → .kilo migration rewrote git worktree refs, nudge
// VS Code's git extension to re-discover them. Without this, worktrees
// won't appear in Source Control until the next VS Code restart.
if (migration.refsFixed > 0) {
this.log(`Migration fixed ${migration.refsFixed} git worktree ref(s), refreshing git`)
this.host.refreshGit()
}
// Do not auto-remove stale worktrees on load.
// Presence checks run in the shared poller and require explicit user cleanup.
@@ -222,6 +231,9 @@ export class AgentManagerProvider implements Disposable {
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)
if ((m.type === "sendMessage" || m.type === "sendCommand") && m.draftID && !m.sessionID) {
this.activeSessionId = m.draftID
}
if (m.type === "agentManager.configureSetupScript") {
void this.configureSetupScript()
return null
@@ -238,6 +250,10 @@ export class AgentManagerProvider implements Disposable {
this.openWorktreeDirectory(m.worktreeId)
return null
}
if (m.type === "agentManager.copyToClipboard") {
this.host.copyToClipboard(m.text)
return null
}
if (m.type === "previewImage") {
return msg
}
@@ -53,7 +53,7 @@ interface StateFile {
defaultBaseBranch?: string
}
import { KILO_DIR, migrateAgentManagerData } from "./constants"
import { KILO_DIR, migrateAgentManagerData, type MigrationResult } from "./constants"
const STATE_FILE = "agent-manager.json"
@@ -302,11 +302,12 @@ export class WorktreeStateManager {
// Persistence
// ---------------------------------------------------------------------------
async load(): Promise<void> {
async load(): Promise<MigrationResult> {
// Migrate Agent Manager data from .kilocode → .kilo before first read
let migration: MigrationResult = { refsFixed: 0 }
if (!this.migrated) {
this.migrated = true
await migrateAgentManagerData(this.root, this.log)
migration = await migrateAgentManagerData(this.root, this.log)
}
try {
const content = await fs.promises.readFile(this.file, "utf-8")
@@ -346,6 +347,7 @@ export class WorktreeStateManager {
this.log(`Failed to load state: ${error}`)
}
}
return migration
}
/** Remove worktrees whose directories no longer exist on disk. */
@@ -30,6 +30,12 @@ const AGENT_MANAGER_ITEMS = [
"setup-script.bat",
]
/** Result of the migration so callers can react (e.g. refresh VS Code git). */
export interface MigrationResult {
/** Number of git worktree refs that were rewritten from .kilocode → .kilo. */
refsFixed: number
}
/**
* Migrate Agent Manager data from .kilocode/ to .kilo/.
*
@@ -43,7 +49,7 @@ const AGENT_MANAGER_ITEMS = [
*
* Idempotent: safe to call on every startup.
*/
export async function migrateAgentManagerData(root: string, log: (msg: string) => void): Promise<void> {
export async function migrateAgentManagerData(root: string, log: (msg: string) => void): Promise<MigrationResult> {
const legacy = path.join(root, LEGACY_DIR)
const target = path.join(root, KILO_DIR)
@@ -77,9 +83,12 @@ export async function migrateAgentManagerData(root: string, log: (msg: string) =
}
}
let refsFixed = 0
if (await isDirectory(path.join(target, "worktrees"))) {
await fixGitWorktreeRefs(root, log)
refsFixed = await fixGitWorktreeRefs(root, log)
}
return { refsFixed }
}
async function exists(filepath: string): Promise<boolean> {
@@ -114,7 +123,7 @@ async function resolveGitDir(root: string): Promise<string | undefined> {
const content = await fs.promises.readFile(gitPath, "utf-8")
const match = content.match(/^gitdir:\s*(.+)$/m)
if (!match) return undefined
if (!match?.[1]) return undefined
// gitdir points to e.g. /repo/.git/worktrees/foo — go up two levels to /repo/.git
return path.resolve(path.dirname(gitPath), match[1].trim(), "..", "..")
} catch {
@@ -128,21 +137,29 @@ async function resolveGitDir(root: string): Promise<string | undefined> {
* Git stores absolute paths in .git/worktrees/{name}/gitdir. When the
* worktree directory moves, those paths become stale. This rewrites any
* gitdir files that reference the old .kilocode path.
*
* Returns the number of refs that were successfully fixed so callers can
* tell whether a VS Code git refresh is warranted.
*/
async function fixGitWorktreeRefs(root: string, log: (msg: string) => void): Promise<void> {
async function fixGitWorktreeRefs(root: string, log: (msg: string) => void): Promise<number> {
const gitDir = await resolveGitDir(root)
if (!gitDir) return
if (!gitDir) {
log("fixGitWorktreeRefs: could not resolve git directory")
return 0
}
const gitWorktreesDir = path.join(gitDir, "worktrees")
try {
const stat = await fs.promises.stat(gitWorktreesDir)
if (!stat.isDirectory()) return
} catch {
return
if (!stat.isDirectory()) return 0
} catch (err) {
log(`fixGitWorktreeRefs: ${gitWorktreesDir} not accessible: ${err}`)
return 0
}
const oldSegment = path.join(root, LEGACY_DIR) + path.sep
const newSegment = path.join(root, KILO_DIR) + path.sep
let fixed = 0
try {
const entries = await fs.promises.readdir(gitWorktreesDir, { withFileTypes: true })
@@ -151,15 +168,26 @@ async function fixGitWorktreeRefs(root: string, log: (msg: string) => void): Pro
const gitdirFile = path.join(gitWorktreesDir, entry.name, "gitdir")
try {
const content = await fs.promises.readFile(gitdirFile, "utf-8")
if (content.includes(oldSegment)) {
await fs.promises.writeFile(gitdirFile, content.replaceAll(oldSegment, newSegment))
if (!content.includes(oldSegment)) continue
const updated = content.replaceAll(oldSegment, newSegment)
await fs.promises.writeFile(gitdirFile, updated)
// Verify the write persisted — catch silent FS failures (e.g. read-only mount)
const verify = await fs.promises.readFile(gitdirFile, "utf-8")
if (verify === updated) {
fixed++
log(`Fixed git worktree ref: ${entry.name}`)
} else {
log(`Warning: git worktree ref write did not persist for ${entry.name}`)
}
} catch {
// gitdir file missing or unreadable — skip
} catch (err) {
log(`Warning: could not fix git worktree ref ${entry.name}: ${err}`)
}
}
} catch {
// .git/worktrees unreadable — skip
} catch (err) {
log(`Warning: could not read ${gitWorktreesDir}: ${err}`)
}
return fixed
}
@@ -101,9 +101,15 @@ export interface Host {
/** Get the CLI server port (for webview CSP). */
serverPort(): number | undefined
/** Copy text to the system clipboard. */
copyToClipboard(text: string): void
/** Capture a telemetry event. */
capture(event: string, properties?: Record<string, unknown>): void
/** Ask VS Code's git extension to re-scan repositories (e.g. after worktree ref migration). */
refreshGit(): void
/** Dispose all host resources. */
dispose(): void
}
@@ -262,6 +262,11 @@ interface OpenWorktreeIn {
worktreeId: string
}
interface CopyToClipboardIn {
type: "agentManager.copyToClipboard"
text: string
}
interface ShowExistingLocalTerminalIn {
type: "agentManager.showExistingLocalTerminal"
}
@@ -401,6 +406,33 @@ interface LoadMessagesIn {
sessionID: string
}
interface SendMessageIn {
type: "sendMessage"
text: string
messageID?: string
sessionID?: string
draftID?: string
providerID?: string
modelID?: string
agent?: string
variant?: string
files?: Array<{ mime: string; url: string; filename?: string }>
}
interface SendCommandIn {
type: "sendCommand"
command: string
arguments: string
messageID?: string
sessionID?: string
draftID?: string
providerID?: string
modelID?: string
agent?: string
variant?: string
files?: Array<{ mime: string; url: string; filename?: string }>
}
interface ClearSessionIn {
type: "clearSession"
}
@@ -435,6 +467,7 @@ export type AgentManagerInMessage =
| ShowTerminalIn
| ShowLocalTerminalIn
| OpenWorktreeIn
| CopyToClipboardIn
| ShowExistingLocalTerminalIn
| RequestRepoInfoIn
| CreateMultiVersionIn
@@ -460,6 +493,8 @@ export type AgentManagerInMessage =
| GenericOpenFileIn
| PreviewImageIn
| LoadMessagesIn
| SendMessageIn
| SendCommandIn
| ClearSessionIn
| AbortIn
| ContinueInWorktreeIn
@@ -152,9 +152,19 @@ export class VscodeHost implements Host {
return this.connectionService.getServerInfo()?.port
}
copyToClipboard(text: string): void {
void vscode.env.clipboard.writeText(text)
}
capture(event: string, properties?: Record<string, unknown>): void {
TelemetryProxy.capture(event as TelemetryEventName, properties)
}
refreshGit(): void {
// Trigger VS Code's built-in git extension to re-scan repositories.
// This picks up worktrees whose gitdir refs were just rewritten by migration.
void vscode.commands.executeCommand("git.refresh")
}
dispose(): void {}
}
+24 -3
View File
@@ -45,6 +45,18 @@ export function activate(context: vscode.ExtensionContext) {
}
})
// Track all open tab panel providers so toolbar button commands can target them.
// NOTE: The editor/title toolbar for tab panels intentionally omits Agent Manager
// and Marketplace buttons (unlike the sidebar). Too many icons causes VS Code to
// collapse them into a "..." overflow menu, hiding important buttons like Settings.
const tabPanels = new Map<vscode.WebviewPanel, KiloProvider>()
const activeTabProvider = () => {
for (const [panel, p] of tabPanels) {
if (panel.active) return p
}
return undefined
}
// Create the provider with shared service
const provider = new KiloProvider(context.extensionUri, connectionService, context)
@@ -94,9 +106,11 @@ export function activate(context: vscode.ExtensionContext) {
agentManagerProvider.continueFromSidebar(sessionId, progress),
)
tabProvider.resolveWebviewPanel(panel)
tabPanels.set(panel, tabProvider)
panel.onDidDispose(
() => {
console.log("[Kilo New] Tab panel restored from restart disposed")
tabPanels.delete(panel)
tabProvider.dispose()
},
null,
@@ -158,7 +172,9 @@ export function activate(context: vscode.ExtensionContext) {
// Register toolbar button command handlers
context.subscriptions.push(
vscode.commands.registerCommand("kilo-code.new.plusButtonClicked", () => {
provider.postMessage({ type: "action", action: "plusButtonClicked" })
const tab = activeTabProvider()
if (tab) tab.postMessage({ type: "action", action: "plusButtonClicked" })
else provider.postMessage({ type: "action", action: "plusButtonClicked" })
}),
vscode.commands.registerCommand("kilo-code.new.agentManagerOpen", () => {
agentManagerProvider.openPanel()
@@ -167,7 +183,9 @@ export function activate(context: vscode.ExtensionContext) {
settingsEditorProvider.openPanel("marketplace", undefined, directory)
}),
vscode.commands.registerCommand("kilo-code.new.historyButtonClicked", () => {
provider.postMessage({ type: "action", action: "historyButtonClicked" })
const tab = activeTabProvider()
if (tab) tab.postMessage({ type: "action", action: "historyButtonClicked" })
else provider.postMessage({ type: "action", action: "historyButtonClicked" })
}),
vscode.commands.registerCommand("kilo-code.new.cycleAgentMode", () => {
provider.postMessage({ type: "action", action: "cycleAgentMode" })
@@ -199,7 +217,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, agentManagerProvider)
return openKiloInNewTab(context, connectionService, agentManagerProvider, tabPanels)
}),
vscode.commands.registerCommand("kilo-code.new.showChanges", () => {
diffViewerProvider.openPanel()
@@ -330,6 +348,7 @@ async function openKiloInNewTab(
context: vscode.ExtensionContext,
connectionService: KiloConnectionService,
agentManagerProvider: AgentManagerProvider,
tabPanels: Map<vscode.WebviewPanel, KiloProvider>,
) {
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((e) => e.viewColumn || 0), 0)
const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
@@ -356,6 +375,7 @@ async function openKiloInNewTab(
agentManagerProvider.continueFromSidebar(sessionId, progress),
)
tabProvider.resolveWebviewPanel(panel)
tabPanels.set(panel, tabProvider)
// Wait for the new panel to become active before locking the editor group.
// This avoids the race where VS Code hasn't switched focus yet.
@@ -365,6 +385,7 @@ async function openKiloInNewTab(
panel.onDidDispose(
() => {
console.log("[Kilo New] Tab panel disposed")
tabPanels.delete(panel)
tabProvider.dispose()
},
null,
@@ -225,7 +225,7 @@ export type WebviewMessage =
| { type: "questionResolved"; requestID: string }
| { type: "permissionResolved"; permissionID: string }
| { type: "permissionError"; permissionID: string }
| { type: "sessionCreated"; session: ReturnType<typeof sessionToWebview> }
| { type: "sessionCreated"; session: ReturnType<typeof sessionToWebview>; draftID?: string }
| { type: "sessionUpdated"; session: ReturnType<typeof sessionToWebview> }
| { type: "messageRemoved"; sessionID: string; messageID: string }
| { type: "sessionError"; sessionID?: string; error?: unknown }
@@ -0,0 +1,25 @@
import { normalizePath } from "../agent-manager/git-import"
const TTL = 30_000
const LABEL = "Start new session"
export interface Followup {
dir: string
time: number
}
export function recordFollowup(input: { answers: string[][]; dir: string; now: number }): Followup | undefined {
const answer = input.answers[0]?.[0]?.trim()
if (answer !== LABEL) return
return {
dir: input.dir,
time: input.now,
}
}
export function matchFollowup(input: { pending: Followup | null; dir: string; now: number }): boolean {
const item = input.pending
if (!item) return false
if (input.now - item.time > TTL) return false
return normalizePath(item.dir) === normalizePath(input.dir)
}
@@ -215,6 +215,7 @@ export async function handleImportAndSend(
error: err instanceof Error ? err.message : "Failed to send message after import",
text,
sessionID: session.id,
draftID: session.id,
messageID,
files,
})
@@ -6,7 +6,11 @@
*/
import type { KiloClient } from "@kilocode/sdk/v2/client"
import type { LegacyMigrationData, MigrationSelections } from "../../legacy-migration/legacy-types"
import type {
LegacyMigrationData,
MigrationSelections,
MigrationSessionProgress,
} from "../../legacy-migration/legacy-types"
import * as MigrationService from "../../legacy-migration/migration-service"
/** Subset of vscode.ExtensionContext needed by migration handlers. */
@@ -31,10 +35,22 @@ export interface MigrationContext {
refreshSessions(): void
cachedLegacyData: LegacyMigrationData | null
migrationCheckInFlight: boolean
lastMigrationHadErrors?: boolean
disposeGlobal(): Promise<void>
broadcastComplete(): void
}
function postSessionProgress(ctx: MigrationContext, progress: MigrationSessionProgress): void {
ctx.postMessage({
type: "legacyMigrationSessionProgress",
session: progress.session,
index: progress.index,
total: progress.total,
phase: progress.phase,
error: progress.error,
})
}
/**
* Check for legacy data on first run and send migration state to the webview
* if the user has not yet been prompted.
@@ -111,26 +127,17 @@ export async function handleStartLegacyMigration(
(item, status, message) => {
ctx.postMessage({ type: "legacyMigrationProgress", item, status, message })
},
(progress: MigrationSessionProgress) => {
postSessionProgress(ctx, progress)
},
ctx.cachedLegacyData?.settings,
ctx.cachedLegacyData?.sessions,
)
const failed = results.some((r) => r.status === "error")
const success = results.some((r) => r.status === "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<typeof MigrationService.setMigrationStatus>[0],
"completed",
)
ctx.broadcastComplete()
ctx.refreshSessions()
}
ctx.lastMigrationHadErrors = results.some((item) => item.status === "error")
ctx.postMessage({ type: "legacyMigrationComplete", results })
} catch (error) {
ctx.lastMigrationHadErrors = true
console.error("[Kilo New] KiloProvider: ❌ Migration failed", error)
ctx.postMessage({
type: "legacyMigrationComplete",
@@ -146,6 +153,17 @@ export async function handleStartLegacyMigration(
}
}
export async function handleFinalizeLegacyMigration(ctx: MigrationContext): Promise<void> {
if (!ctx.extensionContext) return
await ctx.disposeGlobal()
await MigrationService.setMigrationStatus(
ctx.extensionContext as Parameters<typeof MigrationService.setMigrationStatus>[0],
ctx.lastMigrationHadErrors ? "completed_with_errors" : "completed",
)
ctx.broadcastComplete()
ctx.refreshSessions()
}
/** Record that the user skipped migration and broadcast to all instances. */
export async function handleSkipLegacyMigration(ctx: MigrationContext): Promise<void> {
if (!ctx.extensionContext) return
@@ -15,37 +15,51 @@ interface QuestionContext {
}
/** Handle question reply from the webview. */
export async function handleQuestionReply(ctx: QuestionContext, requestID: string, answers: string[][]): Promise<void> {
export async function handleQuestionReply(
ctx: QuestionContext,
requestID: string,
answers: string[][],
sessionID?: string,
): Promise<boolean> {
if (!ctx.client) {
ctx.postMessage({ type: "questionError", requestID })
return
return false
}
const sid = sessionID ?? ctx.currentSessionId
try {
await ctx.client.question.reply(
{ requestID, answers, directory: ctx.getWorkspaceDirectory(ctx.currentSessionId) },
{ requestID, answers, directory: ctx.getWorkspaceDirectory(sid) },
{ throwOnError: true },
)
return true
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to reply to question:", error)
ctx.postMessage({ type: "questionError", requestID })
return false
}
}
/** Handle question reject (dismiss) from the webview. */
export async function handleQuestionReject(ctx: QuestionContext, requestID: string): Promise<void> {
export async function handleQuestionReject(
ctx: QuestionContext,
requestID: string,
sessionID?: string,
): Promise<boolean> {
if (!ctx.client) {
ctx.postMessage({ type: "questionError", requestID })
return
return false
}
const sid = sessionID ?? ctx.currentSessionId
try {
await ctx.client.question.reject(
{ requestID, directory: ctx.getWorkspaceDirectory(ctx.currentSessionId) },
{ throwOnError: true },
)
await ctx.client.question.reject({ requestID, directory: ctx.getWorkspaceDirectory(sid) }, { throwOnError: true })
return true
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to reject question:", error)
ctx.postMessage({ type: "questionError", requestID })
return false
}
}

Some files were not shown because too many files have changed in this diff Show More