mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge branch 'main' into mark/kilo-ui-visual-regression
This commit is contained in:
@@ -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}} متاحة الآن للاستخدام.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}} พร้อมใช้งานแล้ว",
|
||||
|
||||
@@ -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}} 模型了。",
|
||||
|
||||
@@ -32,9 +32,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
|
||||
<div class="size-full rounded overflow-clip">
|
||||
<Avatar
|
||||
fallback={name()}
|
||||
src={
|
||||
props.project.id === KILO_PROJECT_ID ? "https://kilo.ai/favicon.svg" : props.project.icon?.override
|
||||
}
|
||||
src={props.project.id === KILO_PROJECT_ID ? "https://kilo.ai/favicon.svg" : props.project.icon?.override}
|
||||
{...getAvatarColors(props.project.icon?.color)}
|
||||
class="size-full rounded"
|
||||
classList={{ "badge-mask": unseenCount() > 0 && props.notify }}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Head>
|
||||
@@ -39,7 +50,7 @@ export default function Custom404() {
|
||||
{/* Message */}
|
||||
<div className="message-section">
|
||||
<h1 className="message-title">Page not found</h1>
|
||||
<p className="message-subtitle">The page you requested does not exist or has been moved.</p>
|
||||
{subtitle && <p className="message-subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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<boolean> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<vscode.TerminalShellIntegration | undefined> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const state = {
|
||||
done: false,
|
||||
grace: undefined as ReturnType<typeof setTimeout> | undefined,
|
||||
timeout: undefined as ReturnType<typeof setTimeout> | 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<void> {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SetupScriptKind, "cmd">
|
||||
|
||||
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<string | null> {
|
||||
if (!this.hasScript()) return null
|
||||
async getScript(platform: NodeJS.Platform = process.platform): Promise<string | null> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
if (!this.hasScript()) {
|
||||
await this.createDefaultScript()
|
||||
async openInEditor(platform: NodeJS.Platform = process.platform): Promise<void> {
|
||||
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}`)
|
||||
|
||||
@@ -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<void> {
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -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"')
|
||||
})
|
||||
})
|
||||
@@ -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}"`
|
||||
}
|
||||
@@ -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!"
|
||||
`
|
||||
|
||||
+6
-2
@@ -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)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ function createConnection(client: ReturnType<typeof createClient>) {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user