mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
Merge branch 'main' into fix/cli-notification-filter
This commit is contained in:
@@ -162,7 +162,8 @@
|
||||
unzip
|
||||
gnutar
|
||||
gzip
|
||||
ripgrep
|
||||
patchelf
|
||||
ripgrep
|
||||
kilo-dev
|
||||
kilo-install-bin
|
||||
kilo-bin
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-Q2+EejBC2D3Hh/hsSHuQPQYd1Dd+tH1mzIzkPBYOjXs=",
|
||||
"aarch64-linux": "sha256-yX6/eSHRdseDG5GaqicoyiqwjYdB/qFa4vwpiz3ijsM=",
|
||||
"aarch64-darwin": "sha256-0rdhCbZsTSMpous+AuJ6JcMJd+iyki6IdwiH9rNBOZw=",
|
||||
"x86_64-darwin": "sha256-3zr59OGcBXYLtUYOMkKh5UhNIoqjs/aSeaTJGMKJso4="
|
||||
"x86_64-linux": "sha256-nK6w/+ZFZ1e3IF3JQaCdE7mn7FtEuY5fUAJNOiPjTVM=",
|
||||
"aarch64-linux": "sha256-oOiCU3mNgccNrU3VQydTozb5PhRAmUnoyetDkePakq0=",
|
||||
"aarch64-darwin": "sha256-qT4DC6mGD2pkNjSX2RJ6+FCRIlDnXEYiHAxurULkgXI=",
|
||||
"x86_64-darwin": "sha256-4Lew/qSnYVbaJlfP+9fIi2mTuw4mMLYNkFbPNQkJu9k="
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -107,6 +107,6 @@
|
||||
"@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch",
|
||||
"ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch"
|
||||
},
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1075,13 +1075,18 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
|
||||
function projectRoot(directory: string) {
|
||||
const key = workspaceKey(directory)
|
||||
const project = layout.projects
|
||||
.list()
|
||||
.find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
|
||||
.find(
|
||||
(item) =>
|
||||
workspaceKey(item.worktree) === key ||
|
||||
(item.sandboxes ?? []).some((sandbox) => workspaceKey(sandbox) === key),
|
||||
)
|
||||
if (project) return project.worktree
|
||||
|
||||
const known = Object.entries(store.workspaceOrder).find(
|
||||
([root, dirs]) => root === directory || dirs.includes(directory),
|
||||
([root, dirs]) => workspaceKey(root) === key || dirs.some((dir) => workspaceKey(dir) === key),
|
||||
)
|
||||
if (known) return known[0]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "kilo"
|
||||
name = "Kilo"
|
||||
description = "The open source coding agent."
|
||||
version = "7.0.31"
|
||||
version = "7.0.33"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/Kilo-Org/kilocode"
|
||||
@@ -11,26 +11,26 @@ name = "Kilo"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.31/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.33/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.31/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.33/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.31/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.33/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.31/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.33/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.31/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.33/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -3,14 +3,84 @@ import Prism from "prismjs"
|
||||
import * as React from "react"
|
||||
import { Codicon } from "./Codicon"
|
||||
|
||||
let mermaidInitialized = false
|
||||
|
||||
function MermaidBlock({ children }) {
|
||||
const ref = React.useRef(null)
|
||||
const [svg, setSvg] = React.useState("")
|
||||
const [error, setError] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const code = typeof children === "string" ? children : ref.current?.textContent || ""
|
||||
if (!code.trim()) return
|
||||
const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`
|
||||
|
||||
import("mermaid").then((mod) => {
|
||||
const mermaid = mod.default
|
||||
if (!mermaidInitialized) {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: "base",
|
||||
themeVariables: {
|
||||
primaryColor: "#33332d",
|
||||
primaryTextColor: "#e9e9e9",
|
||||
primaryBorderColor: "#555",
|
||||
lineColor: "#a3a3a2",
|
||||
secondaryColor: "#2a2a24",
|
||||
tertiaryColor: "#1a1a18",
|
||||
background: "#1a1a18",
|
||||
mainBkg: "#33332d",
|
||||
nodeBorder: "#555",
|
||||
clusterBkg: "#2a2a24",
|
||||
clusterBorder: "#444",
|
||||
titleColor: "#e9e9e9",
|
||||
edgeLabelBackground: "#1a1a18",
|
||||
},
|
||||
securityLevel: "strict",
|
||||
fontFamily: "inherit",
|
||||
})
|
||||
mermaidInitialized = true
|
||||
}
|
||||
mermaid
|
||||
.render(id, code.trim())
|
||||
.then(({ svg }) => setSvg(svg))
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
setError(true)
|
||||
})
|
||||
})
|
||||
}, [children])
|
||||
|
||||
if (error) {
|
||||
return <pre className="language-mermaid">{children}</pre>
|
||||
}
|
||||
|
||||
if (svg) {
|
||||
return (
|
||||
<div
|
||||
className="mermaid-diagram"
|
||||
dangerouslySetInnerHTML={{ __html: svg }}
|
||||
style={{ display: "flex", justifyContent: "center", padding: "1rem 0" }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<pre ref={ref} style={{ visibility: "hidden", height: 0, overflow: "hidden" }}>
|
||||
{children}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
export function CodeBlock({ children, "data-language": language }) {
|
||||
const ref = React.useRef(null)
|
||||
const timeoutRef = React.useRef(null)
|
||||
const [copied, setCopied] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (language === "mermaid") return
|
||||
if (ref.current) Prism.highlightElement(ref.current, false)
|
||||
}, [children])
|
||||
}, [children, language])
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
@@ -20,6 +90,10 @@ export function CodeBlock({ children, "data-language": language }) {
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (language === "mermaid") {
|
||||
return <MermaidBlock>{children}</MermaidBlock>
|
||||
}
|
||||
|
||||
const handleCopy = async () => {
|
||||
const code = ref.current?.textContent || ""
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 3002",
|
||||
@@ -15,6 +15,7 @@
|
||||
"@markdoc/next.js": "^0.5.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"@vscode/codicons": "^0.0.44",
|
||||
"mermaid": "11.12.3",
|
||||
"next": "^16.1.5",
|
||||
"posthog-js": "^1.335.3",
|
||||
"prismjs": "^1.30.0",
|
||||
|
||||
@@ -38,15 +38,18 @@ Available when the instance is **running**.
|
||||
|
||||
### ↩️ Redeploy
|
||||
|
||||
Stops the machine, applies the latest platform image and your current configuration (environment variables, secrets, channel tokens), and starts it again.
|
||||
Stops the machine, applies your current configuration (environment variables, secrets, channel tokens), and starts it again. When redeploying, you have two options:
|
||||
|
||||
**Your files, git repos, cron jobs, and everything on your persistent volume are preserved.** Redeploy is not a factory reset — think of it as "update and restart."
|
||||
- **Redeploy** — Redeploys using the same platform version your instance was originally set up with. Use this when you only need to apply configuration changes without changing the underlying platform.
|
||||
- **Upgrade & Redeploy** — Upgrades your instance to the latest supported platform version, then redeploys. Use this to pick up new features and fixes from the changelog.
|
||||
|
||||
**Your files, git repos, cron jobs, and everything on your persistent volume are preserved.** Redeploy is not a factory reset — think of it as "apply config and restart" (or "upgrade and restart" if you choose **Upgrade & Redeploy**).
|
||||
|
||||
You should redeploy when:
|
||||
|
||||
- The changelog shows "Redeploy Required" or "Redeploy Suggested"
|
||||
- You've changed channel tokens or secrets in Settings
|
||||
- You want to pick up the latest platform updates
|
||||
- The changelog shows "Redeploy Required" or "Redeploy Suggested" (use **Upgrade & Redeploy**)
|
||||
- You've changed channel tokens or secrets in Settings (use **Redeploy**)
|
||||
- You want to pick up the latest platform updates (use **Upgrade & Redeploy**)
|
||||
|
||||
Available when the instance is **running**.
|
||||
|
||||
@@ -85,12 +88,13 @@ You can connect Telegram, Discord, and Slack by entering bot tokens in the Setti
|
||||
After saving channel tokens, you need to **Redeploy** or **Restart OpenClaw** for the changes to take effect.
|
||||
{% /callout %}
|
||||
|
||||
### Stop and Destroy
|
||||
### Stop, Destroy & Restore
|
||||
|
||||
At the bottom of Settings:
|
||||
|
||||
- **Stop Instance** — Shuts down the machine. All your data is preserved and you can start it again later.
|
||||
- **Destroy Instance** — Permanently deletes your instance and all its data, including files, configuration, and workspace. This cannot be undone.
|
||||
- **Restore Config** — Restores your original `openclaw.json` in your instance. The existing `openclaw.json` is backed up to `/root/.openclaw` before the restore takes place.
|
||||
|
||||
## Accessing the Control UI
|
||||
|
||||
@@ -124,14 +128,14 @@ The dashboard shows recent KiloClaw platform updates. Each entry is tagged as a
|
||||
|
||||
## Instance Lifecycle
|
||||
|
||||
| Action | What Happens | Data Preserved? |
|
||||
| ---------------------- | --------------------------------------------------------------------- | --------------- |
|
||||
| **Create & Provision** | Allocates storage in the best region available and saves your config. | N/A |
|
||||
| **Start Machine** | Boots the machine and starts OpenClaw. | Yes |
|
||||
| **Stop Instance** | Shuts down the machine. | Yes |
|
||||
| **Restart OpenClaw** | Restarts the OpenClaw process. Machine stays up. | Yes |
|
||||
| **Redeploy** | Stops, updates, and restarts the machine. | Yes |
|
||||
| **Destroy Instance** | Permanently deletes everything. | No |
|
||||
| Action | What Happens | Data Preserved? |
|
||||
| ---------------------- | --------------------------------------------------------------------------- | --------------- |
|
||||
| **Create & Provision** | Allocates storage in the best region available and saves your config. | N/A |
|
||||
| **Start Machine** | Boots the machine and starts OpenClaw. | Yes |
|
||||
| **Stop Instance** | Shuts down the machine. | Yes |
|
||||
| **Restart OpenClaw** | Restarts the OpenClaw process. Machine stays up. | Yes |
|
||||
| **Redeploy** | Stops, applies config, and restarts the machine (same version or upgraded). | Yes |
|
||||
| **Destroy Instance** | Permanently deletes everything. | No |
|
||||
|
||||
## Machine Specs
|
||||
|
||||
|
||||
@@ -11,13 +11,6 @@ Kilo Enterprise lets your organization securely manage access using **Single Sig
|
||||
**IDP-initiated logins are not currently supported.** Users must navigate to the [Kilo Web App](https://app.kilo.ai) to log in. Logging in directly from your identity provider's dashboard is not supported at this time.
|
||||
{% /callout %}
|
||||
|
||||
## Why Enable SSO?
|
||||
|
||||
- **Centralized access control:** Manage permissions through your existing identity provider.
|
||||
- **Faster onboarding:** New team members automatically gain access when added to your company directory.
|
||||
- **Improved security:** Enforce company-wide password, MFA, and session policies.
|
||||
- **Easy offboarding:** Smoothly remove access when users leave your organization.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You’ll need:
|
||||
@@ -25,7 +18,7 @@ You’ll need:
|
||||
- Admin or Owner permissions for your Kilo organization.
|
||||
- Access to your **Identity Provider (IdP)** (e.g. Okta, Google Workspace, Azure AD).
|
||||
|
||||
## Step-by-Step Setup
|
||||
## Initiating SSO Configuration
|
||||
|
||||
### 1. Open [Organization](https://app.kilo.ai/organizations) Dashboard
|
||||
|
||||
@@ -36,7 +29,30 @@ Find the Single Sign-On (SSO) Configuration panel, and click "Set up SSO":
|
||||
|
||||
Fill in your contact information and someone from our team will reach out soon to help you configure SSO.
|
||||
|
||||
## ✅ Next Steps
|
||||
## Implementing SSO Configuration
|
||||
|
||||
Once the Kilo team has enabled SSO for your organization, your named admin will get an email from WorkOS to configure SSO.
|
||||
|
||||
{% callout type="warning" %}
|
||||
**Save domain policy for last.**
|
||||
|
||||
If you configure domain policy before setting up SSO, you may lock users out of Kilo.
|
||||
{% /callout %}
|
||||
|
||||
Your admin will need to use the WorkOS link to:
|
||||
|
||||
### 1. Configure your Identity Provider in WorkOS
|
||||
|
||||
Find the Metadata in your Identity Provider and apply that configuration in WorkOS.
|
||||
|
||||
### 2. Configure WorkOS in your Identity Provider
|
||||
|
||||
Copy the Service Provider details (Entity ID, ACS URL, and Metadata) from the WorkOS dashboard and apply them in your Identity Provider.
|
||||
|
||||
### 3. Configure Policy and Domain Settings in WorkOS
|
||||
|
||||
1. Set the organization policy and user provisioning settings according to your organization's needs.
|
||||
2. Configure domain policy and domain verification in WorkOS.
|
||||
|
||||
After enabling SSO:
|
||||
|
||||
|
||||
@@ -1,56 +1,146 @@
|
||||
---
|
||||
title: "Architecture Overview"
|
||||
description: "Overview of Kilo Code architecture"
|
||||
description: "Overview of the Kilo platform architecture"
|
||||
---
|
||||
|
||||
# Architecture Overview
|
||||
|
||||
This document provides a high-level overview of Kilo Code's architecture to help contributors understand how the different components fit together.
|
||||
This document provides a high-level overview of the Kilo platform architecture to help contributors understand how the different components fit together.
|
||||
|
||||
## System Architecture
|
||||
|
||||
Kilo Code is a VS Code extension built with TypeScript that connects to various AI providers to deliver intelligent coding assistance. The architecture follows a layered approach:
|
||||
Kilo is an AI coding platform built around a central CLI engine that powers every client surface — the terminal, VS Code, and the cloud. The architecture follows a layered approach where all clients communicate with the CLI over HTTP + SSE, and the CLI connects to AI providers either directly or through Kilo Cloud.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ VS Code Extension │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ Extension Host │ │ Webview UI │ │
|
||||
│ │ (src/) │◀───▶│ (webview-ui/) │ │
|
||||
│ └────────┬─────────┘ └──────────────────┘ │
|
||||
│ │ │
|
||||
│ │ Messages │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Core Services │ │
|
||||
│ ├────────────┬────────────┬────────────┬───────────────────────┤ │
|
||||
│ │ Tools │ Browser │ MCP │ Code Index │ │
|
||||
│ │ Service │ Session │ Servers │ Service │ │
|
||||
│ └────────────┴────────────┴────────────┴───────────────────────┘ │
|
||||
│ │ │
|
||||
│ │ API Calls │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ API Provider Layer │ │
|
||||
│ ├────────────┬────────────┬────────────┬───────────────────────┤ │
|
||||
│ │ Anthropic │ OpenAI │ Kilo │ OpenRouter │ │
|
||||
│ │ API │ API │ Provider │ API │ │
|
||||
│ └────────────┴────────────┴────────────┴───────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```mermaid
|
||||
graph LR
|
||||
tui["Kilo CLI (TUI)"]
|
||||
vscode["VS Code Extension"]
|
||||
|
||||
subgraph cli ["Kilo CLI Engine"]
|
||||
provider["Provider Router"]
|
||||
end
|
||||
|
||||
subgraph cloud ["Kilo Cloud"]
|
||||
gateway["Kilo Gateway"]
|
||||
cloudagent["Cloud Agent"]
|
||||
bot["Kilo Bot"]
|
||||
claw["Kilo Claw"]
|
||||
review["Code Review"]
|
||||
triage["Auto Triage"]
|
||||
appbuilder["App Builder"]
|
||||
end
|
||||
|
||||
providers["Inference Providers: Anthropic, OpenAI, Google, OpenRouter + 500 more"]
|
||||
|
||||
tui -->|SDK| cli
|
||||
vscode -->|SDK| cli
|
||||
cloudagent -->|Sandbox| cli
|
||||
|
||||
provider -- Direct --> providers
|
||||
provider -- Gateway --> gateway
|
||||
gateway --> providers
|
||||
claw --> gateway
|
||||
|
||||
bot --> cloudagent
|
||||
review --> cloudagent
|
||||
triage --> cloudagent
|
||||
appbuilder --> cloudagent
|
||||
```
|
||||
|
||||
### Features
|
||||
## Kilo CLI — The Foundation
|
||||
|
||||
For detailed documentation on current and planned features, see the [Architecture Features](/docs/contributing/architecture/features) page.
|
||||
The CLI (`packages/opencode/`) is the core engine that all products are built on. It contains the AI agent runtime, tool execution, session management, provider integrations, and an HTTP server. Each client spawns or connects to a `kilo serve` process and communicates via HTTP + SSE using the `@kilocode/sdk`.
|
||||
|
||||
The CLI can run in several modes:
|
||||
|
||||
- **`kilo`** — Interactive TUI for terminal-based coding
|
||||
- **`kilo run`** — Headless single-prompt execution
|
||||
- **`kilo serve`** — HTTP server mode for client integrations
|
||||
- **`kilo web`** — Browser-based UI
|
||||
|
||||
Key subsystems inside the CLI:
|
||||
|
||||
| Subsystem | Purpose |
|
||||
| --------------- | ------------------------------------------------------------------------ |
|
||||
| Agent Runtime | Orchestrates AI conversations, tool calls, and multi-step task execution |
|
||||
| Tools Service | Built-in tools for file editing, shell execution, search, and more |
|
||||
| MCP Servers | Model Context Protocol support for extending with external tools |
|
||||
| LSP Client | Language Server Protocol integration for code intelligence |
|
||||
| Session Manager | Persistent session state, conversation history, and checkpoints |
|
||||
| Provider Router | Connects to 500+ AI models via direct APIs or Kilo Gateway |
|
||||
| HTTP Server | REST API + SSE streaming for client communication |
|
||||
| Config System | Project and global configuration, modes, and permissions |
|
||||
|
||||
## Client Layer
|
||||
|
||||
All clients are thin wrappers over the CLI engine.
|
||||
|
||||
### VS Code Extension
|
||||
|
||||
The VS Code extension (`packages/kilo-vscode/`) bundles the CLI binary and spawns `kilo serve` as a child process. It includes:
|
||||
|
||||
- **Sidebar Chat** — Primary coding assistant interface
|
||||
- **Agent Manager** — Multi-session orchestration panel with git worktree isolation for running parallel tasks
|
||||
|
||||
### TUI
|
||||
|
||||
The built-in terminal UI ships with the CLI itself — a SolidJS interface rendered in the terminal via OpenTUI.
|
||||
|
||||
## Kilo Cloud
|
||||
|
||||
Kilo Cloud is the hosted platform layer that provides authentication, provider routing, and autonomous agent services. The cloud infrastructure lives in a separate repository.
|
||||
|
||||
### Kilo Gateway
|
||||
|
||||
The gateway (`packages/kilo-gateway/` in this repo, plus API routes in the cloud) handles:
|
||||
|
||||
- **Authentication** — Device flow auth, token management, and account linking
|
||||
- **Provider Routing** — Routes AI requests through Kilo's managed API keys or the user's own keys
|
||||
- **Model Catalog** — Serves the available model list and provider configuration
|
||||
- **Usage & Billing** — Tracks token consumption and manages credits
|
||||
|
||||
### Cloud Agent
|
||||
|
||||
A Cloudflare Worker within Kilo Cloud that runs the Kilo CLI in isolated sandbox environments. It powers cloud-based AI coding tasks triggered via the web dashboard, webhooks, or automation workflows. It provides a secure API for:
|
||||
|
||||
- Creating and managing coding sessions with full GitHub/GitLab integration
|
||||
- Running AI tasks in Docker containers with the CLI pre-installed
|
||||
- Streaming results back via WebSocket
|
||||
|
||||
### Kilo Bot
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
### Code Review
|
||||
|
||||
An automated code review service that subscribes to GitHub webhooks, dispatches reviews through the Cloud Agent, and posts feedback directly on pull requests. Supports per-organization concurrency limits and automatic queuing.
|
||||
|
||||
### Auto Triage
|
||||
|
||||
An automated issue triage service that classifies GitHub issues (bug, feature, question), detects duplicates via vector similarity search, and optionally creates fix PRs for high-confidence actionable issues.
|
||||
|
||||
### App Builder
|
||||
|
||||
A service that builds and deploys user applications via the Cloud Agent. Users can generate full applications from prompts, with the App Builder orchestrating the Cloud Agent to scaffold, iterate, and deploy the result.
|
||||
|
||||
### Supporting Services
|
||||
|
||||
| Service | Purpose |
|
||||
| -------------------- | ------------------------------------------------------------------------------------ |
|
||||
| Webhook Agent Ingest | Named webhook endpoints that capture HTTP requests and queue delivery to Cloud Agent |
|
||||
| AI Attribution | Tracks line-level AI-generated code attribution when users accept or reject edits |
|
||||
| Session Ingest | Ingests and stores CLI session data for analytics |
|
||||
| Observability | Telemetry pipelines for monitoring cloud services |
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Modes
|
||||
|
||||
Modes are configurable presets that customize Kilo Code's behavior:
|
||||
Modes are configurable presets that customize the agent's behavior:
|
||||
|
||||
- Define which tools are available
|
||||
- Set custom system prompts
|
||||
@@ -59,7 +149,7 @@ Modes are configurable presets that customize Kilo Code's behavior:
|
||||
|
||||
### Model Context Protocol (MCP)
|
||||
|
||||
MCP enables extending Kilo Code with external tools:
|
||||
MCP enables extending the agent with external tools:
|
||||
|
||||
- Servers provide additional capabilities
|
||||
- Standardized protocol for tool communication
|
||||
@@ -73,75 +163,79 @@ Git-based state management for safe exploration:
|
||||
- Enables rolling back to previous states
|
||||
- Shadow repository for isolation
|
||||
|
||||
### Code Indexing
|
||||
### Worktrees
|
||||
|
||||
Semantic search over the codebase:
|
||||
Git worktree isolation for parallel task execution:
|
||||
|
||||
- Embeddings-based search
|
||||
- Vector database storage (LanceDB/Qdrant)
|
||||
- Automatic chunking and indexing
|
||||
- Each agent session can operate in its own worktree
|
||||
- Prevents conflicts between concurrent tasks
|
||||
- Used by the Agent Manager in VS Code for multi-session workflows
|
||||
|
||||
## Development Patterns
|
||||
|
||||
### Message Passing
|
||||
### Client-Server Communication
|
||||
|
||||
The extension uses VS Code's webview message API:
|
||||
All clients communicate with the CLI via its HTTP + SSE API. The `@kilocode/sdk` package provides a TypeScript client:
|
||||
|
||||
```typescript
|
||||
// Extension → Webview
|
||||
panel.webview.postMessage({ type: "response", data: ... });
|
||||
import { KiloClient } from "@kilocode/sdk"
|
||||
|
||||
// Webview → Extension
|
||||
vscode.postMessage({ type: "request", data: ... });
|
||||
const client = new KiloClient({ baseUrl: "http://localhost:3000" })
|
||||
const session = await client.session.create({ ... })
|
||||
```
|
||||
|
||||
### Service Architecture
|
||||
### Namespace Module Pattern
|
||||
|
||||
Services are typically singletons with clear interfaces:
|
||||
The CLI uses a namespace module pattern for organizing related functionality:
|
||||
|
||||
```typescript
|
||||
class CodeIndexService {
|
||||
private static instance: CodeIndexService
|
||||
export namespace Session {
|
||||
export const create = fn(CreateSchema, async (input) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
static getInstance(): CodeIndexService {
|
||||
if (!this.instance) {
|
||||
this.instance = new CodeIndexService()
|
||||
}
|
||||
return this.instance
|
||||
}
|
||||
export const list = fn(ListSchema, async (input) => {
|
||||
// ...
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Implementation
|
||||
|
||||
Tools follow a consistent pattern:
|
||||
Tools follow a consistent pattern with Zod schema validation:
|
||||
|
||||
```typescript
|
||||
interface Tool {
|
||||
name: string
|
||||
description: string
|
||||
parameters: z.ZodSchema
|
||||
execute(params: unknown): Promise<ToolResult>
|
||||
}
|
||||
export const ReadTool = Tool.define({
|
||||
name: "read",
|
||||
description: "Read a file",
|
||||
parameters: z.object({
|
||||
path: z.string(),
|
||||
}),
|
||||
async execute(params) {
|
||||
// ...
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Build System
|
||||
|
||||
The project uses:
|
||||
|
||||
- **pnpm** - Package management (monorepo workspaces)
|
||||
- **esbuild** - Fast bundling for extension
|
||||
- **Vite** - Webview UI development
|
||||
- **TypeScript** - Type checking across all packages
|
||||
- **Vitest** - Test runner
|
||||
- **Bun** — Package management (monorepo workspaces) and runtime
|
||||
- **Turborepo** — Monorepo task orchestration
|
||||
- **esbuild** — Bundling for the CLI and VS Code extension
|
||||
- **TypeScript** — Type checking via `tsgo` across all packages
|
||||
- **Vitest / Bun test** — Test runner
|
||||
|
||||
## Testing
|
||||
## Repositories
|
||||
|
||||
- **Unit tests** - `*.spec.ts` files alongside source
|
||||
- **Integration tests** - E2E tests in `e2e/` directory
|
||||
- **Run tests**: `cd src && pnpm test` or `cd webview-ui && pnpm test`
|
||||
| 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 |
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Development Environment](/docs/contributing/development-environment) - Setup guide
|
||||
- [Tools Reference](/docs/automate/tools) - Available tools
|
||||
- [Development Environment](/docs/contributing/development-environment) — Setup guide
|
||||
- [Architecture Features](/docs/contributing/architecture/features) — Detailed feature specs
|
||||
- [Ecosystem](/docs/contributing/ecosystem) — Related projects and integrations
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Kilo-specific i18n translations and overrides",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "kilo-code",
|
||||
"displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete",
|
||||
"description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"icon": "assets/icons/logo-outline-black.png",
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
@@ -689,7 +689,8 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"ts-morph": "27.0.2",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.54.0"
|
||||
"typescript-eslint": "^8.54.0",
|
||||
"@vscode/vsce": "^3.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
|
||||
@@ -23,7 +23,7 @@ try {
|
||||
await $`bun run check-types`.cwd(root)
|
||||
await $`bun run lint`.cwd(root)
|
||||
await $`node ${join(root, "esbuild.js")} --production`.cwd(root)
|
||||
await $`bunx @vscode/vsce package --no-dependencies --skip-license -o ${outDir}/`.cwd(root)
|
||||
await $`bunx vsce package --no-dependencies --skip-license -o ${outDir}/`.cwd(root)
|
||||
} finally {
|
||||
await Bun.write(pkgPath, JSON.stringify(pkg, null, 2) + "\n")
|
||||
}
|
||||
|
||||
@@ -1447,6 +1447,13 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
}
|
||||
|
||||
private startDiffPolling(sessionId: string): void {
|
||||
// If already polling the same session, keep the existing interval and cache
|
||||
// to avoid an unnecessary stop→restart cycle that clears lastDiffHash and
|
||||
// cachedDiffTarget, creating a flash of empty diff data in the webview.
|
||||
if (this.diffSessionId === sessionId && this.diffInterval) {
|
||||
this.log(`Already polling session ${sessionId}, skipping restart`)
|
||||
return
|
||||
}
|
||||
this.stopDiffPolling()
|
||||
this.diffSessionId = sessionId
|
||||
this.lastDiffHash = undefined
|
||||
|
||||
@@ -20,6 +20,7 @@ const TSX_FILES = [
|
||||
path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/DiffPanel.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/FullScreenDiffView.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/DiffEndMarker.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/FileTree.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/review-annotations.ts"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/MultiModelSelector.tsx"),
|
||||
|
||||
@@ -1086,6 +1086,13 @@ const AgentManagerContent: Component = () => {
|
||||
return []
|
||||
})
|
||||
|
||||
const diffSessionKey = createMemo(() => {
|
||||
const sel = selection()
|
||||
if (sel === LOCAL) return `local:${LOCAL}`
|
||||
if (sel === null) return `session:${session.currentSessionID() ?? ""}`
|
||||
return `worktree:${sel}`
|
||||
})
|
||||
|
||||
const setSharedDiffStyle = (style: "unified" | "split") => {
|
||||
if (reviewDiffStyle() === style) return
|
||||
setReviewDiffStyle(style)
|
||||
@@ -2124,7 +2131,7 @@ const AgentManagerContent: Component = () => {
|
||||
<DiffPanel
|
||||
diffs={diffDatas()[selection() === LOCAL ? LOCAL : (session.currentSessionID() ?? "")] ?? []}
|
||||
loading={diffLoading()}
|
||||
sessionKey={selection() === LOCAL ? LOCAL : (session.currentSessionID() ?? "")}
|
||||
sessionKey={diffSessionKey()}
|
||||
diffStyle={reviewDiffStyle()}
|
||||
onDiffStyleChange={setSharedDiffStyle}
|
||||
comments={reviewComments()}
|
||||
@@ -2146,12 +2153,16 @@ const AgentManagerContent: Component = () => {
|
||||
<FullScreenDiffView
|
||||
diffs={reviewDiffs()}
|
||||
loading={diffLoading()}
|
||||
sessionKey={selection() === LOCAL ? LOCAL : (session.currentSessionID() ?? "")}
|
||||
sessionKey={diffSessionKey()}
|
||||
comments={reviewComments()}
|
||||
onCommentsChange={setReviewCommentsForSelection}
|
||||
onSendAll={closeReviewTab}
|
||||
diffStyle={reviewDiffStyle()}
|
||||
onDiffStyleChange={setSharedDiffStyle}
|
||||
onOpenFile={(file) => {
|
||||
const id = session.currentSessionID()
|
||||
if (id) vscode.postMessage({ type: "agentManager.openFile", sessionId: id, filePath: file })
|
||||
}}
|
||||
onClose={closeReviewTab}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Component } from "solid-js"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
|
||||
export const DiffEndMarker: Component = () => {
|
||||
const { t } = useLanguage()
|
||||
|
||||
return (
|
||||
<div class="am-diff-end-marker">
|
||||
<div class="am-diff-end-mascot-wrap">
|
||||
<svg
|
||||
class="am-diff-end-mascot"
|
||||
width="96"
|
||||
height="96"
|
||||
viewBox="0 0 96 96"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<ellipse cx="49" cy="76" rx="20" ry="6" fill="#1B1035" opacity="0.45" />
|
||||
|
||||
<rect x="41" y="56" width="6" height="16" fill="#EAB308" />
|
||||
<rect x="51" y="56" width="6" height="16" fill="#EAB308" />
|
||||
|
||||
<g class="am-kiloman-upper">
|
||||
<rect x="41" y="36" width="16" height="22" fill="#EAB308" />
|
||||
<circle cx="49" cy="24" r="14" fill="#FEF08A" />
|
||||
<rect x="53" y="21" width="4" height="4" fill="#111827" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="am-diff-end-text">{t("agentManager.review.endOfLongDiff")}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
type ReviewComment,
|
||||
} from "./review-comments"
|
||||
import { buildReviewAnnotation, type AnnotationLabels, type AnnotationMeta } from "./review-annotations"
|
||||
import { LONG_DIFF_MARKER_FILE_COUNT, initialOpenFiles, isLargeDiffFile } from "./diff-open-policy"
|
||||
import { DiffEndMarker } from "./DiffEndMarker"
|
||||
|
||||
// --- Data model ---
|
||||
|
||||
@@ -55,10 +57,13 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
delete: t("common.delete"),
|
||||
})
|
||||
const [open, setOpen] = createSignal<string[]>([])
|
||||
const [openInit, setOpenInit] = createSignal(false)
|
||||
const [draft, setDraft] = createSignal<{ file: string; side: AnnotationSide; line: number } | null>(null)
|
||||
const [editing, setEditing] = createSignal<string | null>(null)
|
||||
let nextId = 0
|
||||
// Tracks the session key for which auto-open has already run. When the
|
||||
// key changes (different worktree) we re-expand. Within the same key,
|
||||
// only pruning happens so the user's manual collapse state is preserved.
|
||||
let initializedKey: string | undefined
|
||||
|
||||
const comments = () => props.comments
|
||||
const setComments = (next: ReviewComment[]) => props.onCommentsChange(next)
|
||||
@@ -112,35 +117,36 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
focusRoot()
|
||||
}
|
||||
|
||||
// Reset auto-open state when switching sessions so diffs expand for the new session
|
||||
// Unified auto-open effect: tracks both sessionKey and diffs in a single effect
|
||||
// to eliminate the race condition between the old separate sessionKey-reset and
|
||||
// diffs-watch effects. Uses the session key to decide when auto-expand is needed
|
||||
// vs when we just prune stale entries from the open list.
|
||||
createEffect(
|
||||
on(
|
||||
() => props.sessionKey,
|
||||
() => {
|
||||
setOpenInit(false)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
() => [props.sessionKey, props.diffs] as const,
|
||||
([key, diffs]) => {
|
||||
// No diffs yet (async fetch in progress) — don't mark as initialized
|
||||
// so auto-open runs when data arrives.
|
||||
// Important: do not prune on empty, otherwise transient empty updates
|
||||
// collapse all files and they stay collapsed for the same key.
|
||||
if (diffs.length === 0) return
|
||||
|
||||
// Auto-open files when diffs arrive
|
||||
createEffect(
|
||||
on(
|
||||
() => props.diffs,
|
||||
(diffs) => {
|
||||
const files = diffs.map((d) => d.file)
|
||||
const fileSet = new Set(files)
|
||||
// Only update open state when the file list actually changed to avoid
|
||||
// unnecessary re-renders that reset scroll position during polling
|
||||
const fileSet = new Set(diffs.map((diff) => diff.file))
|
||||
|
||||
// New context: initialize open state from the diff policy.
|
||||
if (key !== initializedKey) {
|
||||
initializedKey = key
|
||||
setOpen(initialOpenFiles(diffs))
|
||||
return
|
||||
}
|
||||
|
||||
// Already initialized for this key — preserve manual expand/collapse,
|
||||
// only prune files that no longer exist (e.g. deleted during session)
|
||||
setOpen((prev) => {
|
||||
const filtered = prev.filter((file) => fileSet.has(file))
|
||||
if (filtered.length === prev.length && prev.every((f) => fileSet.has(f))) return prev
|
||||
return filtered
|
||||
})
|
||||
if (openInit()) return
|
||||
if (diffs.length === 0) return
|
||||
if (diffs.length <= 15) setOpen(files)
|
||||
setOpenInit(true)
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -293,6 +299,8 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
files: props.diffs.length,
|
||||
additions: props.diffs.reduce((sum, diff) => sum + diff.additions, 0),
|
||||
deletions: props.diffs.reduce((sum, diff) => sum + diff.deletions, 0),
|
||||
large: props.diffs.filter((diff) => isLargeDiffFile(diff)).length,
|
||||
collapsed: Math.max(props.diffs.length - open().length, 0),
|
||||
}))
|
||||
|
||||
return (
|
||||
@@ -319,6 +327,16 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
<span>{t("session.review.filesChanged", { count: totals().files })}</span>
|
||||
<span class="am-diff-header-adds">+{totals().additions}</span>
|
||||
<span class="am-diff-header-dels">-{totals().deletions}</span>
|
||||
<Show when={totals().collapsed > 0}>
|
||||
<span class="am-diff-header-collapsed">
|
||||
{totals().large > 0
|
||||
? t("agentManager.review.collapsedWithLarge", {
|
||||
collapsed: totals().collapsed,
|
||||
large: totals().large,
|
||||
})
|
||||
: t("agentManager.review.collapsedOnly", { count: totals().collapsed })}
|
||||
</span>
|
||||
</Show>
|
||||
</span>
|
||||
</>
|
||||
</Show>
|
||||
@@ -359,6 +377,7 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
{(diff) => {
|
||||
const isAdded = () => diff.status === "added"
|
||||
const isDeleted = () => diff.status === "deleted"
|
||||
const isLargeCollapsed = () => isLargeDiffFile(diff) && !open().includes(diff.file)
|
||||
const fileCommentCount = () => (commentsByFile().get(diff.file) ?? []).length
|
||||
|
||||
return (
|
||||
@@ -389,8 +408,9 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
{t("ui.sessionReview.change.removed")}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!isAdded() && !isDeleted()}>
|
||||
<DiffChanges changes={diff} />
|
||||
<DiffChanges changes={diff} />
|
||||
<Show when={isLargeCollapsed()}>
|
||||
<span class="am-diff-large-pill">{t("agentManager.review.largeFileCollapsed")}</span>
|
||||
</Show>
|
||||
<Show when={props.onOpenFile && !isDeleted()}>
|
||||
<Tooltip value={t("agentManager.diff.openFile")} placement="top">
|
||||
@@ -431,6 +451,9 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
}}
|
||||
</For>
|
||||
</Accordion>
|
||||
<Show when={props.diffs.length > LONG_DIFF_MARKER_FILE_COUNT}>
|
||||
<DiffEndMarker />
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={comments().length > 0}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import type { WorktreeFileDiff } from "../src/types/messages"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
import { buildFileTree, flatten, type FileTreeNode } from "./file-tree-utils"
|
||||
import type { ReviewComment } from "./review-comments"
|
||||
|
||||
export type { FileTreeNode } from "./file-tree-utils"
|
||||
export { buildFileTree, flatten, flattenChain } from "./file-tree-utils"
|
||||
@@ -12,6 +13,7 @@ interface FileTreeProps {
|
||||
diffs: WorktreeFileDiff[]
|
||||
activeFile: string | null
|
||||
onFileSelect: (path: string) => void
|
||||
comments?: ReviewComment[]
|
||||
}
|
||||
|
||||
const DirectoryNode: Component<{
|
||||
@@ -19,6 +21,7 @@ const DirectoryNode: Component<{
|
||||
activeFile: string | null
|
||||
onFileSelect: (path: string) => void
|
||||
depth: number
|
||||
commentsByFile?: Map<string, number>
|
||||
}> = (props) => {
|
||||
const [expanded, setExpanded] = createSignal(true)
|
||||
const hasActiveDescendant = createMemo(() => {
|
||||
@@ -48,6 +51,7 @@ const DirectoryNode: Component<{
|
||||
activeFile={props.activeFile}
|
||||
onFileSelect={props.onFileSelect}
|
||||
depth={props.depth + 1}
|
||||
commentsByFile={props.commentsByFile}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@@ -56,6 +60,7 @@ const DirectoryNode: Component<{
|
||||
activeFile={props.activeFile}
|
||||
onFileSelect={props.onFileSelect}
|
||||
depth={props.depth + 1}
|
||||
commentsByFile={props.commentsByFile}
|
||||
/>
|
||||
</Show>
|
||||
)}
|
||||
@@ -70,9 +75,15 @@ const FileNode: Component<{
|
||||
activeFile: string | null
|
||||
onFileSelect: (path: string) => void
|
||||
depth: number
|
||||
commentsByFile?: Map<string, number>
|
||||
}> = (props) => {
|
||||
const active = () => props.activeFile === props.node.path
|
||||
const status = () => props.node.diff?.status ?? "modified"
|
||||
const additions = () => props.node.diff?.additions ?? 0
|
||||
const deletions = () => props.node.diff?.deletions ?? 0
|
||||
const showAdd = () => additions() > 0 || status() === "added"
|
||||
const showDel = () => deletions() > 0 || status() === "deleted"
|
||||
const comments = () => props.commentsByFile?.get(props.node.path) ?? 0
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -87,6 +98,9 @@ const FileNode: Component<{
|
||||
>
|
||||
<FileIcon node={{ path: props.node.path, type: "file" }} />
|
||||
<span class="am-file-tree-name">{props.node.name}</span>
|
||||
<Show when={comments() > 0}>
|
||||
<span class="am-file-tree-comment-badge">{comments()}</span>
|
||||
</Show>
|
||||
<Show when={props.node.diff}>
|
||||
{(diff) => (
|
||||
<span class="am-file-tree-changes">
|
||||
@@ -96,9 +110,11 @@ const FileNode: Component<{
|
||||
<Show when={diff().status === "deleted"}>
|
||||
<span class="am-file-tree-badge-deleted">D</span>
|
||||
</Show>
|
||||
<Show when={diff().status !== "added" && diff().status !== "deleted"}>
|
||||
<span class="am-file-tree-stat-add">+{diff().additions}</span>
|
||||
<span class="am-file-tree-stat-del">-{diff().deletions}</span>
|
||||
<Show when={showAdd()}>
|
||||
<span class="am-file-tree-stat-add">+{additions()}</span>
|
||||
</Show>
|
||||
<Show when={showDel()}>
|
||||
<span class="am-file-tree-stat-del">-{deletions()}</span>
|
||||
</Show>
|
||||
</span>
|
||||
)}
|
||||
@@ -110,6 +126,13 @@ const FileNode: Component<{
|
||||
export const FileTree: Component<FileTreeProps> = (props) => {
|
||||
const { t } = useLanguage()
|
||||
const tree = createMemo(() => flatten(buildFileTree(props.diffs)))
|
||||
const commentsByFile = createMemo(() => {
|
||||
const map = new Map<string, number>()
|
||||
for (const comment of props.comments ?? []) {
|
||||
map.set(comment.file, (map.get(comment.file) ?? 0) + 1)
|
||||
}
|
||||
return map
|
||||
})
|
||||
const totals = createMemo(() => {
|
||||
const adds = props.diffs.reduce((s, d) => s + d.additions, 0)
|
||||
const dels = props.diffs.reduce((s, d) => s + d.deletions, 0)
|
||||
@@ -124,10 +147,22 @@ export const FileTree: Component<FileTreeProps> = (props) => {
|
||||
<Show
|
||||
when={node.children}
|
||||
fallback={
|
||||
<FileNode node={node} activeFile={props.activeFile} onFileSelect={props.onFileSelect} depth={0} />
|
||||
<FileNode
|
||||
node={node}
|
||||
activeFile={props.activeFile}
|
||||
onFileSelect={props.onFileSelect}
|
||||
depth={0}
|
||||
commentsByFile={commentsByFile()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DirectoryNode node={node} activeFile={props.activeFile} onFileSelect={props.onFileSelect} depth={0} />
|
||||
<DirectoryNode
|
||||
node={node}
|
||||
activeFile={props.activeFile}
|
||||
onFileSelect={props.onFileSelect}
|
||||
depth={0}
|
||||
commentsByFile={commentsByFile()}
|
||||
/>
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { IconButton } from "@kilocode/kilo-ui/icon-button"
|
||||
import { Spinner } from "@kilocode/kilo-ui/spinner"
|
||||
import { ResizeHandle } from "@kilocode/kilo-ui/resize-handle"
|
||||
import { TooltipKeybind } from "@kilocode/kilo-ui/tooltip"
|
||||
import { Tooltip, TooltipKeybind } from "@kilocode/kilo-ui/tooltip"
|
||||
import type { DiffLineAnnotation, AnnotationSide } from "@pierre/diffs"
|
||||
import type { WorktreeFileDiff } from "../src/types/messages"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
type ReviewComment,
|
||||
} from "./review-comments"
|
||||
import { buildReviewAnnotation, type AnnotationLabels, type AnnotationMeta } from "./review-annotations"
|
||||
import { LONG_DIFF_MARKER_FILE_COUNT, initialOpenFiles, isLargeDiffFile } from "./diff-open-policy"
|
||||
import { DiffEndMarker } from "./DiffEndMarker"
|
||||
|
||||
type DiffStyle = "unified" | "split"
|
||||
|
||||
@@ -35,6 +37,7 @@ interface FullScreenDiffViewProps {
|
||||
onSendAll?: () => void
|
||||
diffStyle: DiffStyle
|
||||
onDiffStyleChange: (style: DiffStyle) => void
|
||||
onOpenFile?: (relativePath: string) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
@@ -55,13 +58,16 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
delete: t("common.delete"),
|
||||
})
|
||||
const [open, setOpen] = createSignal<string[]>([])
|
||||
const [openInit, setOpenInit] = createSignal(false)
|
||||
const [draft, setDraft] = createSignal<{ file: string; side: AnnotationSide; line: number } | null>(null)
|
||||
const [editing, setEditing] = createSignal<string | null>(null)
|
||||
const [activeFile, setActiveFile] = createSignal<string | null>(null)
|
||||
const [treeWidth, setTreeWidth] = createSignal(240)
|
||||
let nextId = 0
|
||||
let draftMeta: AnnotationMeta | null = null
|
||||
// Tracks the session key for which auto-open has already run. When the
|
||||
// key changes (different worktree) we re-expand. Within the same key,
|
||||
// only pruning happens so the user's manual collapse state is preserved.
|
||||
let initializedKey: string | undefined
|
||||
let rootRef: HTMLDivElement | undefined
|
||||
let scrollRef: HTMLDivElement | undefined
|
||||
let syncFrame: number | undefined
|
||||
@@ -105,43 +111,43 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
focusRoot()
|
||||
}
|
||||
|
||||
// Reset auto-open state when switching sessions so diffs expand for the new session
|
||||
// Unified auto-open effect: tracks both sessionKey and diffs in a single effect
|
||||
// to eliminate the race condition between the old separate sessionKey-reset and
|
||||
// diffs-watch effects. Uses the session key to decide when auto-expand is needed
|
||||
// vs when we just prune stale entries from the open list.
|
||||
createEffect(
|
||||
on(
|
||||
() => props.sessionKey,
|
||||
() => {
|
||||
setOpenInit(false)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
() => [props.sessionKey, props.diffs] as const,
|
||||
([key, diffs]) => {
|
||||
if (diffs.length === 0) {
|
||||
// No diffs yet — clear active file only for a new key; keep current
|
||||
// selection for transient empty updates in the same key.
|
||||
if (key !== initializedKey) setActiveFile(null)
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-open files when diffs arrive
|
||||
createEffect(
|
||||
on(
|
||||
() => props.diffs,
|
||||
(diffs) => {
|
||||
const files = diffs.map((d) => d.file)
|
||||
const fileSet = new Set(files)
|
||||
// Only update open state when the file list actually changed to avoid
|
||||
// unnecessary re-renders that reset scroll position during polling
|
||||
const fileSet = new Set(diffs.map((diff) => diff.file))
|
||||
|
||||
// Keep active file in sync — pick first if current is stale
|
||||
const current = activeFile()
|
||||
if (!current || !diffs.some((d) => d.file === current)) {
|
||||
setActiveFile(diffs[0]!.file)
|
||||
}
|
||||
|
||||
// New context: initialize open state from the diff policy.
|
||||
if (key !== initializedKey) {
|
||||
initializedKey = key
|
||||
setOpen(initialOpenFiles(diffs))
|
||||
return
|
||||
}
|
||||
|
||||
// Already initialized for this key — preserve manual expand/collapse,
|
||||
// only prune files that no longer exist (e.g. deleted during session)
|
||||
setOpen((prev) => {
|
||||
const filtered = prev.filter((file) => fileSet.has(file))
|
||||
if (filtered.length === prev.length && prev.every((f) => fileSet.has(f))) return prev
|
||||
return filtered
|
||||
})
|
||||
if (diffs.length === 0) {
|
||||
setActiveFile(null)
|
||||
return
|
||||
}
|
||||
if (!openInit()) {
|
||||
if (diffs.length <= 15) setOpen(files)
|
||||
setOpenInit(true)
|
||||
}
|
||||
const current = activeFile()
|
||||
if (!current || !diffs.some((d) => d.file === current)) {
|
||||
setActiveFile(diffs[0]!.file)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -363,6 +369,8 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
files: props.diffs.length,
|
||||
additions: props.diffs.reduce((s, d) => s + d.additions, 0),
|
||||
deletions: props.diffs.reduce((s, d) => s + d.deletions, 0),
|
||||
large: props.diffs.filter((diff) => isLargeDiffFile(diff)).length,
|
||||
collapsed: Math.max(props.diffs.length - open().length, 0),
|
||||
}))
|
||||
|
||||
return (
|
||||
@@ -392,6 +400,16 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
<span>{t("session.review.filesChanged", { count: totals().files })}</span>
|
||||
<span class="am-review-toolbar-adds">+{totals().additions}</span>
|
||||
<span class="am-review-toolbar-dels">-{totals().deletions}</span>
|
||||
<Show when={totals().collapsed > 0}>
|
||||
<span class="am-review-toolbar-collapsed">
|
||||
{totals().large > 0
|
||||
? t("agentManager.review.collapsedWithLarge", {
|
||||
collapsed: totals().collapsed,
|
||||
large: totals().large,
|
||||
})
|
||||
: t("agentManager.review.collapsedOnly", { count: totals().collapsed })}
|
||||
</span>
|
||||
</Show>
|
||||
</span>
|
||||
</div>
|
||||
<div class="am-review-toolbar-right">
|
||||
@@ -418,7 +436,12 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
<div class="am-review-body">
|
||||
<div class="am-review-tree-resize" style={{ width: `${treeWidth()}px` }}>
|
||||
<div class="am-review-tree-wrapper">
|
||||
<FileTree diffs={props.diffs} activeFile={activeFile()} onFileSelect={handleFileSelect} />
|
||||
<FileTree
|
||||
diffs={props.diffs}
|
||||
activeFile={activeFile()}
|
||||
onFileSelect={handleFileSelect}
|
||||
comments={comments()}
|
||||
/>
|
||||
</div>
|
||||
<ResizeHandle
|
||||
direction="horizontal"
|
||||
@@ -450,6 +473,7 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
{(diff) => {
|
||||
const isAdded = () => diff.status === "added"
|
||||
const isDeleted = () => diff.status === "deleted"
|
||||
const isLargeCollapsed = () => isLargeDiffFile(diff) && !open().includes(diff.file)
|
||||
const fileCommentCount = () => (commentsByFile().get(diff.file) ?? []).length
|
||||
|
||||
return (
|
||||
@@ -480,8 +504,23 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
{t("ui.sessionReview.change.removed")}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!isAdded() && !isDeleted()}>
|
||||
<DiffChanges changes={diff} />
|
||||
<DiffChanges changes={diff} />
|
||||
<Show when={isLargeCollapsed()}>
|
||||
<span class="am-diff-large-pill">{t("agentManager.review.largeFileCollapsed")}</span>
|
||||
</Show>
|
||||
<Show when={props.onOpenFile && !isDeleted()}>
|
||||
<Tooltip value={t("agentManager.diff.openFile")} placement="top">
|
||||
<IconButton
|
||||
icon="go-to-file"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
label={t("agentManager.diff.openFile")}
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
props.onOpenFile?.(diff.file)
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<span data-slot="session-review-diff-chevron">
|
||||
<Icon name="chevron-down" size="small" />
|
||||
@@ -508,6 +547,9 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
}}
|
||||
</For>
|
||||
</Accordion>
|
||||
<Show when={props.diffs.length > LONG_DIFF_MARKER_FILE_COUNT}>
|
||||
<DiffEndMarker />
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -909,6 +909,12 @@ button.am-section-toggle:hover .am-section-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.am-diff-header-collapsed,
|
||||
.am-review-toolbar-collapsed {
|
||||
color: var(--text-weaker);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.am-diff-header [data-component="radio-group"],
|
||||
.am-diff-header [data-component="radio-group"] [data-slot="radio-group-item-label"],
|
||||
.am-diff-header [data-component="radio-group"] [data-slot="radio-group-item-control"] {
|
||||
@@ -934,6 +940,56 @@ button.am-section-toggle:hover .am-section-label {
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.am-diff-end-marker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 28px 16px 34px;
|
||||
color: var(--text-weaker);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.am-diff-end-mascot-wrap {
|
||||
filter: drop-shadow(0 6px 12px color-mix(in oklab, var(--surface-base) 45%, black));
|
||||
}
|
||||
|
||||
.am-diff-end-mascot {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.am-kiloman-upper {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center bottom;
|
||||
animation: am-kiloman-idle 520ms linear infinite;
|
||||
}
|
||||
|
||||
.am-diff-end-text {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
@keyframes am-kiloman-idle {
|
||||
0% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
|
||||
.am-diff-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -997,6 +1053,41 @@ button.am-section-toggle:hover .am-section-label {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.am-diff-content [data-slot="session-review-file-info"],
|
||||
.am-review-diff-content [data-slot="session-review-file-info"] {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.am-diff-content [data-slot="session-review-file-name-container"],
|
||||
.am-review-diff-content [data-slot="session-review-file-name-container"] {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.am-diff-content [data-slot="session-review-directory"],
|
||||
.am-review-diff-content [data-slot="session-review-directory"] {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.am-diff-content [data-slot="session-review-filename"],
|
||||
.am-review-diff-content [data-slot="session-review-filename"] {
|
||||
min-width: 0;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.am-diff-content [data-slot="session-review-trigger-actions"],
|
||||
.am-review-diff-content [data-slot="session-review-trigger-actions"] {
|
||||
flex-shrink: 0;
|
||||
min-width: max-content;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Keep +N/-N indicators at consistent size across side/fullscreen views */
|
||||
.am-diff-panel [data-component="diff-changes"] [data-slot="diff-changes-additions"],
|
||||
.am-diff-panel [data-component="diff-changes"] [data-slot="diff-changes-deletions"],
|
||||
@@ -1146,6 +1237,21 @@ button.am-section-toggle:hover .am-section-label {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.am-diff-large-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: color-mix(in oklab, var(--text-warning, #a15c00) 85%, var(--text-base));
|
||||
background: color-mix(in oklab, var(--surface-warning, rgba(245, 158, 11, 0.16)) 65%, transparent);
|
||||
border: 1px solid color-mix(in oklab, var(--border-warning, rgba(245, 158, 11, 0.45)) 75%, transparent);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Comments footer bar */
|
||||
|
||||
.am-diff-comments-footer {
|
||||
@@ -2324,6 +2430,10 @@ button.am-section-toggle:hover .am-section-label {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.am-diff-header-actions > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Review tab in tab bar */
|
||||
|
||||
.am-tab-review {
|
||||
@@ -2345,6 +2455,7 @@ button.am-section-toggle:hover .am-section-label {
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
--am-diff-count-size: 12px;
|
||||
/* Inherit pierre diffs CSS vars from am-diff-panel */
|
||||
--diffs-light-bg: var(--vscode-editor-background, #1e1e1e);
|
||||
@@ -2386,9 +2497,10 @@ button.am-section-toggle:hover .am-section-label {
|
||||
.am-review-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
padding: 4px 8px;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
border-bottom: 1px solid var(--border-weak-base);
|
||||
background: var(--surface-base);
|
||||
gap: 8px;
|
||||
@@ -2398,12 +2510,21 @@ button.am-section-toggle:hover .am-section-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.am-review-toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.am-review-toolbar-right > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.am-review-toolbar [data-component="radio-group"],
|
||||
@@ -2422,9 +2543,14 @@ button.am-section-toggle:hover .am-section-label {
|
||||
.am-review-toolbar-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
gap: 8px;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-weak);
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.am-review-toolbar-adds {
|
||||
@@ -2443,6 +2569,7 @@ button.am-section-toggle:hover .am-section-label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -2450,6 +2577,8 @@ button.am-section-toggle:hover .am-section-label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.am-review-tree-resize {
|
||||
@@ -2473,6 +2602,7 @@ button.am-section-toggle:hover .am-section-label {
|
||||
|
||||
.am-review-diff {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@@ -2483,6 +2613,7 @@ button.am-section-toggle:hover .am-section-label {
|
||||
contain: none;
|
||||
scrollbar-width: auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.am-review-diff-content[data-component="session-review"] [data-component="sticky-accordion-header"] {
|
||||
@@ -2645,6 +2776,23 @@ button.am-section-toggle:hover .am-section-label {
|
||||
color: var(--syntax-diff-add, #318430);
|
||||
}
|
||||
|
||||
.am-file-tree-comment-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 8px;
|
||||
background: var(--surface-interactive-strong);
|
||||
color: var(--text-on-fill);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
margin-left: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.am-file-tree-badge-deleted {
|
||||
font-size: var(--am-diff-count-size);
|
||||
font-weight: 600;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { WorktreeFileDiff } from "../src/types/messages"
|
||||
|
||||
export const LONG_DIFF_MARKER_FILE_COUNT = 50
|
||||
export const LARGE_FILE_CHANGED_LINES = 400
|
||||
|
||||
export function isLargeDiffFile(diff: WorktreeFileDiff): boolean {
|
||||
return diff.additions + diff.deletions > LARGE_FILE_CHANGED_LINES
|
||||
}
|
||||
|
||||
export function initialOpenFiles(diffs: WorktreeFileDiff[]): string[] {
|
||||
if (diffs.length === 0) return []
|
||||
|
||||
const files = diffs.filter((diff) => !isLargeDiffFile(diff)).map((diff) => diff.file)
|
||||
return files
|
||||
}
|
||||
@@ -1025,6 +1025,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "اترك تعليقًا...",
|
||||
"agentManager.review.commentAction": "تعليق",
|
||||
"agentManager.review.sendToChat": "إرسال إلى الدردشة",
|
||||
"agentManager.review.collapsedOnly": "{{count}} مطوي",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} مطوي، {{large}} كبير",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "الصق رابط PR...",
|
||||
|
||||
@@ -1039,6 +1039,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Deixe um comentário...",
|
||||
"agentManager.review.commentAction": "Comentar",
|
||||
"agentManager.review.sendToChat": "Enviar para o chat",
|
||||
"agentManager.review.collapsedOnly": "{{count}} recolhidos",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} recolhidos, {{large}} grandes",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Cole a URL do PR...",
|
||||
|
||||
@@ -1061,6 +1061,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Ostavi komentar...",
|
||||
"agentManager.review.commentAction": "Komentariši",
|
||||
"agentManager.review.sendToChat": "Pošalji u chat",
|
||||
"agentManager.review.collapsedOnly": "{{count}} sažeto",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} sažeto, {{large}} velikih",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Zalijepite PR URL...",
|
||||
|
||||
@@ -1034,6 +1034,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Skriv en kommentar...",
|
||||
"agentManager.review.commentAction": "Kommenter",
|
||||
"agentManager.review.sendToChat": "Send til chat",
|
||||
"agentManager.review.collapsedOnly": "{{count}} foldet sammen",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} foldet sammen, {{large}} store",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Indsæt PR URL...",
|
||||
|
||||
@@ -1048,6 +1048,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Kommentar hinterlassen...",
|
||||
"agentManager.review.commentAction": "Kommentieren",
|
||||
"agentManager.review.sendToChat": "An Chat senden",
|
||||
"agentManager.review.collapsedOnly": "{{count}} eingeklappt",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} eingeklappt, {{large}} groß",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "PR-URL einfügen...",
|
||||
|
||||
@@ -1090,6 +1090,10 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Leave a comment...",
|
||||
"agentManager.review.commentAction": "Comment",
|
||||
"agentManager.review.sendToChat": "Send to chat",
|
||||
"agentManager.review.collapsedOnly": "{{count}} collapsed",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} collapsed, {{large}} large",
|
||||
"agentManager.review.largeFileCollapsed": "Large file (collapsed)",
|
||||
"agentManager.review.endOfLongDiff": "You made it to the end!",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Paste PR URL...",
|
||||
|
||||
@@ -1042,6 +1042,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Deja un comentario...",
|
||||
"agentManager.review.commentAction": "Comentar",
|
||||
"agentManager.review.sendToChat": "Enviar al chat",
|
||||
"agentManager.review.collapsedOnly": "{{count}} contraídos",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} contraídos, {{large}} grandes",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Pegar URL del PR...",
|
||||
|
||||
@@ -1050,6 +1050,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Laisser un commentaire...",
|
||||
"agentManager.review.commentAction": "Commenter",
|
||||
"agentManager.review.sendToChat": "Envoyer au chat",
|
||||
"agentManager.review.collapsedOnly": "{{count}} repliés",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} repliés, {{large}} volumineux",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Coller l'URL du PR...",
|
||||
|
||||
@@ -1030,6 +1030,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "コメントを入力...",
|
||||
"agentManager.review.commentAction": "コメント",
|
||||
"agentManager.review.sendToChat": "チャットに送信",
|
||||
"agentManager.review.collapsedOnly": "{{count}} 件折りたたみ",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} 件折りたたみ、{{large}} 件がサイズ大",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "PR URLを貼り付け...",
|
||||
|
||||
@@ -1030,6 +1030,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "댓글을 남기세요...",
|
||||
"agentManager.review.commentAction": "댓글",
|
||||
"agentManager.review.sendToChat": "채팅으로 보내기",
|
||||
"agentManager.review.collapsedOnly": "{{count}}개 접힘",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}}개 접힘, {{large}}개 대용량",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "PR URL 붙여넣기...",
|
||||
|
||||
@@ -1035,6 +1035,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Legg igjen en kommentar...",
|
||||
"agentManager.review.commentAction": "Kommenter",
|
||||
"agentManager.review.sendToChat": "Send til chat",
|
||||
"agentManager.review.collapsedOnly": "{{count}} kollapset",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} kollapset, {{large}} store",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Lim inn PR URL...",
|
||||
|
||||
@@ -1037,6 +1037,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Zostaw komentarz...",
|
||||
"agentManager.review.commentAction": "Komentuj",
|
||||
"agentManager.review.sendToChat": "Wyślij do czatu",
|
||||
"agentManager.review.collapsedOnly": "{{count}} zwiniętych",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} zwiniętych, {{large}} dużych",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Wklej URL PR...",
|
||||
|
||||
@@ -1038,6 +1038,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "Оставьте комментарий...",
|
||||
"agentManager.review.commentAction": "Комментировать",
|
||||
"agentManager.review.sendToChat": "Отправить в чат",
|
||||
"agentManager.review.collapsedOnly": "{{count}} свернуто",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} свернуто, {{large}} больших",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Вставьте URL PR...",
|
||||
|
||||
@@ -1023,6 +1023,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "แสดงความคิดเห็น...",
|
||||
"agentManager.review.commentAction": "แสดงความคิดเห็น",
|
||||
"agentManager.review.sendToChat": "ส่งไปยังแชท",
|
||||
"agentManager.review.collapsedOnly": "ยุบ {{count}} รายการ",
|
||||
"agentManager.review.collapsedWithLarge": "ยุบ {{collapsed}} รายการ, ขนาดใหญ่ {{large}} รายการ",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "วาง URL ของ PR...",
|
||||
|
||||
@@ -1025,6 +1025,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "留下评论...",
|
||||
"agentManager.review.commentAction": "评论",
|
||||
"agentManager.review.sendToChat": "发送到聊天",
|
||||
"agentManager.review.collapsedOnly": "{{count}} 个已折叠",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} 个已折叠,{{large}} 个过大",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "粘贴 PR URL...",
|
||||
|
||||
@@ -1021,6 +1021,8 @@ export const dict = {
|
||||
"agentManager.review.commentPlaceholder": "留下留言...",
|
||||
"agentManager.review.commentAction": "留言",
|
||||
"agentManager.review.sendToChat": "傳送到聊天",
|
||||
"agentManager.review.collapsedOnly": "{{count}} 個已摺疊",
|
||||
"agentManager.review.collapsedWithLarge": "{{collapsed}} 個已摺疊,{{large}} 個過大",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "貼上 PR URL...",
|
||||
|
||||
@@ -25,11 +25,20 @@ if (envPath) {
|
||||
const scriptPath = fs.realpathSync(__filename)
|
||||
const scriptDir = path.dirname(scriptPath)
|
||||
|
||||
//
|
||||
// kilocode_change start - fall through to findBinary() if cached binary fails
|
||||
const cached = path.join(scriptDir, ".kilo")
|
||||
if (fs.existsSync(cached)) {
|
||||
run(cached)
|
||||
const result = childProcess.spawnSync(cached, process.argv.slice(2), {
|
||||
stdio: "inherit",
|
||||
})
|
||||
if (!result.error) {
|
||||
const code = typeof result.status === "number" ? result.status : 0
|
||||
process.exit(code)
|
||||
}
|
||||
// cached binary failed (e.g. wrong platform/arch, missing dynamic linker),
|
||||
// fall through to findBinary() which has better variant detection
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
const platformMap = {
|
||||
darwin: "darwin",
|
||||
@@ -166,9 +175,6 @@ function findBinary(startDir) {
|
||||
}
|
||||
}
|
||||
|
||||
const scriptPath = fs.realpathSync(__filename)
|
||||
const scriptDir = path.dirname(scriptPath)
|
||||
|
||||
const resolved = findBinary(scriptDir)
|
||||
if (!resolved) {
|
||||
console.error(
|
||||
@@ -179,4 +185,4 @@ if (!resolved) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
run(resolved)
|
||||
run(resolved)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"name": "@kilocode/cli",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -188,6 +188,27 @@ for (const item of targets) {
|
||||
},
|
||||
})
|
||||
|
||||
// kilocode_change start - fix Nix-specific ELF interpreter paths for Linux binaries
|
||||
if (item.os === "linux") {
|
||||
const interpreters: Record<string, string> = {
|
||||
x64: "/lib64/ld-linux-x86-64.so.2",
|
||||
arm64: "/lib/ld-linux-aarch64.so.1",
|
||||
"x64-musl": "/lib/ld-musl-x86_64.so.1",
|
||||
"arm64-musl": "/lib/ld-musl-aarch64.so.1",
|
||||
}
|
||||
const key = item.abi === "musl" ? `${item.arch}-musl` : item.arch
|
||||
const interpreter = interpreters[key]
|
||||
if (interpreter) {
|
||||
try {
|
||||
await $`patchelf --set-interpreter ${interpreter} dist/${name}/bin/kilo`
|
||||
console.log(`patched interpreter for ${name} -> ${interpreter}`)
|
||||
} catch {
|
||||
console.warn(`patchelf not available, skipping interpreter fix for ${name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
await $`rm -rf ./dist/${name}/bin/tui`
|
||||
await Bun.file(`dist/${name}/package.json`).write(
|
||||
JSON.stringify(
|
||||
|
||||
@@ -3,129 +3,150 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import childProcess from "child_process"
|
||||
import { fileURLToPath } from "url"
|
||||
import { createRequire } from "module"
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
// kilocode_change start - variant detection matching bin/kilo logic
|
||||
const platformMap = {
|
||||
darwin: "darwin",
|
||||
linux: "linux",
|
||||
win32: "windows",
|
||||
}
|
||||
const archMap = {
|
||||
x64: "x64",
|
||||
arm64: "arm64",
|
||||
arm: "arm",
|
||||
}
|
||||
|
||||
function detectPlatformAndArch() {
|
||||
// Map platform names
|
||||
let platform
|
||||
switch (os.platform()) {
|
||||
case "darwin":
|
||||
platform = "darwin"
|
||||
break
|
||||
case "linux":
|
||||
platform = "linux"
|
||||
break
|
||||
case "win32":
|
||||
platform = "windows"
|
||||
break
|
||||
default:
|
||||
platform = os.platform()
|
||||
break
|
||||
}
|
||||
|
||||
// Map architecture names
|
||||
let arch
|
||||
switch (os.arch()) {
|
||||
case "x64":
|
||||
arch = "x64"
|
||||
break
|
||||
case "arm64":
|
||||
arch = "arm64"
|
||||
break
|
||||
case "arm":
|
||||
arch = "arm"
|
||||
break
|
||||
default:
|
||||
arch = os.arch()
|
||||
break
|
||||
}
|
||||
|
||||
const platform = platformMap[os.platform()] || os.platform()
|
||||
const arch = archMap[os.arch()] || os.arch()
|
||||
return { platform, arch }
|
||||
}
|
||||
|
||||
function findBinary() {
|
||||
function supportsAvx2() {
|
||||
const { platform, arch } = detectPlatformAndArch()
|
||||
const packageName = `@kilocode/cli-${platform}-${arch}`
|
||||
const binaryName = platform === "windows" ? "kilo.exe" : "kilo"
|
||||
if (arch !== "x64") return false
|
||||
|
||||
try {
|
||||
// Use require.resolve to find the package
|
||||
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
||||
const packageDir = path.dirname(packageJsonPath)
|
||||
const binaryPath = path.join(packageDir, "bin", binaryName)
|
||||
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
throw new Error(`Binary not found at ${binaryPath}`)
|
||||
}
|
||||
|
||||
return { binaryPath, binaryName }
|
||||
} catch (error) {
|
||||
throw new Error(`Could not find package ${packageName}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
function prepareBinDirectory(binaryName) {
|
||||
const binDir = path.join(__dirname, "bin")
|
||||
const targetPath = path.join(binDir, binaryName)
|
||||
|
||||
// Ensure bin directory exists
|
||||
if (!fs.existsSync(binDir)) {
|
||||
fs.mkdirSync(binDir, { recursive: true })
|
||||
}
|
||||
|
||||
// Remove existing binary/symlink if it exists
|
||||
if (fs.existsSync(targetPath)) {
|
||||
fs.unlinkSync(targetPath)
|
||||
}
|
||||
|
||||
return { binDir, targetPath }
|
||||
}
|
||||
|
||||
function symlinkBinary(sourcePath, binaryName) {
|
||||
const { targetPath } = prepareBinDirectory(binaryName)
|
||||
|
||||
fs.symlinkSync(sourcePath, targetPath)
|
||||
console.log(`kilo binary symlinked: ${targetPath} -> ${sourcePath}`)
|
||||
|
||||
// Verify the file exists after operation
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
throw new Error(`Failed to symlink binary to ${targetPath}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
if (os.platform() === "win32") {
|
||||
// On Windows, the .exe is already included in the package and bin field points to it
|
||||
// No postinstall setup needed
|
||||
console.log("Windows detected: binary setup not needed (using packaged .exe)")
|
||||
return
|
||||
}
|
||||
|
||||
// On non-Windows platforms, just verify the binary package exists
|
||||
// Don't replace the wrapper script - it handles binary execution
|
||||
const { binaryPath } = findBinary()
|
||||
const target = path.join(__dirname, "bin", ".kilo") // kilocode_change
|
||||
if (fs.existsSync(target)) fs.unlinkSync(target)
|
||||
if (platform === "linux") {
|
||||
try {
|
||||
fs.linkSync(binaryPath, target)
|
||||
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
||||
} catch {
|
||||
fs.copyFileSync(binaryPath, target)
|
||||
return false
|
||||
}
|
||||
fs.chmodSync(target, 0o755)
|
||||
} catch (error) {
|
||||
console.error("Failed to setup kilo binary:", error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (platform === "darwin") {
|
||||
try {
|
||||
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
||||
encoding: "utf8",
|
||||
timeout: 1500,
|
||||
})
|
||||
if (result.status !== 0) return false
|
||||
return (result.stdout || "").trim() === "1"
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function isMusl() {
|
||||
try {
|
||||
if (fs.existsSync("/etc/alpine-release")) return true
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
||||
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
||||
if (text.includes("musl")) return true
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function getPackageNames() {
|
||||
const { platform, arch } = detectPlatformAndArch()
|
||||
const base = `@kilocode/cli-${platform}-${arch}`
|
||||
const avx2 = supportsAvx2()
|
||||
const baseline = arch === "x64" && !avx2
|
||||
|
||||
if (platform === "linux") {
|
||||
const musl = isMusl()
|
||||
if (musl) {
|
||||
if (arch === "x64") {
|
||||
if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
||||
return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
||||
}
|
||||
return [`${base}-musl`, base]
|
||||
}
|
||||
if (arch === "x64") {
|
||||
if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
||||
return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
||||
}
|
||||
return [base, `${base}-musl`]
|
||||
}
|
||||
|
||||
if (arch === "x64") {
|
||||
if (baseline) return [`${base}-baseline`, base]
|
||||
return [base, `${base}-baseline`]
|
||||
}
|
||||
return [base]
|
||||
}
|
||||
|
||||
function findBinary() {
|
||||
const { platform } = detectPlatformAndArch()
|
||||
const binaryName = platform === "windows" ? "kilo.exe" : "kilo"
|
||||
const names = getPackageNames()
|
||||
|
||||
for (const packageName of names) {
|
||||
try {
|
||||
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
||||
const packageDir = path.dirname(packageJsonPath)
|
||||
const binaryPath = path.join(packageDir, "bin", binaryName)
|
||||
|
||||
if (fs.existsSync(binaryPath)) {
|
||||
return { binaryPath, binaryName }
|
||||
}
|
||||
} catch {
|
||||
// package not installed, try next variant
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Could not find any binary package. Tried: ${names.map((n) => `"${n}"`).join(", ")}`)
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
function main() {
|
||||
if (os.platform() === "win32") {
|
||||
// On Windows, the .exe is already included in the package and bin field points to it
|
||||
console.log("Windows detected: binary setup not needed (using packaged .exe)")
|
||||
return
|
||||
}
|
||||
|
||||
const { binaryPath } = findBinary()
|
||||
const target = path.join(__dirname, "bin", ".kilo") // kilocode_change
|
||||
if (fs.existsSync(target)) fs.unlinkSync(target)
|
||||
try {
|
||||
fs.linkSync(binaryPath, target)
|
||||
} catch {
|
||||
fs.copyFileSync(binaryPath, target)
|
||||
}
|
||||
fs.chmodSync(target, 0o755)
|
||||
}
|
||||
|
||||
try {
|
||||
main()
|
||||
} catch (error) {
|
||||
console.error("Postinstall script error:", error.message)
|
||||
process.exit(0)
|
||||
console.error("Failed to setup kilo binary:", error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"dependencies": {},
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
+5
-5
@@ -89,11 +89,11 @@ async function main() {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Fetching latest dev branch...")
|
||||
await $`git fetch origin dev`
|
||||
console.log("Fetching latest main branch...")
|
||||
await $`git fetch origin main`
|
||||
|
||||
console.log("Checking out beta branch...")
|
||||
await $`git checkout -B beta origin/dev`
|
||||
console.log("Checking out main branch...")
|
||||
await $`git checkout -B beta origin/main`
|
||||
|
||||
const applied: number[] = []
|
||||
const failed: FailedPR[] = []
|
||||
@@ -177,7 +177,7 @@ async function main() {
|
||||
await $`git fetch origin beta`
|
||||
|
||||
const localTree = await $`git rev-parse beta^{tree}`.text()
|
||||
const remoteTrees = (await $`git log origin/dev..origin/beta --format=%T`.text()).split("\n")
|
||||
const remoteTrees = (await $`git log origin/main..origin/beta --format=%T`.text()).split("\n")
|
||||
|
||||
const matchIdx = remoteTrees.indexOf(localTree.trim())
|
||||
if (matchIdx !== -1) {
|
||||
|
||||
+6
-1
@@ -62,7 +62,7 @@ if (Script.release) {
|
||||
await $`git commit -am "release: v${Script.version}"`
|
||||
await $`git tag v${Script.version}`
|
||||
await $`git fetch origin`
|
||||
await $`git cherry-pick HEAD..origin/dev`.nothrow()
|
||||
await $`git cherry-pick HEAD..origin/main`.nothrow() // kilocode_change
|
||||
await $`git push origin HEAD --tags --no-verify --force-with-lease`
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000))
|
||||
}
|
||||
@@ -79,5 +79,10 @@ await import(`../packages/sdk/js/script/publish.ts`)
|
||||
console.log("\n=== plugin ===\n")
|
||||
await import(`../packages/plugin/script/publish.ts`)
|
||||
|
||||
// kilocode_change start
|
||||
console.log("\n=== vscode ===\n")
|
||||
await import(`../packages/kilo-vscode/script/publish.ts`)
|
||||
// kilocode_change end
|
||||
|
||||
const dir = fileURLToPath(new URL("..", import.meta.url))
|
||||
process.chdir(dir)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/upstream-merge",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Scripts for automating upstream opencode merges into Kilo",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "7.0.31",
|
||||
"version": "7.0.33",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user