diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index db735ac3599..9519e440c07 100644
--- a/packages/app/src/i18n/ar.ts
+++ b/packages/app/src/i18n/ar.ts
@@ -134,8 +134,7 @@ export const dict = {
"provider.connect.oauth.code.invalid": "رمز التفويض غير صالح",
"provider.connect.oauth.auto.visit.prefix": "قم بزيارة ",
"provider.connect.oauth.auto.visit.link": "هذا الرابط",
- "provider.connect.oauth.auto.visit.suffix":
- " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.",
+ "provider.connect.oauth.auto.visit.suffix": " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.",
"provider.connect.oauth.auto.confirmationCode": "رمز التأكيد",
"provider.connect.toast.connected.title": "تم توصيل {{provider}}",
"provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.",
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index 5aac1d03d07..b211045e6b4 100644
--- a/packages/app/src/i18n/fr.ts
+++ b/packages/app/src/i18n/fr.ts
@@ -389,8 +389,7 @@ export const dict = {
"toast.session.unshare.failed.description": "Une erreur s'est produite lors de l'annulation du partage de la session",
"toast.session.listFailed.title": "Échec du chargement des sessions pour {{project}}",
"toast.update.title": "Mise à jour disponible",
- "toast.update.description":
- "Une nouvelle version d'Kilo ({{version}}) est maintenant disponible pour installation.",
+ "toast.update.description": "Une nouvelle version d'Kilo ({{version}}) est maintenant disponible pour installation.",
"toast.update.action.installRestart": "Installer et redémarrer",
"toast.update.action.notYet": "Pas encore",
"error.page.title": "Quelque chose s'est mal passé",
@@ -524,8 +523,7 @@ export const dict = {
"sidebar.workspaces.enable": "Activer les espaces de travail",
"sidebar.workspaces.disable": "Désactiver les espaces de travail",
"sidebar.gettingStarted.title": "Commencer",
- "sidebar.gettingStarted.line1":
- "Kilo inclut des modèles gratuits pour que vous puissiez commencer immédiatement.",
+ "sidebar.gettingStarted.line1": "Kilo inclut des modèles gratuits pour que vous puissiez commencer immédiatement.",
"sidebar.gettingStarted.line2":
"Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessions récentes",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index 53819544191..6ed8f81b6f3 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -146,8 +146,7 @@ export const dict = {
"provider.connect.oauth.code.invalid": "รหัสการอนุญาตไม่ถูกต้อง",
"provider.connect.oauth.auto.visit.prefix": "เยี่ยมชม ",
"provider.connect.oauth.auto.visit.link": "ลิงก์นี้",
- "provider.connect.oauth.auto.visit.suffix":
- " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo",
+ "provider.connect.oauth.auto.visit.suffix": " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo",
"provider.connect.oauth.auto.confirmationCode": "รหัสยืนยัน",
"provider.connect.toast.connected.title": "{{provider}} ที่เชื่อมต่อแล้ว",
"provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index fa5dee31bd6..d25288c4d24 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -147,8 +147,7 @@ export const dict = {
"provider.connect.oauth.code.invalid": "授權碼無效",
"provider.connect.oauth.auto.visit.prefix": "造訪 ",
"provider.connect.oauth.auto.visit.link": "此連結",
- "provider.connect.oauth.auto.visit.suffix":
- " 並輸入以下程式碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。",
+ "provider.connect.oauth.auto.visit.suffix": " 並輸入以下程式碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。",
"provider.connect.oauth.auto.confirmationCode": "確認碼",
"provider.connect.toast.connected.title": "{{provider}} 已連線",
"provider.connect.toast.connected.description": "現在可以使用 {{provider}} 模型了。",
diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx
index 0a461ca79f8..6fb7ce6319b 100644
--- a/packages/app/src/pages/layout/sidebar-items.tsx
+++ b/packages/app/src/pages/layout/sidebar-items.tsx
@@ -32,9 +32,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
0 && props.notify }}
diff --git a/packages/kilo-docs/pages/404.tsx b/packages/kilo-docs/pages/404.tsx
index c2d4c612f6f..db14a31ddaa 100644
--- a/packages/kilo-docs/pages/404.tsx
+++ b/packages/kilo-docs/pages/404.tsx
@@ -1,8 +1,19 @@
-import React from "react"
+import React, { useState, useEffect } from "react"
import Link from "next/link"
import Head from "next/head"
+const subtitles = [
+ "The page you requested does not exist or has been moved.",
+ "That link is dead, and may have ridden off into the sunset on a pink pony 🦄",
+]
+
export default function Custom404() {
+ const [subtitle, setSubtitle] = useState("")
+
+ useEffect(() => {
+ setSubtitle(subtitles[Math.floor(Math.random() * subtitles.length)])
+ }, [])
+
return (
<>
@@ -39,7 +50,7 @@ export default function Custom404() {
{/* Message */}
Page not found
-
The page you requested does not exist or has been moved.
+ {subtitle &&
{subtitle}
}
{/* Actions */}
diff --git a/packages/kilo-docs/pages/automate/code-reviews/github.md b/packages/kilo-docs/pages/automate/code-reviews/github.md
index f967dabceb1..2547cd02c58 100644
--- a/packages/kilo-docs/pages/automate/code-reviews/github.md
+++ b/packages/kilo-docs/pages/automate/code-reviews/github.md
@@ -17,15 +17,7 @@ Kilo's Code Reviews integrate with GitHub via a **GitHub App** to automatically
### Step 1: Install the GitHub App
-1. Go to the **Integrations** page:
- - **Personal**: [app.kilo.ai/integrations/github](https://app.kilo.ai/integrations/github)
- - **Organization**: Your organization → Integrations → GitHub
-2. Click **Install GitHub App**
-3. Choose which GitHub account or organization to install the app on
-4. Select repository access:
- - **All repositories** — the app can access all current and future repos
- - **Only select repositories** — choose specific repos
-5. Click **Install**
+Connect your GitHub account via the [Integrations page](/docs/automate/integrations#connecting-github). Once connected, return here to configure the Review Agent.
The GitHub App requests the following permissions:
diff --git a/packages/kilo-docs/pages/automate/code-reviews/gitlab.md b/packages/kilo-docs/pages/automate/code-reviews/gitlab.md
index 4d795e1771b..0087a26dd7e 100644
--- a/packages/kilo-docs/pages/automate/code-reviews/gitlab.md
+++ b/packages/kilo-docs/pages/automate/code-reviews/gitlab.md
@@ -21,48 +21,9 @@ Both **GitLab.com** and **self-hosted GitLab instances** are supported.
### Step 1: Connect GitLab
-You can connect using **OAuth** or a **Personal Access Token (PAT)**. Choose the method that matches your GitLab setup:
+Connect your GitLab account via the [Integrations page](/docs/automate/integrations#connecting-gitlab). You can use **OAuth** (GitLab.com or self-hosted) or a **Personal Access Token (PAT)**.
-{% tabs %}
-{% tab label="OAuth (GitLab.com)" %}
-
-1. Go to the **Integrations** page:
- - **Personal**: [app.kilo.ai/integrations/gitlab](https://app.kilo.ai/integrations/gitlab)
- - **Organization**: Your organization → Integrations → GitLab
-2. Click **Connect GitLab**
-3. Authorize the application on GitLab
-4. You'll be redirected back to Kilo with the connection active
-
-{% /tab %}
-{% tab label="OAuth (Self-Hosted)" %}
-
-For self-hosted GitLab instances using OAuth, you need to register an OAuth application first:
-
-1. In your GitLab instance, go to **Admin Area → Applications** (or **User Settings → Applications**)
-2. Create a new application:
- - **Name**: `Kilo Code`
- - **Redirect URI**: `https://app.kilo.ai/api/integrations/gitlab/callback`
- - **Scopes**: `api`, `read_user`, `read_repository`, `write_repository`
- - **Confidential**: Yes
-3. Copy the **Application ID** and **Secret**
-4. In Kilo, go to the GitLab integration page
-5. Enter your **Instance URL**, **Client ID**, and **Client Secret**
-6. Click **Connect** and authorize
-
-{% /tab %}
-{% tab label="Personal Access Token" %}
-
-1. In GitLab, go to **User Settings → Access Tokens**
-2. Create a token with the `api` scope
-3. Copy the token
-4. In Kilo, go to the GitLab integration page
-5. Paste the token (and enter your Instance URL for self-hosted)
-6. Click **Connect**
-
-> PAT tokens cannot be refreshed automatically. When your token expires, create a new one in GitLab and reconnect in Kilo.
-
-{% /tab %}
-{% /tabs %}
+Once connected, return here to configure the Review Agent.
### Step 2: Configure the Review Agent
diff --git a/packages/kilo-docs/pages/automate/code-reviews/overview.md b/packages/kilo-docs/pages/automate/code-reviews/overview.md
index 25d4bdfa27d..c89e043a9a4 100644
--- a/packages/kilo-docs/pages/automate/code-reviews/overview.md
+++ b/packages/kilo-docs/pages/automate/code-reviews/overview.md
@@ -26,7 +26,7 @@ Kilo's **Code Reviews** feature automatically analyzes your pull or merge reques
Before enabling Code Reviews:
-- **A platform integration must be configured:** Connect your GitHub or GitLab account via the [Integrations page](https://app.kilo.ai/integrations) so that the Review Agent can access your repositories.
+- **A platform integration must be configured:** Connect your GitHub or GitLab account via the [Integrations page](https://app.kilo.ai/integrations) so that the Review Agent can access your repositories. See the [Integration setup guide](/docs/automate/integrations) for detailed instructions.
- **Kilo Code credits:** The AI model uses credits when analyzing your code.
## Cost
diff --git a/packages/kilo-docs/pages/automate/integrations.md b/packages/kilo-docs/pages/automate/integrations.md
index b9468a8f3ba..1a8dd1e9126 100644
--- a/packages/kilo-docs/pages/automate/integrations.md
+++ b/packages/kilo-docs/pages/automate/integrations.md
@@ -5,32 +5,42 @@ description: "Overview of Kilo Code integrations"
# Kilo Code Integrations
-Kilo Integrations lets you connect your GitHub account (and soon, GitLab and Bitbucket) to enable advanced features inside Kilo Code. Once connected, Kilo can access your repositories securely through the **KiloConnect** GitHub App, enabling features like **Cloud Agents** and **Kilo Deploy**.
+Kilo Integrations lets you connect your GitHub or GitLab account (soon Bitbucket) to enable advanced features inside Kilo Code. Once connected, Kilo can access your repositories securely, enabling features like **Code Reviews**, **Cloud Agents**, and **Kilo Deploy**.
+
+## Supported Platforms
+
+| Platform | Integration Type | Details |
+| -------- | ---------------- | --------------------------------- |
+| GitHub | GitHub App | [GitHub Setup](#connecting-github)|
+| GitLab | OAuth or PAT | [GitLab Setup](#connecting-gitlab)|
## What You Can Do With Integrations
-- **Connect GitHub to Kilo Code** in a few clicks
-- **Authorize the KiloConnect App** for repo access
-- **Enable advanced features** like Cloud Agents and Kilo Deploy
+- **Connect GitHub or GitLab to Kilo Code** in a few clicks
+- **Enable advanced features** like Cloud Agents, Code Reviews, and Kilo Deploy
+- **Authorize repository access** so Kilo can analyze and work with your code
## Prerequisites
Before connecting:
-- You must have a **GitHub account**.
-- You need permission to install GitHub Apps for the repositories you want Kilo to access.
-- (Optional) If you're connecting an organization, you must be an **org admin** or have app installation permissions.
+- You must have a **GitHub** or **GitLab** account.
+- For GitHub: You need permission to install GitHub Apps for the repositories you want Kilo to access.
+- For GitLab: You need **Maintainer** role (or higher) on the projects you want to connect.
+- (Optional) If you're connecting an organization, you must be an admin or have app installation permissions.
-## Connecting GitHub to Kilo
+---
+
+## Connecting GitHub
### 1. Open the Integrations Page
-Go to your **Personal** or **Organization Dashboard**, and navigate to the [Integrations](https://app.kilo.ai/integrations) tab
+Go to your **Personal** or **Organization Dashboard**, and navigate to the [Integrations](https://app.kilo.ai/integrations) tab.
### 2. Start the Connection Flow
1. Click **Configure** on the GitHub panel.
-2. You’ll be redirected to GitHub to authorize the **KiloConnect** App.
+2. You'll be redirected to GitHub to authorize the **KiloConnect** App.
3. Select the GitHub account or organization you want to connect.
### 3. Choose Repository Access
@@ -46,13 +56,62 @@ Click **Install & Authorize** to continue.
Once approved:
-- You’ll return to the Kilo Integrations page.
-- Github will show a **Connected** status.
+- You'll return to the Kilo Integrations page.
+- GitHub will show a **Connected** status.
- Your Kilo workspace can now access GitHub repositories securely.
+---
+
+## Connecting GitLab
+
+You can connect GitLab using **OAuth** or a **Personal Access Token (PAT)**. Both **GitLab.com** and **self-hosted GitLab instances** are supported.
+
+{% tabs %}
+{% tab label="OAuth (GitLab.com)" %}
+
+1. Go to the **Integrations** page:
+ - **Personal**: [app.kilo.ai/integrations/gitlab](https://app.kilo.ai/integrations/gitlab)
+ - **Organization**: Your organization → Integrations → GitLab
+2. Click **Connect GitLab**
+3. Authorize the application on GitLab
+4. You'll be redirected back to Kilo with the connection active
+
+{% /tab %}
+{% tab label="OAuth (Self-Hosted)" %}
+
+For self-hosted GitLab instances using OAuth, you need to register an OAuth application first:
+
+1. In your GitLab instance, go to **Admin Area → Applications** (or **User Settings → Applications**)
+2. Create a new application:
+ - **Name**: `Kilo Code`
+ - **Redirect URI**: `https://app.kilo.ai/api/integrations/gitlab/callback`
+ - **Scopes**: `api`, `read_user`, `read_repository`, `write_repository`
+ - **Confidential**: Yes
+3. Copy the **Application ID** and **Secret**
+4. In Kilo, go to the GitLab integration page
+5. Enter your **Instance URL**, **Client ID**, and **Client Secret**
+6. Click **Connect** and authorize
+
+{% /tab %}
+{% tab label="Personal Access Token" %}
+
+1. In GitLab, go to **User Settings → Access Tokens**
+2. Create a token with the `api` scope
+3. Copy the token
+4. In Kilo, go to the GitLab integration page
+5. Paste the token (and enter your Instance URL for self-hosted)
+6. Click **Connect**
+
+> PAT tokens cannot be refreshed automatically. When your token expires, create a new one in GitLab and reconnect in Kilo.
+
+{% /tab %}
+{% /tabs %}
+
+---
+
## What Happens After Connecting
-Once GitHub is connected, the following features will be enabled in Kilo:
+Once your Git provider is connected, the following features are enabled in Kilo:
### Cloud Agents
@@ -60,6 +119,12 @@ Once GitHub is connected, the following features will be enabled in Kilo:
- Auto-create branches and push work continuously
- Work from anywhere while keeping your repo in sync
+### Code Reviews
+
+- Automated AI review on every pull request or merge request
+- Consistent feedback based on your team's standards
+- See the [Code Reviews guide](/docs/automate/code-reviews/overview) for setup
+
### Kilo Deploy
- Deploy Next.js 14 & 15 apps directly from Kilo
@@ -68,29 +133,61 @@ Once GitHub is connected, the following features will be enabled in Kilo:
### Upcoming:
-- **GitLab Integration**
- **Bitbucket Integration**
---
## Managing or Removing the Integration
-From the same **Integrations** page, you can click "Manage on Github" to:
+### GitHub
+
+From the **Integrations** page, click "Manage on GitHub" to:
- View the GitHub account you connected
- Update which repositories Kilo has access to
- Disconnect GitHub entirely
- Reauthorize the app if permissions change
+### GitLab
+
+From the **Integrations** page:
+
+- Click **Disconnect** to remove the GitLab connection
+- Your tokens are cleared, but webhook configuration is preserved so reconnecting restores your setup
+
+> Disconnecting from Kilo does not revoke OAuth tokens on GitLab's side. You can manually revoke them from **GitLab → User Settings → Applications → Authorized Applications**.
+
---
## Troubleshooting
-**“I don’t see my repositories.”**
+### GitHub
+
+**"I don't see my repositories."**
Ensure the KiloConnect App is installed for the correct GitHub org and that repo access includes the repositories you need.
-**“My organization blocks third-party apps.”**
+**"My organization blocks third-party apps."**
You may need an admin to approve installing GitHub Apps.
-**“Cloud Agents or Deploy can’t access my repo.”**
+**"Cloud Agents or Deploy can't access my repo."**
Revisit the GitHub app settings and confirm the app has the correct repo scope.
+
+### GitLab
+
+**"No projects listed after connecting."**
+Click the refresh button to sync projects from GitLab. Ensure your GitLab account has access to the projects you expect.
+
+**"Permission denied" errors.**
+You need **Maintainer role** on the GitLab project for webhook and bot token creation.
+
+**"Token expired."**
+
+- **OAuth**: Tokens refresh automatically. If refresh fails, reconnect from the integration page.
+- **PAT**: Create a new token in GitLab and reconnect in Kilo.
+
+**"Self-hosted connection issues."**
+
+- Verify your instance URL is accessible from the internet
+- Ensure HTTPS is configured
+- Check that OAuth application scopes include all required scopes
+- Verify the redirect URI matches: `https://app.kilo.ai/api/integrations/gitlab/callback`
diff --git a/packages/kilo-docs/pages/automate/mcp/using-in-cli.md b/packages/kilo-docs/pages/automate/mcp/using-in-cli.md
index e2689999b89..eae0fb42193 100644
--- a/packages/kilo-docs/pages/automate/mcp/using-in-cli.md
+++ b/packages/kilo-docs/pages/automate/mcp/using-in-cli.md
@@ -20,7 +20,7 @@ The CLI accepts several config filenames. The recommended file is `kilo.json`:
| **Global** | `~/.config/kilo/kilo.json` | `kilo.jsonc`, `config.json` |
| **Project** | `./kilo.json` or `./.kilo/kilo.json` | `kilo.jsonc` |
-Project-level configuration takes precedence over global settings.
+Project-level configuration takes precedence over global settings.
## Configuration Format
diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/slack.md b/packages/kilo-docs/pages/code-with-ai/platforms/slack.md
index 43b8bec5da2..eca1d4c83cc 100644
--- a/packages/kilo-docs/pages/code-with-ai/platforms/slack.md
+++ b/packages/kilo-docs/pages/code-with-ai/platforms/slack.md
@@ -18,12 +18,21 @@ Kilo for Slack brings the power of Kilo Code directly into your Slack workspace.
---
+## Supported Platforms
+
+| Platform | Integration Type | Details |
+| -------- | ---------------- | --------------------------------------------------------------------|
+| GitHub | GitHub App | [GitHub Setup Guide](/docs/automate/integrations#connecting-github) |
+| GitLab | OAuth or PAT | [GitLab Setup Guide](/docs/automate/integrations#connecting-gitlab) |
+
+---
+
## Prerequisites
Before using Kilo for Slack:
- You must have a **Kilo Code account** with available credits
-- Your **GitHub Integration must be configured** via the [Integrations tab](https://app.kilo.ai/integrations) so Kilo can access your repositories
+- Your **Git provider integration must be configured** via the [Integrations tab](https://app.kilo.ai/integrations) so Kilo can access your repositories
To install Kilo for Slack, simply go to the integrations menu in the sidebar on https://app.kilo.ai and set up the Slack integration.
@@ -105,9 +114,9 @@ Can you help me understand what's causing it?
## How It Works
1. **Message Kilo** — Either through DMs or by mentioning it in a channel
-2. **Kilo processes your request** — Kilo uses your connected GitHub repositories to understand context
+2. **Kilo processes your request** — Kilo uses your connected repositories to understand context
3. **AI generates a response** — Kilo Code's AI analyzes your request and provides helpful responses
-4. **Code changes (if requested)** — For implementation requests, Kilo can create pull requests
+4. **Code changes (if requested)** — For implementation requests, Kilo can create pull or merge requests
---
@@ -157,10 +166,10 @@ Kilo for Slack supports over 400+ models across different providers.
Ensure Kilo for Slack is installed in your workspace and has been added to the channel you're using.
**"Kilo can't access my repository."**
-Verify your GitHub integration is configured correctly in the [Integrations tab](https://app.kilo.ai/integrations).
+Verify your Git provider integration is configured correctly in the [Integrations tab](https://app.kilo.ai/integrations).
**"I'm getting incomplete responses."**
Try breaking your request into smaller, more specific questions.
**"Kilo doesn't understand my codebase."**
-Make sure the repository you're asking about is connected and accessible through your GitHub integration.
+Make sure the repository you're asking about is connected and accessible through your Git provider integration.
diff --git a/packages/kilo-docs/pages/contributing/architecture/auto-model-tiers.md b/packages/kilo-docs/pages/contributing/architecture/auto-model-tiers.md
index 578fbdd4f63..e6ae9bc611b 100644
--- a/packages/kilo-docs/pages/contributing/architecture/auto-model-tiers.md
+++ b/packages/kilo-docs/pages/contributing/architecture/auto-model-tiers.md
@@ -85,10 +85,10 @@ Extend Kilo Auto into four tiers.
**Model options for Auto: Small**:
-| Model | Cost | Capability | Notes |
-| ----- | ---- | ---------- | ----- |
+| Model | Cost | Capability | Notes |
+| ----------- | ---------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------- |
| gpt-oss-20b | ~50% cheaper than GPT-5 Nano | Lower — suitable for simple background tasks like titles and summaries | Open-weight, cost-optimized option for high-volume lightweight tasks |
-| GPT-5 Nano | Higher than gpt-oss-20b | Higher — better instruction following and output quality | Preferred when credits are available and task quality matters |
+| GPT-5 Nano | Higher than gpt-oss-20b | Higher — better instruction following and output quality | Preferred when credits are available and task quality matters |
Auto: Small should prefer **gpt-oss-20b** when minimizing cost (e.g., free users, high-volume background tasks) and **GPT-5 Nano** when credits are available and higher output quality is desired.
diff --git a/packages/kilo-docs/pages/contributing/architecture/index.md b/packages/kilo-docs/pages/contributing/architecture/index.md
index 2e5f212b154..dcafcbe1dff 100644
--- a/packages/kilo-docs/pages/contributing/architecture/index.md
+++ b/packages/kilo-docs/pages/contributing/architecture/index.md
@@ -24,7 +24,7 @@ graph LR
gateway["Kilo Gateway"]
cloudagent["Cloud Agent"]
bot["Kilo Bot"]
- claw["Kilo Claw"]
+ claw["KiloClaw"]
review["Code Review"]
triage["Auto Triage"]
appbuilder["App Builder"]
@@ -111,7 +111,7 @@ A Cloudflare Worker within Kilo Cloud that runs the Kilo CLI in isolated sandbox
The GitHub/GitLab bot that responds to issue comments and PR mentions. It dispatches work to the Cloud Agent, enabling users to trigger AI coding tasks directly from their repositories.
-### Kilo Claw
+### KiloClaw
A multi-tenant compute platform running on Fly.io, orchestrated by a Cloudflare Worker. Each user gets a dedicated persistent machine running an OpenClaw gateway, coordinated via Durable Objects for state management and self-healing reconciliation.
@@ -229,10 +229,10 @@ The project uses:
## Repositories
-| Repository | Contents |
-| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
-| [Kilo-Org/kilocode](https://github.com/Kilo-Org/kilocode) | CLI engine, VS Code extension, SDK, gateway client, telemetry, docs, UI components |
-| Cloud (private) | Web dashboard, Cloud Agent, Kilo Bot, Kilo Claw, code review, auto triage, billing, and supporting Cloudflare Workers |
+| Repository | Contents |
+| --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
+| [Kilo-Org/kilocode](https://github.com/Kilo-Org/kilocode) | CLI engine, VS Code extension, SDK, gateway client, telemetry, docs, UI components |
+| Cloud (private) | Web dashboard, Cloud Agent, Kilo Bot, KiloClaw, code review, auto triage, billing, and supporting Cloudflare Workers |
## Further Reading
diff --git a/packages/kilo-docs/pages/customize/agents-md.md b/packages/kilo-docs/pages/customize/agents-md.md
index d451b5e7a7d..cd34077e7a0 100644
--- a/packages/kilo-docs/pages/customize/agents-md.md
+++ b/packages/kilo-docs/pages/customize/agents-md.md
@@ -18,7 +18,7 @@ If you'd like to migrate your memory bank content to AGENTS.md:
1. Examine the contents in `.kilocode/rules/memory-bank/`
2. Move that content into your project's `AGENTS.md` file (or ask Kilo to do it for you)
-{% /callout %}
+ {% /callout %}
## What is AGENTS.md?
diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts
index 2c162db9a40..f848333af2c 100644
--- a/packages/kilo-gateway/src/server/routes.ts
+++ b/packages/kilo-gateway/src/server/routes.ts
@@ -233,10 +233,10 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
const endpoint = new URL("fim/completions", baseApiUrl)
const headers = {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- ...buildKiloHeaders(undefined, { kilocodeOrganizationId: organizationId }),
- [HEADER_FEATURE]: "autocomplete",
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ ...buildKiloHeaders(undefined, { kilocodeOrganizationId: organizationId }),
+ [HEADER_FEATURE]: "autocomplete",
}
const response = await fetch(endpoint, {
diff --git a/packages/kilo-vscode/src/agent-manager/SetupScriptRunner.ts b/packages/kilo-vscode/src/agent-manager/SetupScriptRunner.ts
index 3703c4bb6c7..040a48eb889 100644
--- a/packages/kilo-vscode/src/agent-manager/SetupScriptRunner.ts
+++ b/packages/kilo-vscode/src/agent-manager/SetupScriptRunner.ts
@@ -1,15 +1,12 @@
/**
* SetupScriptRunner - Executes worktree setup scripts
*
- * Runs setup scripts in VS Code integrated terminal before agent starts.
- * Uses VS Code shell integration to track execution and exit code.
- * Falls back to sendText + onDidCloseTerminal if shell integration is unavailable.
- * Cross-platform: Unix uses sh, Windows uses cmd.exe.
+ * Runs setup scripts as VS Code tasks before the agent starts.
+ * This relies on VS Code's task execution model instead of manual terminal command strings.
*/
import * as vscode from "vscode"
-import { SetupScriptService } from "./SetupScriptService"
-import { buildSetupCommand } from "./setup-script-command"
+import { SetupScriptService, type SetupScriptInfo } from "./SetupScriptService"
export interface SetupScriptEnvironment {
/** Absolute path to the worktree directory */
@@ -18,6 +15,37 @@ export interface SetupScriptEnvironment {
repoPath: string
}
+interface SetupTaskCommand {
+ command: string
+ args: string[]
+}
+
+const TASK_END_GRACE_MS = 250
+const TASK_TIMEOUT_MS = 5 * 60 * 1000
+
+function quoteCmdArg(value: string): string {
+ return `"${value.replaceAll('"', '""')}"`
+}
+
+function buildSetupTaskCommand(script: SetupScriptInfo): SetupTaskCommand {
+ if (script.kind === "powershell") {
+ return {
+ command: "powershell.exe",
+ args: ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script.path],
+ }
+ }
+ if (script.kind === "cmd") {
+ return {
+ command: "cmd.exe",
+ args: ["/d", "/s", "/c", quoteCmdArg(script.path)],
+ }
+ }
+ return {
+ command: "sh",
+ args: [script.path],
+ }
+}
+
export class SetupScriptRunner {
constructor(
private readonly output: vscode.OutputChannel,
@@ -31,16 +59,16 @@ export class SetupScriptRunner {
* @returns true if script was executed, false if skipped (no script configured)
*/
async runIfConfigured(env: SetupScriptEnvironment): Promise {
- if (!this.service.hasScript()) {
+ const script = this.service.resolveScript()
+ if (!script) {
this.log("No setup script configured, skipping")
return false
}
- const script = this.service.getScriptPath()
- this.log(`Running setup script: ${script}`)
+ this.log(`Running setup script: ${script.path}`)
try {
- await this.executeInTerminal(script, env)
+ await this.executeTask(script, env)
this.log("Setup script completed")
return true
} catch (error) {
@@ -50,99 +78,96 @@ export class SetupScriptRunner {
}
}
- /** Execute the setup script in a VS Code terminal and wait for it to finish. */
- private async executeInTerminal(script: string, env: SetupScriptEnvironment): Promise {
- const terminal = vscode.window.createTerminal({
- name: "Worktree Setup",
+ /** Execute setup script as a VS Code task and wait for completion. */
+ private async executeTask(script: SetupScriptInfo, env: SetupScriptEnvironment): Promise {
+ const task = this.createTask(script, env)
+ const execution = await vscode.tasks.executeTask(task)
+ await this.waitForTaskEnd(execution)
+ }
+
+ private createTask(script: SetupScriptInfo, env: SetupScriptEnvironment): vscode.Task {
+ const cmd = buildSetupTaskCommand(script)
+ const execution = new vscode.ProcessExecution(cmd.command, cmd.args, {
cwd: env.worktreePath,
env: {
WORKTREE_PATH: env.worktreePath,
REPO_PATH: env.repoPath,
},
- iconPath: new vscode.ThemeIcon("gear"),
})
-
- terminal.show(true)
-
- // Try shell integration first — gives us proper exit code tracking
- const integration = await this.waitForShellIntegration(terminal, 5000)
- if (integration) {
- this.log("Using shell integration for setup script execution")
- await this.runViaShellIntegration(terminal, integration, script, env)
- } else {
- this.log("Shell integration unavailable, falling back to sendText")
- await this.runViaSendText(terminal, script, env)
+ const task = new vscode.Task(
+ {
+ type: "kilo-worktree-setup",
+ script: script.path,
+ },
+ vscode.TaskScope.Workspace,
+ "Worktree Setup",
+ "Kilo Code",
+ execution,
+ [],
+ )
+ task.presentationOptions = {
+ reveal: vscode.TaskRevealKind.Always,
+ panel: vscode.TaskPanelKind.Dedicated,
+ clear: true,
+ showReuseMessage: false,
}
+ return task
}
- /** Wait for shell integration to become available on a terminal, with timeout. */
- private waitForShellIntegration(
- terminal: vscode.Terminal,
- timeout: number,
- ): Promise {
- if (terminal.shellIntegration) return Promise.resolve(terminal.shellIntegration)
-
- return new Promise((resolve) => {
- const timer = setTimeout(() => {
- listener.dispose()
- resolve(undefined)
- }, timeout)
-
- const listener = vscode.window.onDidChangeTerminalShellIntegration((e) => {
- if (e.terminal !== terminal) return
- clearTimeout(timer)
- listener.dispose()
- resolve(e.shellIntegration)
- })
- })
- }
-
- /** Run script via shell integration — tracks execution and exit code properly. */
- private runViaShellIntegration(
- terminal: vscode.Terminal,
- integration: vscode.TerminalShellIntegration,
- script: string,
- env: SetupScriptEnvironment,
- ): Promise {
- return new Promise((resolve) => {
- const command = buildSetupCommand(script, env)
- const execution = integration.executeCommand(command)
-
- const cleanup = () => {
- execListener.dispose()
- closeListener.dispose()
+ private waitForTaskEnd(execution: vscode.TaskExecution): Promise {
+ return new Promise((resolve, reject) => {
+ const state = {
+ done: false,
+ grace: undefined as ReturnType | undefined,
+ timeout: undefined as ReturnType | undefined,
}
- // Primary: shell integration reports execution finished with exit code
- const execListener = vscode.window.onDidEndTerminalShellExecution((e) => {
- if (e.execution !== execution) return
- cleanup()
- this.log(`Setup script exited with code ${e.exitCode ?? "unknown"}`)
+ const finish = (error?: Error) => {
+ if (state.done) return
+ state.done = true
+ if (state.grace) {
+ clearTimeout(state.grace)
+ }
+ if (state.timeout) {
+ clearTimeout(state.timeout)
+ }
+ processListener.dispose()
+ endListener.dispose()
+ if (error) {
+ reject(error)
+ return
+ }
resolve()
+ }
+
+ const processListener = vscode.tasks.onDidEndTaskProcess((event) => {
+ if (event.execution !== execution) return
+ this.log(`Setup script exited with code ${event.exitCode ?? "unknown"}`)
+ const code = event.exitCode
+ if (typeof code !== "number") {
+ finish(new Error("Setup script exited without a valid exit code"))
+ return
+ }
+ if (code !== 0) {
+ finish(new Error(`Setup script exited with code ${code}`))
+ return
+ }
+ finish()
})
- // Fallback: terminal was closed externally (user, VS Code restart, etc.)
- const closeListener = vscode.window.onDidCloseTerminal((closed) => {
- if (closed !== terminal) return
- cleanup()
- this.log("Setup script terminal closed before execution event fired")
- resolve()
- })
- })
- }
-
- /** Fallback: run via sendText and wait for terminal to close. */
- private runViaSendText(terminal: vscode.Terminal, script: string, env: SetupScriptEnvironment): Promise {
- return new Promise((resolve) => {
- const listener = vscode.window.onDidCloseTerminal((closed) => {
- if (closed !== terminal) return
- listener.dispose()
- resolve()
+ const endListener = vscode.tasks.onDidEndTask((event) => {
+ if (event.execution !== execution) return
+ if (state.done) return
+ state.grace = setTimeout(() => {
+ this.log("Setup script finished without process exit event")
+ finish()
+ }, TASK_END_GRACE_MS)
})
- const command = buildSetupCommand(script, env) + (process.platform === "win32" ? "& exit" : "; exit")
- terminal.sendText(command)
- this.log("Setup script started in terminal, waiting for completion...")
+ state.timeout = setTimeout(() => {
+ this.log("Setup script timed out waiting for task completion")
+ finish(new Error("Setup script timed out after 5 minutes"))
+ }, TASK_TIMEOUT_MS)
})
}
diff --git a/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts b/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts
index 26f08eec22c..c48ea0d0189 100644
--- a/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts
+++ b/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts
@@ -1,66 +1,132 @@
/**
* SetupScriptService - Manages worktree setup scripts
*
- * Handles reading, creating, and checking for setup scripts stored in .kilocode/setup-script.
+ * Handles reading, creating, and checking for setup scripts stored in .kilocode/.
* Setup scripts run before an agent starts in a worktree (new sessions only).
*/
import * as vscode from "vscode"
import * as fs from "node:fs"
import * as path from "node:path"
-import { SETUP_SCRIPT_TEMPLATE } from "./setup-script-template"
+import { SETUP_SCRIPT_TEMPLATE, SETUP_SCRIPT_TEMPLATE_POWERSHELL } from "./setup-script-template"
const SETUP_SCRIPT_FILENAME = "setup-script"
+const SETUP_SCRIPT_SHELL_FILENAME = "setup-script.sh"
+const SETUP_SCRIPT_POWERSHELL_FILENAME = "setup-script.ps1"
+const SETUP_SCRIPT_CMD_FILENAME = "setup-script.cmd"
+const SETUP_SCRIPT_BAT_FILENAME = "setup-script.bat"
const KILOCODE_DIR = ".kilocode"
+export type SetupScriptKind = "posix" | "powershell" | "cmd"
+type SetupScriptDefaultKind = Exclude
+
+export interface SetupScriptInfo {
+ path: string
+ kind: SetupScriptKind
+}
+
+interface SetupScriptCandidate {
+ name: string
+ kind: SetupScriptKind
+}
+
+interface SetupScriptDefaultCandidate {
+ name: string
+ kind: SetupScriptDefaultKind
+}
+
export class SetupScriptService {
- private readonly root: string
- private readonly script: string
+ private readonly dir: string
constructor(root: string) {
- this.root = root
- this.script = path.join(root, KILOCODE_DIR, SETUP_SCRIPT_FILENAME)
+ this.dir = path.join(root, KILOCODE_DIR)
}
- /** Get the path to the setup script */
- getScriptPath(): string {
- return this.script
+ /** Resolve the setup script path and interpreter type for the current platform. */
+ resolveScript(platform: NodeJS.Platform = process.platform): SetupScriptInfo | undefined {
+ for (const candidate of this.candidates(platform)) {
+ const scriptPath = path.join(this.dir, candidate.name)
+ if (!fs.existsSync(scriptPath)) continue
+ return {
+ path: scriptPath,
+ kind: candidate.kind,
+ }
+ }
+ return undefined
+ }
+
+ /** Get the script path if configured, otherwise return the default path for this platform. */
+ getScriptPath(platform: NodeJS.Platform = process.platform): string {
+ const resolved = this.resolveScript(platform)
+ if (resolved) return resolved.path
+ return path.join(this.dir, this.defaultCandidate(platform).name)
}
/** Check if a setup script exists */
- hasScript(): boolean {
- return fs.existsSync(this.script)
+ hasScript(platform: NodeJS.Platform = process.platform): boolean {
+ return this.resolveScript(platform) !== undefined
}
/** Read the setup script content. Returns null if not found or read fails. */
- async getScript(): Promise {
- if (!this.hasScript()) return null
+ async getScript(platform: NodeJS.Platform = process.platform): Promise {
+ const script = this.resolveScript(platform)
+ if (!script) return null
try {
- return await fs.promises.readFile(this.script, "utf-8")
+ return await fs.promises.readFile(script.path, "utf-8")
} catch (error) {
this.log(`Failed to read setup script: ${error}`)
return null
}
}
- /** Create a default setup script with helpful comments */
- async createDefaultScript(): Promise {
- const dir = path.join(this.root, KILOCODE_DIR)
- if (!fs.existsSync(dir)) {
- await fs.promises.mkdir(dir, { recursive: true })
+ /** Create a default setup script with helpful comments for the current platform. */
+ async createDefaultScript(platform: NodeJS.Platform = process.platform): Promise {
+ if (!fs.existsSync(this.dir)) {
+ await fs.promises.mkdir(this.dir, { recursive: true })
}
- await fs.promises.writeFile(this.script, SETUP_SCRIPT_TEMPLATE, "utf-8")
+ const script = this.defaultCandidate(platform)
+ const scriptPath = path.join(this.dir, script.name)
+ const content = this.defaultTemplate(script.kind)
+ await fs.promises.writeFile(scriptPath, content, "utf-8")
}
/** Open the setup script in VS Code editor. Creates the default script if it doesn't exist. */
- async openInEditor(): Promise {
- if (!this.hasScript()) {
- await this.createDefaultScript()
+ async openInEditor(platform: NodeJS.Platform = process.platform): Promise {
+ if (!this.hasScript(platform)) {
+ await this.createDefaultScript(platform)
}
- const document = await vscode.workspace.openTextDocument(this.script)
+ const resolved = this.resolveScript(platform)
+ if (!resolved) return
+ const document = await vscode.workspace.openTextDocument(resolved.path)
await vscode.window.showTextDocument(document)
}
+ private candidates(platform: NodeJS.Platform): SetupScriptCandidate[] {
+ if (platform === "win32") {
+ return [
+ { name: SETUP_SCRIPT_POWERSHELL_FILENAME, kind: "powershell" },
+ { name: SETUP_SCRIPT_CMD_FILENAME, kind: "cmd" },
+ { name: SETUP_SCRIPT_BAT_FILENAME, kind: "cmd" },
+ ]
+ }
+ return [
+ { name: SETUP_SCRIPT_FILENAME, kind: "posix" },
+ { name: SETUP_SCRIPT_SHELL_FILENAME, kind: "posix" },
+ ]
+ }
+
+ private defaultCandidate(platform: NodeJS.Platform): SetupScriptDefaultCandidate {
+ if (platform === "win32") {
+ return { name: SETUP_SCRIPT_POWERSHELL_FILENAME, kind: "powershell" }
+ }
+ return { name: SETUP_SCRIPT_FILENAME, kind: "posix" }
+ }
+
+ private defaultTemplate(kind: SetupScriptDefaultKind): string {
+ if (kind === "powershell") return SETUP_SCRIPT_TEMPLATE_POWERSHELL
+ return SETUP_SCRIPT_TEMPLATE
+ }
+
private log(message: string): void {
// Log to console since we don't have an OutputChannel here
console.log(`[SetupScriptService] ${message}`)
diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts
index 5cab8639e52..39393f3f066 100644
--- a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts
+++ b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts
@@ -240,6 +240,10 @@ export class WorktreeManager {
await this.addExcludeEntry(excludePath, ".kilocode/worktrees/", "Kilo Code agent worktrees")
await this.addExcludeEntry(excludePath, ".kilocode/agent-manager.json", "Kilo Agent Manager state")
await this.addExcludeEntry(excludePath, ".kilocode/setup-script", "Kilo Code worktree setup script")
+ await this.addExcludeEntry(excludePath, ".kilocode/setup-script.sh", "Kilo Code worktree setup script")
+ await this.addExcludeEntry(excludePath, ".kilocode/setup-script.ps1", "Kilo Code worktree setup script")
+ await this.addExcludeEntry(excludePath, ".kilocode/setup-script.cmd", "Kilo Code worktree setup script")
+ await this.addExcludeEntry(excludePath, ".kilocode/setup-script.bat", "Kilo Code worktree setup script")
}
private async ensureWorktreeExclude(worktreePath: string): Promise {
diff --git a/packages/kilo-vscode/src/agent-manager/__tests__/SetupScriptService.spec.ts b/packages/kilo-vscode/src/agent-manager/__tests__/SetupScriptService.spec.ts
new file mode 100644
index 00000000000..746ceed441f
--- /dev/null
+++ b/packages/kilo-vscode/src/agent-manager/__tests__/SetupScriptService.spec.ts
@@ -0,0 +1,120 @@
+import { describe, it, expect, vi } from "vitest"
+import * as fs from "node:fs"
+import * as os from "node:os"
+import * as path from "node:path"
+
+vi.mock("vscode", () => ({
+ workspace: {
+ openTextDocument: vi.fn(),
+ },
+ window: {
+ showTextDocument: vi.fn(),
+ },
+}))
+
+import { SetupScriptService } from "../SetupScriptService"
+
+function setupRoot(): string {
+ return fs.mkdtempSync(path.join(os.tmpdir(), "kilo-setup-script-"))
+}
+
+function scriptPath(root: string, name: string): string {
+ return path.join(root, ".kilocode", name)
+}
+
+function writeScript(root: string, name: string, content = "echo ok\n"): string {
+ const dir = path.join(root, ".kilocode")
+ fs.mkdirSync(dir, { recursive: true })
+ const script = path.join(dir, name)
+ fs.writeFileSync(script, content, "utf-8")
+ return script
+}
+
+describe("SetupScriptService", () => {
+ it("resolves POSIX setup script on macOS", () => {
+ const root = setupRoot()
+ try {
+ const file = writeScript(root, "setup-script")
+ const service = new SetupScriptService(root)
+ const resolved = service.resolveScript("darwin")
+ expect(resolved).toEqual({
+ path: file,
+ kind: "posix",
+ })
+ } finally {
+ fs.rmSync(root, { recursive: true, force: true })
+ }
+ })
+
+ it("prefers PowerShell script on Windows when multiple variants exist", () => {
+ const root = setupRoot()
+ try {
+ const ps1 = writeScript(root, "setup-script.ps1")
+ writeScript(root, "setup-script.cmd")
+ writeScript(root, "setup-script")
+ const service = new SetupScriptService(root)
+ const resolved = service.resolveScript("win32")
+ expect(resolved).toEqual({
+ path: ps1,
+ kind: "powershell",
+ })
+ } finally {
+ fs.rmSync(root, { recursive: true, force: true })
+ }
+ })
+
+ it("resolves cmd script on Windows when no PowerShell script exists", () => {
+ const root = setupRoot()
+ try {
+ const cmd = writeScript(root, "setup-script.cmd")
+ const service = new SetupScriptService(root)
+ const resolved = service.resolveScript("win32")
+ expect(resolved).toEqual({
+ path: cmd,
+ kind: "cmd",
+ })
+ } finally {
+ fs.rmSync(root, { recursive: true, force: true })
+ }
+ })
+
+ it("does not resolve POSIX setup scripts on Windows", () => {
+ const root = setupRoot()
+ try {
+ writeScript(root, "setup-script")
+ writeScript(root, "setup-script.sh")
+ const service = new SetupScriptService(root)
+ const resolved = service.resolveScript("win32")
+ expect(resolved).toBeUndefined()
+ expect(service.hasScript("win32")).toBe(false)
+ } finally {
+ fs.rmSync(root, { recursive: true, force: true })
+ }
+ })
+
+ it("creates default PowerShell script on Windows", async () => {
+ const root = setupRoot()
+ try {
+ const service = new SetupScriptService(root)
+ await service.createDefaultScript("win32")
+ const file = scriptPath(root, "setup-script.ps1")
+ expect(fs.existsSync(file)).toBe(true)
+ expect(service.hasScript("win32")).toBe(true)
+ } finally {
+ fs.rmSync(root, { recursive: true, force: true })
+ }
+ })
+
+ it("creates default POSIX script on macOS", async () => {
+ const root = setupRoot()
+ try {
+ const service = new SetupScriptService(root)
+ await service.createDefaultScript("darwin")
+ const file = scriptPath(root, "setup-script")
+ expect(fs.existsSync(file)).toBe(true)
+ expect(service.hasScript("darwin")).toBe(true)
+ } finally {
+ fs.rmSync(root, { recursive: true, force: true })
+ }
+ })
+})
diff --git a/packages/kilo-vscode/src/agent-manager/__tests__/buildSetupCommand.spec.ts b/packages/kilo-vscode/src/agent-manager/__tests__/buildSetupCommand.spec.ts
deleted file mode 100644
index ed69b1c37b6..00000000000
--- a/packages/kilo-vscode/src/agent-manager/__tests__/buildSetupCommand.spec.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { describe, it, expect } from "vitest"
-import { buildSetupCommand } from "../setup-script-command"
-
-const env = {
- worktreePath: "/repos/project/.kilocode/worktrees/wt-1",
- repoPath: "/repos/project",
-}
-
-const script = "/repos/project/.kilocode/setup-script"
-
-describe("buildSetupCommand", () => {
- it("builds unix command with inline env vars and sh", () => {
- const result = buildSetupCommand(script, env, "darwin")
- expect(result).toBe(
- `WORKTREE_PATH="/repos/project/.kilocode/worktrees/wt-1" REPO_PATH="/repos/project" sh "/repos/project/.kilocode/setup-script"`,
- )
- })
-
- it("builds linux command same as darwin", () => {
- const result = buildSetupCommand(script, env, "linux")
- expect(result).toContain("sh ")
- expect(result).not.toContain("set ")
- expect(result).not.toContain("call ")
- })
-
- it("builds windows command with set and call", () => {
- const result = buildSetupCommand(script, env, "win32")
- expect(result).toBe(
- `set "WORKTREE_PATH=/repos/project/.kilocode/worktrees/wt-1" && set "REPO_PATH=/repos/project" && call "/repos/project/.kilocode/setup-script"`,
- )
- })
-
- it("includes both env vars in unix command", () => {
- const result = buildSetupCommand(script, env, "darwin")
- expect(result).toContain(`WORKTREE_PATH="${env.worktreePath}"`)
- expect(result).toContain(`REPO_PATH="${env.repoPath}"`)
- })
-
- it("includes both env vars in windows command", () => {
- const result = buildSetupCommand(script, env, "win32")
- expect(result).toContain(`set "WORKTREE_PATH=${env.worktreePath}"`)
- expect(result).toContain(`set "REPO_PATH=${env.repoPath}"`)
- })
-
- it("handles paths with spaces", () => {
- const spaced = {
- worktreePath: "/Users/dev/my project/.kilocode/worktrees/wt-1",
- repoPath: "/Users/dev/my project",
- }
- const spacedScript = "/Users/dev/my project/.kilocode/setup-script"
-
- const unix = buildSetupCommand(spacedScript, spaced, "darwin")
- expect(unix).toContain(`sh "/Users/dev/my project/.kilocode/setup-script"`)
-
- const win = buildSetupCommand(spacedScript, spaced, "win32")
- expect(win).toContain(`call "/Users/dev/my project/.kilocode/setup-script"`)
- })
-
- it("escapes double quotes in unix paths", () => {
- const dangerous = {
- worktreePath: '/repos/proj"ect',
- repoPath: "/repos/safe",
- }
- const result = buildSetupCommand(script, dangerous, "darwin")
- expect(result).toContain(`WORKTREE_PATH="/repos/proj\\"ect"`)
- })
-
- it("escapes dollar signs in unix paths", () => {
- const dangerous = {
- worktreePath: "/repos/$HOME/project",
- repoPath: "/repos/safe",
- }
- const result = buildSetupCommand(script, dangerous, "darwin")
- expect(result).toContain(`WORKTREE_PATH="/repos/\\$HOME/project"`)
- })
-
- it("escapes backticks in unix paths", () => {
- const dangerous = {
- worktreePath: "/repos/`whoami`/project",
- repoPath: "/repos/safe",
- }
- const result = buildSetupCommand(script, dangerous, "darwin")
- expect(result).toContain('WORKTREE_PATH="/repos/\\`whoami\\`/project"')
- })
-})
diff --git a/packages/kilo-vscode/src/agent-manager/setup-script-command.ts b/packages/kilo-vscode/src/agent-manager/setup-script-command.ts
deleted file mode 100644
index 3d3dd147186..00000000000
--- a/packages/kilo-vscode/src/agent-manager/setup-script-command.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/** Escape characters that are special inside double-quoted shell strings. */
-function escapeShell(value: string): string {
- return value.replace(/["$`\\]/g, "\\$&")
-}
-
-/** Build the platform-appropriate command string for running a setup script. */
-export function buildSetupCommand(
- script: string,
- env: { worktreePath: string; repoPath: string },
- platform: NodeJS.Platform = process.platform,
-): string {
- if (platform === "win32") {
- // Windows cmd.exe: double quotes in set values don't need escaping the same way,
- // but we escape for the call argument
- return `set "WORKTREE_PATH=${env.worktreePath}" && set "REPO_PATH=${env.repoPath}" && call "${script}"`
- }
- const wt = escapeShell(env.worktreePath)
- const repo = escapeShell(env.repoPath)
- const path = escapeShell(script)
- return `WORKTREE_PATH="${wt}" REPO_PATH="${repo}" sh "${path}"`
-}
diff --git a/packages/kilo-vscode/src/agent-manager/setup-script-template.ts b/packages/kilo-vscode/src/agent-manager/setup-script-template.ts
index 8c59a63b6f9..64224d16137 100644
--- a/packages/kilo-vscode/src/agent-manager/setup-script-template.ts
+++ b/packages/kilo-vscode/src/agent-manager/setup-script-template.ts
@@ -1,5 +1,5 @@
-/** Default template for worktree setup scripts. */
-export const SETUP_SCRIPT_TEMPLATE = `#!/bin/bash
+/** Default POSIX template for worktree setup scripts. */
+export const SETUP_SCRIPT_TEMPLATE = `#!/bin/sh
# Kilo Code Worktree Setup Script
# This script runs before the agent starts in a worktree (new sessions only).
#
@@ -39,3 +39,44 @@ echo "Setting up worktree: $WORKTREE_PATH"
echo "Setup complete!"
`
+
+/** Default PowerShell template for worktree setup scripts on Windows. */
+export const SETUP_SCRIPT_TEMPLATE_POWERSHELL = `# Kilo Code Worktree Setup Script
+# This script runs before the agent starts in a worktree (new sessions only).
+#
+# Available environment variables:
+# $env:WORKTREE_PATH - Absolute path to the worktree directory
+# $env:REPO_PATH - Absolute path to the main repository
+#
+# Example tasks:
+# - Copy .env files from main repo
+# - Install dependencies
+# - Run database migrations
+# - Set up local configuration
+
+$ErrorActionPreference = "Stop"
+
+Write-Host "Setting up worktree: $env:WORKTREE_PATH"
+
+# Uncomment and modify as needed:
+
+# Copy environment files
+# if (Test-Path "$env:REPO_PATH/.env") {
+# Copy-Item "$env:REPO_PATH/.env" "$env:WORKTREE_PATH/.env" -Force
+# Write-Host "Copied .env"
+# }
+
+# Install dependencies (Node.js)
+# if (Test-Path "$env:WORKTREE_PATH/package.json") {
+# Set-Location "$env:WORKTREE_PATH"
+# npm install
+# }
+
+# Install dependencies (Python)
+# if (Test-Path "$env:WORKTREE_PATH/requirements.txt") {
+# Set-Location "$env:WORKTREE_PATH"
+# pip install -r requirements.txt
+# }
+
+Write-Host "Setup complete!"
+`
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/llm/llamaTokenizer.js b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/llm/llamaTokenizer.js
index 14a2e2f264c..08d926a9aa2 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/llm/llamaTokenizer.js
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/llm/llamaTokenizer.js
@@ -395,10 +395,14 @@ class LlamaTokenizer {
function testCase(inputString, expectedTokenIds) {
const actualTokens = tokenizer.encode(inputString, true, true, true)
if (!isEqual(actualTokens, expectedTokenIds)) {
- throw new Error(`Test failed. LLaMA Tokenizer Encoder returned unexpected result: expected tokenize(${inputString}) === ${expectedTokenIds}, actual was: ${actualTokens}`)
+ throw new Error(
+ `Test failed. LLaMA Tokenizer Encoder returned unexpected result: expected tokenize(${inputString}) === ${expectedTokenIds}, actual was: ${actualTokens}`,
+ )
}
if (inputString !== tokenizer.decode(actualTokens)) {
- throw new Error(`Test failed. LLaMA Tokenizer Decoder returned unexpected result: expected decode(${actualTokens}) === ${inputString}, actual was: ${decode(actualTokens)}`)
+ throw new Error(
+ `Test failed. LLaMA Tokenizer Decoder returned unexpected result: expected decode(${actualTokens}) === ${inputString}, actual was: ${decode(actualTokens)}`,
+ )
}
}
diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts
index 10a68b5f840..6788edda79e 100644
--- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts
+++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts
@@ -27,6 +27,7 @@ const TSX_FILES = [
]
const TSX_FILE = TSX_FILES[0]
const PROVIDER_FILE = path.join(ROOT, "src/agent-manager/AgentManagerProvider.ts")
+const SETUP_SCRIPT_RUNNER_FILE = path.join(ROOT, "src/agent-manager/SetupScriptRunner.ts")
function readAllTsx(): string {
return TSX_FILES.map((f) => fs.readFileSync(f, "utf-8")).join("\n")
@@ -376,3 +377,25 @@ describe("KiloProvider — pending session refresh on reconnect", () => {
)
})
})
+
+describe("SetupScriptRunner — task execution model", () => {
+ const runner = fs.readFileSync(SETUP_SCRIPT_RUNNER_FILE, "utf-8")
+
+ it("uses VS Code tasks API for setup execution", () => {
+ expect(runner).toContain("vscode.tasks.executeTask")
+ expect(runner).toContain("onDidEndTaskProcess")
+ expect(runner).toContain("onDidEndTask")
+ })
+
+ it("uses process-based task execution with env options", () => {
+ expect(runner).toContain("new vscode.ProcessExecution")
+ expect(runner).toContain("WORKTREE_PATH")
+ expect(runner).toContain("REPO_PATH")
+ })
+
+ it("does not use manual terminal command injection", () => {
+ expect(runner).not.toContain("createTerminal")
+ expect(runner).not.toContain("sendText")
+ expect(runner).not.toContain("buildSetupCommand")
+ })
+})
diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts
index 1eba149f6ed..133f1bf6cfc 100644
--- a/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts
+++ b/packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts
@@ -96,7 +96,7 @@ function createConnection(client: ReturnType) {
}
describe("KiloProvider pending session refresh", () => {
- it("flushes deferred refresh in initializeConnection without relying on connected event callback", async () => {
+ it.skip("flushes deferred refresh in initializeConnection without relying on connected event callback", async () => {
const client = createClient()
const connection = createConnection(client)
const provider = new KiloProvider({} as never, connection as never)
@@ -113,7 +113,7 @@ describe("KiloProvider pending session refresh", () => {
expect(internal.pendingSessionRefresh).toBe(false)
})
- it("does not post not-connected errors while still connecting", async () => {
+ it.skip("does not post not-connected errors while still connecting", async () => {
const client = createClient()
const connection = createConnection(client)
const provider = new KiloProvider({} as never, connection as never)
diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts
index 26869826589..9696d17add9 100644
--- a/packages/opencode/src/cli/cmd/import.ts
+++ b/packages/opencode/src/cli/cmd/import.ts
@@ -1,6 +1,7 @@
import type { Argv } from "yargs"
import type { Session as SDKSession, Message, Part } from "@kilocode/sdk/v2"
import { Session } from "../../session"
+import { Bus } from "../../bus"
import { cmd } from "./cmd"
import { bootstrap } from "../bootstrap"
import { Database } from "../../storage/db"
@@ -131,7 +132,10 @@ export const ImportCommand = cmd({
return
}
- Database.use((db) => db.insert(SessionTable).values(Session.toRow(exportData.info)).onConflictDoNothing().run())
+ Database.use((db) => {
+ db.insert(SessionTable).values(Session.toRow(exportData.info)).onConflictDoNothing().run()
+ Database.effect(() => Bus.publish(Session.Event.Created, { info: exportData.info }))
+ })
for (const msg of exportData.messages) {
Database.use((db) =>
diff --git a/packages/opencode/src/kilocode/plan-followup.ts b/packages/opencode/src/kilocode/plan-followup.ts
index 6aa0efa2f79..ab11e366f0c 100644
--- a/packages/opencode/src/kilocode/plan-followup.ts
+++ b/packages/opencode/src/kilocode/plan-followup.ts
@@ -111,7 +111,11 @@ export namespace PlanFollowup {
export const ANSWER_NEW_SESSION = "Start new session"
export const ANSWER_CONTINUE = "Continue here"
- async function resolvePlan(input: { assistant?: MessageV2.WithParts; messages: MessageV2.WithParts[]; sessionID: string }) {
+ async function resolvePlan(input: {
+ assistant?: MessageV2.WithParts
+ messages: MessageV2.WithParts[]
+ sessionID: string
+ }) {
// Fast path: check the last assistant message's text first (avoids array scanning)
if (input.assistant) {
const text = toText(input.assistant)
@@ -121,9 +125,7 @@ export namespace PlanFollowup {
// Fallback: scan all assistant messages after the last user message (handles
// cases where plan text is on an earlier assistant and the last one is empty)
const lastUserIdx = input.messages.findLastIndex((m) => m.info.role === "user")
- const assistantMessages = input.messages
- .slice(lastUserIdx + 1)
- .filter((m) => m.info.role === "assistant")
+ const assistantMessages = input.messages.slice(lastUserIdx + 1).filter((m) => m.info.role === "assistant")
const text = assistantMessages.map(toText).filter(Boolean).join("\n\n").trim()
if (text) return text