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