mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-31 01:37:28 +08:00
merge main into improve-review-comments-ui
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep answered questions expanded in chat history and improve their text wrapping.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Clarify when to use foreground subagents and wait for required background results before the final answer.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Add a button to copy pull request links from the Agent Manager PR panel.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep the working indicator and the session actions row flush with the prompt input, so the row above the composer no longer jumps when a turn finishes.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Avoid creating project-local dependency trees when configuration directories contain no file plugins.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Add an Agent Manager document inspector that previews Markdown files with inline review comments that can be sent to the agent, while source files open in the VS Code editor.
|
||||
@@ -0,0 +1,3 @@
|
||||
{% callout type="warning" title="KiloClaw is end of life" %}
|
||||
KiloClaw is no longer available to new users, and support for the product is ending. This documentation is kept for reference by existing users and is no longer maintained.
|
||||
{% /callout %}
|
||||
@@ -16,7 +16,7 @@ If you already pay for ChatGPT Plus or Pro, you can use that subscription to run
|
||||
- **Multiple AI modes:** Switch between Code, Plan, Debug, and Ask modes for different tasks.
|
||||
|
||||
{% callout type="note" %}
|
||||
Your ChatGPT subscription works with Kilo Code's core functionality (VS Code extension and CLI), but does **not** include cloud features such as Cloud Agents, Kilo Deploy, or KiloClaw. To use GPT models in those features, use the [Kilo Gateway](/docs/gateway).
|
||||
Your ChatGPT subscription works with Kilo Code's core functionality (VS Code extension and CLI), but does **not** include cloud features such as Cloud Agents or Kilo Deploy. To use GPT models in those features, use the [Kilo Gateway](/docs/gateway).
|
||||
{% /callout %}
|
||||
|
||||
## Setup
|
||||
@@ -62,7 +62,7 @@ Then set your default model to one of the OpenAI Codex models available in Kilo
|
||||
|
||||
- **Codex catalog models only.** This provider only exposes the models listed in Kilo Code's Codex model catalog. It does not give access to every model available through the OpenAI API.
|
||||
- **OAuth tokens can't be exported with settings.** Tokens are stored in VS Code SecretStorage, which isn't included in Kilo Code's settings export.
|
||||
- **Cloud features not included.** Cloud Agents, Kilo Deploy, and KiloClaw require the [Kilo Gateway](/docs/gateway).
|
||||
- **Cloud features not included.** Cloud Agents and Kilo Deploy require the [Kilo Gateway](/docs/gateway).
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -78,5 +78,5 @@ Usage counts against your ChatGPT subscription limits — there are no separate
|
||||
**Can I still switch to other AI providers?**
|
||||
Yes. Use OpenAI when it fits and switch to Claude, Gemini, or any local model at any time.
|
||||
|
||||
**Can I use my ChatGPT subscription in KiloClaw?**
|
||||
No. For KiloClaw and other cloud features, use GPT models through the [Kilo Gateway](/docs/gateway), which gives access to 500+ models including the latest GPT releases.
|
||||
**Can I use my ChatGPT subscription in Kilo's cloud features?**
|
||||
No. For Cloud Agents, Kilo Deploy, and other cloud features, use GPT models through the [Kilo Gateway](/docs/gateway), which gives access to 500+ models including the latest GPT releases.
|
||||
|
||||
@@ -28,7 +28,7 @@ If you have an active [SuperGrok or X Premium subscription](https://x.ai/grok),
|
||||
- **Automatic token refresh:** Kilo Code refreshes your access token in the background so long-running sessions stay authenticated.
|
||||
|
||||
{% callout type="note" %}
|
||||
SuperGrok and X Premium subscription access works with Kilo Code's core functionality (VS Code extension and CLI). For cloud features such as Cloud Agents or KiloClaw, use the [Kilo Gateway](/docs/gateway) — the Gateway supports xAI via [BYOK](/docs/getting-started/byok) with an API key (OAuth and subscription-based access are not supported through the Gateway).
|
||||
SuperGrok and X Premium subscription access works with Kilo Code's core functionality (VS Code extension and CLI). For cloud features such as Cloud Agents, use the [Kilo Gateway](/docs/gateway) — the Gateway supports xAI via [BYOK](/docs/getting-started/byok) with an API key (OAuth and subscription-based access are not supported through the Gateway).
|
||||
{% /callout %}
|
||||
|
||||
### Setup with SuperGrok / X Premium
|
||||
|
||||
@@ -159,11 +159,8 @@ Triggers allow you to initiate cloud agent sessions automatically, either via HT
|
||||
Triggers are currently in beta and subject to change.
|
||||
{% /callout %}
|
||||
|
||||
Webhook triggers and scheduled triggers use the same trigger concepts across Cloud
|
||||
Agent and KiloClaw, but target different agents. Use Cloud Agent triggers when an
|
||||
HTTP event or schedule should start a Cloud Agent session against a repository.
|
||||
Use KiloClaw triggers when the event should deliver a chat message to a KiloClaw
|
||||
instance.
|
||||
Use Cloud Agent triggers when an HTTP event or schedule should start a Cloud Agent
|
||||
session against a repository.
|
||||
|
||||
### Accessing Triggers
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ description: "Using Kilo Code on iOS and Android"
|
||||
|
||||
# Mobile Apps
|
||||
|
||||
Use Kilo Code from your phone to keep coding sessions moving while you are away from your desk. The mobile app connects to Cloud Agents, KiloClaw, and remote sessions from your local CLI or editor extensions.
|
||||
Use Kilo Code from your phone to keep coding sessions moving while you are away from your desk. The mobile app connects to Cloud Agents and remote sessions from your local CLI or editor extensions.
|
||||
|
||||
{% callout type="info" title="Android app available now" %}
|
||||
Install Kilo Code for Android from [Google Play](https://play.google.com/store/apps/details?id=com.kilocode.kiloapp).
|
||||
@@ -16,10 +16,8 @@ Install Kilo Code for Android from [Google Play](https://play.google.com/store/a
|
||||
The mobile app lets you:
|
||||
|
||||
- View and manage Kilo Code sessions, including remote CLI and extension sessions running on your local machine.
|
||||
- Chat with KiloClaw from your phone.
|
||||
- Spawn Cloud Agents and code directly from the app.
|
||||
- Monitor and view all non-remote sessions in one place.
|
||||
- Create, onboard, and manage KiloClaw instances.
|
||||
- Send follow-up messages while a session is still running — they are queued and processed in order.
|
||||
- Run slash commands (like `/compact`) on connected remote CLI sessions, and start a new session in the same workspace with `/new`. The new session inherits the current session's mode and model. Older CLI versions that do not support remote commands prompt you to upgrade.
|
||||
- Clear the visible transcript of a remote CLI session with `/clear`. Clearing is client-side only, so it works on any CLI version; server history is kept and may reappear when you re-enter the session.
|
||||
@@ -38,16 +36,14 @@ You can review or change your decision at any time in **Settings**. Declining op
|
||||
For Kilo Pass pricing, billing, and account management details, use the [Kilo Pass pricing page](https://kilo.ai/pricing/kilo-pass).
|
||||
|
||||
{% imageGallery columns="3" width="220px" %}
|
||||
{% image src="/docs/img/mobile-apps/home.webp" alt="Kilo Code mobile home screen showing KiloClaw and active agent sessions" caption="Start coding tasks, open KiloClaw, and resume active sessions from the mobile home screen." /%}
|
||||
{% image src="/docs/img/mobile-apps/home.webp" alt="Kilo Code mobile home screen showing active agent sessions" caption="Start coding tasks and resume active sessions from the mobile home screen." /%}
|
||||
|
||||
{% image src="/docs/img/mobile-apps/new-session.webp" alt="Kilo Code mobile new session screen with coding mode selector" caption="Create a new Cloud Agent session and choose the right mode for the task." /%}
|
||||
|
||||
{% image src="/docs/img/mobile-apps/session-chat.webp" alt="Kilo Code mobile session chat with an active coding task" caption="Review progress and continue coding conversations from the mobile app." /%}
|
||||
{% /imageGallery %}
|
||||
|
||||
{% imageGallery columns="2" width="220px" %}
|
||||
{% image src="/docs/img/mobile-apps/kiloclaw-chat.webp" alt="KiloClaw chat in the Kilo Code mobile app" caption="Chat with KiloClaw on mobile." /%}
|
||||
|
||||
{% imageGallery columns="1" width="220px" %}
|
||||
{% image src="/docs/img/mobile-apps/session-filters.webp" alt="Kilo Code mobile session filter panel for Cloud Extension CLI Slack and other platforms" caption="Filter sessions by platform and project, including Cloud, Extension, CLI, Slack, and other sessions." /%}
|
||||
{% /imageGallery %}
|
||||
|
||||
|
||||
@@ -223,4 +223,3 @@ After system-boundary pages, continue with Development Patterns for implementati
|
||||
- [Development Patterns](/docs/contributing/architecture/development-patterns) - code-ownership decisions and contributor workflow
|
||||
- [Development Environment](/docs/contributing/development-environment) - setup guide
|
||||
- [Ecosystem](/docs/contributing/ecosystem) - related projects and integrations
|
||||
- [KiloClaw Overview](/docs/kiloclaw/overview) - customer-facing KiloClaw docs
|
||||
|
||||
@@ -66,25 +66,6 @@ const terminalContent = {
|
||||
<span className="terminal-prompt">$</span> kilo rules list
|
||||
</>
|
||||
),
|
||||
kiloclaw: (
|
||||
<>
|
||||
<span className="terminal-comment"># Chat with your KiloClaw agent from the CLI</span>
|
||||
{"\n"}
|
||||
<span className="terminal-prompt">$</span> kilo /claw
|
||||
{"\n"}
|
||||
{"\n"}
|
||||
<span className="terminal-comment"># Or connect via chat platforms</span>
|
||||
{"\n"}
|
||||
<span className="terminal-comment"># Telegram, Discord, Slack — no self-hosting required</span>
|
||||
{"\n"}
|
||||
{"\n"}
|
||||
<span className="terminal-comment"># Trigger your agent via webhook</span>
|
||||
{"\n"}
|
||||
<span className="terminal-prompt">$</span> curl -X POST https://your-instance.kiloclaw.ai/webhook \{"\n"}
|
||||
-H "Content-Type: application/json" \{"\n"}
|
||||
{` -d '{"event":"deploy","repo":"my-app"}'`}
|
||||
</>
|
||||
),
|
||||
}
|
||||
|
||||
// Category card data based on the information architecture
|
||||
@@ -120,24 +101,6 @@ const categories = [
|
||||
{ title: "Custom Rules", href: "/code-with-ai" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "KiloClaw",
|
||||
description:
|
||||
"Hosted OpenClaw agents — deploy, manage, and integrate AI agents with chat platforms, dev tools, and triggers without self-hosting.",
|
||||
href: "/kiloclaw/overview",
|
||||
icon: (
|
||||
<svg className="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M2 17l10 5 10-5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M2 12l10 5 10-5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
),
|
||||
links: [
|
||||
{ title: "Overview", href: "/kiloclaw/overview" },
|
||||
{ title: "Chat Platforms", href: "/kiloclaw/chat-platforms" },
|
||||
{ title: "Development Tools", href: "/kiloclaw/development-tools" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Collaborate",
|
||||
description: "Work with teams, share sessions, and manage organizations",
|
||||
@@ -230,9 +193,7 @@ const categories = [
|
||||
]
|
||||
|
||||
export default function HomePage() {
|
||||
const [activeTab, setActiveTab] = useState<"installation" | "firstTask" | "customRules" | "gateway" | "kiloclaw">(
|
||||
"installation",
|
||||
)
|
||||
const [activeTab, setActiveTab] = useState<"installation" | "firstTask" | "customRules" | "gateway">("installation")
|
||||
|
||||
return (
|
||||
<div className="homepage">
|
||||
@@ -247,15 +208,15 @@ export default function HomePage() {
|
||||
<div className="hero-content">
|
||||
<h1 className="hero-title">Kilo Documentation</h1>
|
||||
<p className="hero-subtitle">
|
||||
Explore guides and examples for the Kilo platform — from coding agents and AI-powered development to hosted
|
||||
agentic infrastructure with KiloClaw.
|
||||
Explore guides and examples for the Kilo platform — from coding agents and AI-powered development to
|
||||
automation and hosted agentic infrastructure.
|
||||
</p>
|
||||
<div className="hero-buttons">
|
||||
<Link href="/getting-started" className="btn btn-primary">
|
||||
Get started with Kilo Code →
|
||||
</Link>
|
||||
<Link href="/kiloclaw/overview" className="btn btn-secondary">
|
||||
Explore KiloClaw
|
||||
<Link href="/gateway" className="btn btn-secondary">
|
||||
Explore Kilo Gateway
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -276,20 +237,6 @@ export default function HomePage() {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="quick-section">
|
||||
<h3 className="quick-title">KILOCLAW</h3>
|
||||
<div className="quick-links">
|
||||
<Link href="/kiloclaw/overview" className="quick-link">
|
||||
Overview
|
||||
</Link>
|
||||
<Link href="/kiloclaw/chat-platforms" className="quick-link">
|
||||
Chat Platforms
|
||||
</Link>
|
||||
<Link href="/kiloclaw/development-tools" className="quick-link">
|
||||
Development Tools
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="quick-section">
|
||||
<h3 className="quick-title">PLATFORMS</h3>
|
||||
<div className="quick-links">
|
||||
@@ -348,7 +295,7 @@ export default function HomePage() {
|
||||
<section className="terminal-section">
|
||||
<div className="terminal-intro">
|
||||
<h2 className="section-title">Try it out</h2>
|
||||
<p className="terminal-description">Get started quickly with Kilo Code, KiloClaw, and Kilo Gateway</p>
|
||||
<p className="terminal-description">Get started quickly with Kilo Code and Kilo Gateway</p>
|
||||
</div>
|
||||
<div className="terminal-container">
|
||||
<div className="terminal-tabs">
|
||||
@@ -370,12 +317,6 @@ export default function HomePage() {
|
||||
>
|
||||
Custom Rules
|
||||
</button>
|
||||
<button
|
||||
className={`terminal-tab ${activeTab === "kiloclaw" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("kiloclaw")}
|
||||
>
|
||||
KiloClaw
|
||||
</button>
|
||||
<button
|
||||
className={`terminal-tab ${activeTab === "gateway" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("gateway")}
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Use KiloClaw with Discord: setup, DM access control, and channel c
|
||||
|
||||
# Discord
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
This page covers everything you need to use KiloClaw with Discord: connecting your bot, controlling who can DM it, and adding it to specific channels.
|
||||
|
||||
## Connecting KiloClaw to Discord
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Use Kilo Chat or connect your KiloClaw agent to Telegram, Discord,
|
||||
|
||||
# Chat Platforms
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
KiloClaw includes Kilo Chat as its first-party channel and also supports connecting your AI agent to messaging platforms so it can receive instructions and send responses directly in your chat apps. You can configure third-party channels from the **Settings** tab on your [KiloClaw dashboard](/docs/kiloclaw/dashboard#channels), or from the OpenClaw Control UI after accessing your instance.
|
||||
|
||||
## Kilo Chat
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Using KiloClaw with Slack"
|
||||
|
||||
# Slack
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
This page covers everything you need to use KiloClaw with Slack: connecting your bot, controlling who can DM it, and adding it to channels.
|
||||
|
||||
## Connecting KiloClaw to Slack
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Use KiloClaw with Telegram: setup, DM access control, and group ch
|
||||
|
||||
# Telegram
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
This page covers everything you need to use KiloClaw with Telegram: connecting your bot and adding it to group chats.
|
||||
|
||||
## Connecting KiloClaw to Telegram
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Browse and switch models from the Control UI chat"
|
||||
|
||||
# Changing Models
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
The Control UI Chat tab doubles as a command line for model management. KiloClaw exposes 335+ models through the `kilocode` provider and you can browse and switch between them without leaving the chat.
|
||||
|
||||
| Command | Description |
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Control which commands your KiloClaw agent can run on the host mac
|
||||
|
||||
# Exec Approvals
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Exec approvals are the safety interlock that controls which commands your agent can run on the host machine (gateway or node). By default, **all host exec requests are denied** — you must explicitly allowlist the commands you want your agent to run independently. This prevents accidental execution of destructive commands.
|
||||
|
||||
{% callout type="warning" %}
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Browser-based dashboard for managing your OpenClaw instance"
|
||||
|
||||
# OpenClaw Control UI
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
The Control UI is a browser-based dashboard (built with Vite + Lit) served by the OpenClaw Gateway on the same port as the gateway itself (default: `http://localhost:18789/`). It connects via WebSocket and gives you real-time control over your agent, channels, sessions, and system configuration. For KiloClaw users, see [Accessing the Control UI](/docs/kiloclaw/dashboard#accessing-the-control-ui) to get started.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Pin your KiloClaw instance to a specific OpenClaw version and vari
|
||||
|
||||
# Version Pinning
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Version pinning lets you lock your KiloClaw instance to a specific OpenClaw version and variant. This gives you control over when your instance upgrades — it stays on the pinned version until you explicitly change it.
|
||||
|
||||
## When to Use Version Pinning
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Managing your KiloClaw instance from the dashboard"
|
||||
|
||||
# KiloClaw Dashboard
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
This page covers everything you can do from the KiloClaw dashboard. For getting started, see [KiloClaw Overview](/docs/kiloclaw/overview).
|
||||
|
||||
{% image src="/docs/img/kiloclaw/dashboard.png" alt="Connect account screen" width="800" caption="The KiloClaw Dashboard" /%}
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Connect Composio to your KiloClaw agent to access hundreds of tool
|
||||
|
||||
# Composio Integration
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Connect Composio to your KiloClaw agent to instantly unlock access to 250+ tool integrations — from Salesforce and HubSpot to Notion, Jira, and beyond. Composio is a platform that handles the authentication and connection details for each service, so your agent can use them without you having to set up each one individually.
|
||||
|
||||
{% callout type="info" title="Tip" %}
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Connect a GitHub account to your KiloClaw agent for repository acc
|
||||
|
||||
# GitHub Integration
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Connect a GitHub account to your KiloClaw agent so it can clone repositories, push commits, open pull requests, and leave code reviews — all autonomously.
|
||||
|
||||
{% callout type="warning" title="Security" %}
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Connect a dedicated Google account to KiloClaw for access to Gmail
|
||||
|
||||
# Google Workspace Integration
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Connect a dedicated Google account to KiloClaw so it can interact with Google Workspace services — Gmail, Calendar, Drive, Docs, Sheets, Slides, Tasks, People, Forms, Chat, Classroom, and Apps Script.
|
||||
|
||||
{% callout type="warning" title="Use a dedicated Google account" %}
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Manage integrations and settings for your KiloClaw agent"
|
||||
|
||||
# Integrations
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Configure integrations and settings for your KiloClaw agent. Connect third-party services to give your agent access to repositories, issue trackers, calendars, documents, and hundreds of other tools — all without manual intervention.
|
||||
|
||||
## Available Integrations
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Connect Linear to your KiloClaw agent to create and manage issues
|
||||
|
||||
# Linear Integration
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Connect Linear to your KiloClaw agent so it can create issues, update their status, read project backlogs, and track work — all automatically. Linear is a project management tool popular with software teams for planning and tracking features, bugs, and tasks.
|
||||
|
||||
{% callout type="warning" title="Keep your API key private" %}
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Start-to-finish guide for configuring your KiloClaw instance"
|
||||
|
||||
# Setup walkthrough
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
This guide walks you through a full KiloClaw setup — from creating accounts to scheduling your first automated workflow. Plan for about 60 minutes.
|
||||
|
||||
## Planning your setup
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Frequently asked questions about KiloClaw"
|
||||
|
||||
# FAQ
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
## How can I change my model?
|
||||
|
||||
You can change the model in two ways:
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Pricing details for KiloClaw instances and model inference"
|
||||
|
||||
# Pricing
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
KiloClaw uses Kilo Gateway credits by default — if you route requests through BYOK, model usage is billed directly by your provider instead.
|
||||
|
||||
## Instance Hosting
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "One-click deployment of your Kilo-hosted AI agent with OpenClaw"
|
||||
|
||||
# KiloClaw 🦀
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
KiloClaw is Kilo's hosted [OpenClaw](https://openclaw.ai) service — a one-click deployment that gives you a personal or organization-scoped AI agent without the complexity of self-hosting. OpenClaw is a 24/7, open source AI agent that connects to Kilo Chat and optional chat platforms like Telegram, Discord, and Slack so it can take real actions automatically, not just chat.
|
||||
|
||||
KiloClaw is powered by Kilo Code. The API key is platform-managed, so you never need to bring your own.
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Default system utilities, languages, and CLI tools included in the
|
||||
|
||||
# Pre-installed Software
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Every KiloClaw instance ships with a curated set of system utilities, language runtimes, package managers, and CLI tools. This page documents everything that comes pre-installed in the KiloClaw Docker image so you know what's available out of the box. Where a specific version is listed it reflects the pin in the Dockerfile as of March 2026. Entries marked **unpinned** install the latest available version at image build time and may differ between releases.
|
||||
|
||||
## Base Image
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Connect your KiloClaw agent to 1Password to securely manage creden
|
||||
|
||||
# 1Password Integration Guide
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Connect your KiloClaw agent to 1Password to securely manage credentials. This allows your agent to fetch API keys or passwords without ever seeing them in plain text.
|
||||
|
||||
## Step 1: Create a Dedicated Vault
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Enable your KiloClaw agents to perform financial transactions with
|
||||
|
||||
# AgentCard Integration
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Enable your KiloClaw agents to perform financial transactions by creating and managing virtual debit cards. This integration allows for automated purchasing and expense management within set limits.
|
||||
|
||||
## AgentCard Setup
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Equip your KiloClaw agent with real-time web browsing via the Brav
|
||||
|
||||
# Brave Search Integration
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Equip your KiloClaw agent with real-time web browsing capabilities by integrating the Brave Search API. This allows the agent to fetch up-to-date information, perform market research, and verify facts beyond its training data.
|
||||
|
||||
## How to Generate a Brave Search API Key
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Third-party tool integrations for your KiloClaw agent"
|
||||
|
||||
# Tools
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
KiloClaw supports integrations with third-party tools that extend your agent's capabilities — from secure credential management to web search and financial transactions.
|
||||
|
||||
## Available Integrations
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Configure your KiloClaw agent to use third-party tools and service
|
||||
|
||||
# Setting Up Other Services
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
While KiloClaw comes with a set of [pre-configured tool integrations](/docs/kiloclaw/tools), your agent isn't limited to just those. KiloClaw can be configured to use virtually any third-party integration as a tool — as long as it has a CLI or an API, you can teach your agent to work with it.
|
||||
|
||||
We have seen this pattern work well with outside services like ZenDesk, Todoist, GitLab, and more.
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Automate your KiloClaw agent with webhooks and scheduled triggers"
|
||||
|
||||
# Triggers
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Triggers let external events and schedules drive your KiloClaw agent automatically. Instead of typing every instruction yourself, triggers deliver messages to your agent on your behalf. This lets it react to real-world events or run tasks on a schedule without polling.
|
||||
|
||||
All triggers are managed from the **Settings** page in the KiloClaw section of the sidebar.
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Run tasks on a schedule using cron expressions"
|
||||
|
||||
# Scheduled Triggers
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
Scheduled triggers let your KiloClaw agent run tasks automatically on a recurring schedule. Instead of waiting for an external event, a scheduled trigger fires at the times you define using cron expressions. When it fires, the prompt template is rendered and delivered as a chat message to your KiloClaw instance, just like a webhook.
|
||||
|
||||
Scheduled triggers are one trigger mode shared by KiloClaw and Cloud Agent. In
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Trigger your KiloClaw agent from external events using webhooks"
|
||||
|
||||
# Webhooks
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
KiloClaw supports inbound webhooks so external events can trigger your agent automatically. Form submissions, alerts, calendar updates, ecommerce orders, IoT sensor data; anything that can send an HTTP request can kick off a conversation with your agent. When a webhook fires, the payload is rendered through a prompt template and delivered as a chat message to your KiloClaw instance. The agent processes and responds as if you typed it yourself.
|
||||
|
||||
Webhook triggers are one trigger mode shared by KiloClaw and Cloud Agent. In
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "How KiloClaw instances are structured"
|
||||
|
||||
# Architecture Notes
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
For advanced users — how KiloClaw instances are structured:
|
||||
|
||||
- **Dedicated machine** — Each user gets their own machine and persistent volume. There is no shared infrastructure between users.
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Answers to common KiloClaw troubleshooting questions"
|
||||
|
||||
# Common Questions
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
## OpenClaw Doctor
|
||||
|
||||
OpenClaw Doctor is the recommended first step when something isn't working. It runs diagnostics on your instance and automatically fixes common configuration issues.
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Frequently asked questions about KiloClaw"
|
||||
|
||||
# FAQ
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
## How can I change my model?
|
||||
|
||||
You can change the model in two ways:
|
||||
|
||||
@@ -5,6 +5,8 @@ description: "Understanding KiloClaw gateway process states"
|
||||
|
||||
# Gateway Process States
|
||||
|
||||
{% partial file="kiloclaw-eol.md" /%}
|
||||
|
||||
The Gateway Process tab shows the current state of the OpenClaw process inside your machine:
|
||||
|
||||
- **Running** — The process is up and handling requests
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f5683017f16dcb908a96c2726cf6d48f58bf8be56e20ef6f9fd8e4e916cbf8d9
|
||||
size 15619
|
||||
oid sha256:ef3bc4e6f95193ae78ed67f7b50c7623f6e2b97ac6c23b41e051d49639837c6c
|
||||
size 15620
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7ad513a7edc4886d509a21ed637a96c1119c380d8edd885d1cca90cab2336d6a
|
||||
size 28308
|
||||
oid sha256:d866d08bc0d18ee2abc4c2bd246f2a958107ca1cb1771cb61e37aa2597504ed8
|
||||
size 28266
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f6eeadf51d0e877606af261a5ecfc00c14cca0936fa10c6bfd10a30c7e495105
|
||||
size 27239
|
||||
oid sha256:fd3d9561954b7acb867100a16271aaf1135651cba82f7d8e7b90055a823a00ba
|
||||
size 27158
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8ed7319f670b70e7847114e6f2b636bb862c423a65e0747614023e45dfff526a
|
||||
size 27277
|
||||
oid sha256:c2c8b3e4ded1bde02fe2c72764d16c4e056a91e9fa935c9925b8095bfaf9a748
|
||||
size 27272
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b847d25a2fc62bf046c95575ae06b391e73c390d31b31a59495217f03e004c42
|
||||
size 29733
|
||||
oid sha256:c2c16dcc24af726c383ff7af879e653065022627c0384222cf8652ebb640ec13
|
||||
size 29760
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d17bc762c90f113c5187d61f134de6b7032a703879b9a62da4a8be6c6a8f80e4
|
||||
size 34138
|
||||
oid sha256:513eb47825e3639469e5cf42a1a808a57c8c7f61884746310e1bc479bad37dd6
|
||||
size 34260
|
||||
|
||||
@@ -525,7 +525,34 @@ html[data-theme="kilo-vscode"] [data-component="todos"] {
|
||||
}
|
||||
|
||||
[data-component="question-answers"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--kilo-font-size-13);
|
||||
line-height: var(--line-height-large);
|
||||
|
||||
[data-slot="question-answer-item"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="question-text"],
|
||||
[data-slot="answer-text"] {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
[data-slot="question-text"] {
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="answer-text"] {
|
||||
color: var(--text-strong);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes reasoning-card-down {
|
||||
|
||||
@@ -3171,7 +3171,7 @@ ToolRegistry.register({
|
||||
return (
|
||||
<BasicTool
|
||||
{...props}
|
||||
defaultOpen={false}
|
||||
defaultOpen={completed() && !dismissed()}
|
||||
icon="bubble-5"
|
||||
trigger={
|
||||
<ToolTriggerRow
|
||||
|
||||
@@ -617,7 +617,7 @@ export const ToolHintErrors: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
// --- Question tool: answered (collapsed) ---
|
||||
// --- Question tool: answered (expanded by default) ---
|
||||
|
||||
export const QuestionAnswered: Story = {
|
||||
name: "QuestionAnswered",
|
||||
@@ -628,10 +628,10 @@ export const QuestionAnswered: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
// --- Question tool: answered (expanded) ---
|
||||
// --- Question tool: answered (manually collapsed) ---
|
||||
|
||||
export const QuestionAnsweredExpanded: Story = {
|
||||
name: "QuestionAnswered (expanded)",
|
||||
export const QuestionAnsweredCollapsed: Story = {
|
||||
name: "QuestionAnswered (manually collapsed)",
|
||||
render: () => (
|
||||
<AllProviders data={mockDataQuestionAnswered}>
|
||||
<AssistantParts messages={[mockAssistantMessage]} />
|
||||
|
||||
@@ -41,10 +41,14 @@ test.describe("prompt spacing", () => {
|
||||
test("uses the same responsive gutter in Agent Manager chat", async ({ page }) => {
|
||||
await open(page, "agentmanager--readable-chat-420", 420)
|
||||
const value = await spacing(page)
|
||||
// This session shows its actions row, and a visible dock owns the space
|
||||
// above the composer so both dock states stay flush on it.
|
||||
const dock = await page.locator('[data-component="session-dock"]').evaluate((el) => el.hasAttribute("data-active"))
|
||||
|
||||
expect(dock).toBe(true)
|
||||
expect(value.left).toBeCloseTo(8.4, 1)
|
||||
expect(value.right).toBeCloseTo(8.4, 1)
|
||||
expect(value.top).toBe("8.4px")
|
||||
expect(value.top).toBe("0px")
|
||||
expect(value.bottom).toBe("8.4px")
|
||||
expect(value.gutter).toBe("max(6px, 2cqi)")
|
||||
})
|
||||
|
||||
@@ -81,6 +81,43 @@ test("only one of the two states is visible in the dock", async ({ page }) => {
|
||||
await expect(page.locator('[data-component="session-dock"] .new-task-button-wrapper')).toBeHidden()
|
||||
})
|
||||
|
||||
/**
|
||||
* The dock owns the space above the composer. A gutter left on only one of its
|
||||
* two states put a gap under the actions row that the working indicator did not
|
||||
* have, so the row moved by that gutter on every turn boundary.
|
||||
*/
|
||||
test("both dock states sit flush on the prompt", async ({ page }) => {
|
||||
await openStory(page)
|
||||
|
||||
const measure = async () => {
|
||||
await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))))
|
||||
return page.evaluate(() => {
|
||||
const dock = document.querySelector('[data-component="session-dock"]')
|
||||
const prompt = document.querySelector(".chat-input > .prompt-input-container")
|
||||
if (!(dock instanceof HTMLElement) || !(prompt instanceof HTMLElement)) throw new Error("dock or prompt missing")
|
||||
return {
|
||||
gap: prompt.getBoundingClientRect().top - dock.getBoundingClientRect().bottom,
|
||||
top: prompt.getBoundingClientRect().top,
|
||||
margin: getComputedStyle(prompt).marginTop,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
await expect(page.locator('[data-component="session-dock"] .new-task-button-wrapper')).toBeVisible()
|
||||
const idle = await measure()
|
||||
|
||||
await page.getByTestId("toggle-busy").click()
|
||||
await expect(page.locator('[data-component="session-dock"] .working-indicator')).toBeVisible()
|
||||
const working = await measure()
|
||||
|
||||
expect(idle.gap).toBe(0)
|
||||
expect(working.gap).toBe(0)
|
||||
expect(idle.margin).toBe("0px")
|
||||
expect(working.margin).toBe("0px")
|
||||
// Same spacing on both sides of the swap, so the composer cannot jump.
|
||||
expect(working.top).toBe(idle.top)
|
||||
})
|
||||
|
||||
test("the indicator stays a centered lane on a wide surface", async ({ page }) => {
|
||||
await openStory(page)
|
||||
// Agent Manager width: a full-width indicator put the spinner at the far-left
|
||||
|
||||
@@ -104,5 +104,16 @@ describe("Agent Manager document state", () => {
|
||||
)
|
||||
expect(source.defaultPrevented).toBe(true)
|
||||
expect(native).toEqual([["src/index.ts", 8, 3, "wt-a"]])
|
||||
|
||||
const missing = new CustomEvent("kilo:open-file", {
|
||||
cancelable: true,
|
||||
detail: { filePath: "src/missing.ts" },
|
||||
})
|
||||
handleDocumentOpen(
|
||||
missing,
|
||||
() => false,
|
||||
() => false,
|
||||
)
|
||||
expect(missing.defaultPrevented).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -192,6 +192,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "تعليق",
|
||||
"agentManager.review.sendToChat": "إرسال إلى الدردشة",
|
||||
"agentManager.pr.comment.title": "التعليقات",
|
||||
"agentManager.pr.copyLink": "نسخ رابط PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} غير محلولة",
|
||||
"agentManager.pr.comment.resolvedGroup": "تم الحل ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "إرسال {{count}} غير محلولة إلى الوكيل",
|
||||
|
||||
@@ -195,6 +195,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Comentar",
|
||||
"agentManager.review.sendToChat": "Enviar para o chat",
|
||||
"agentManager.pr.comment.title": "Comentários",
|
||||
"agentManager.pr.copyLink": "Copiar link do PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} não resolvidos",
|
||||
"agentManager.pr.comment.resolvedGroup": "Resolvidos ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Enviar {{count}} não resolvidos para o agente",
|
||||
|
||||
@@ -195,6 +195,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Komentariši",
|
||||
"agentManager.review.sendToChat": "Pošalji u chat",
|
||||
"agentManager.pr.comment.title": "Komentari",
|
||||
"agentManager.pr.copyLink": "Kopiraj PR vezu",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} neriješenih",
|
||||
"agentManager.pr.comment.resolvedGroup": "Riješeno ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Pošalji {{count}} neriješenih agentu",
|
||||
|
||||
@@ -196,6 +196,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Kommenter",
|
||||
"agentManager.review.sendToChat": "Send til chat",
|
||||
"agentManager.pr.comment.title": "Kommentarer",
|
||||
"agentManager.pr.copyLink": "Kopiér PR-link",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} uløste",
|
||||
"agentManager.pr.comment.resolvedGroup": "Løste ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Send {{count}} uløste til agenten",
|
||||
|
||||
@@ -197,6 +197,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Kommentieren",
|
||||
"agentManager.review.sendToChat": "An Chat senden",
|
||||
"agentManager.pr.comment.title": "Kommentare",
|
||||
"agentManager.pr.copyLink": "PR-Link kopieren",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} ungelöst",
|
||||
"agentManager.pr.comment.resolvedGroup": "Gelöst ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "{{count}} ungelöste an Agent senden",
|
||||
|
||||
@@ -198,6 +198,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Comment",
|
||||
"agentManager.review.sendToChat": "Send to chat",
|
||||
"agentManager.pr.comment.title": "Comments",
|
||||
"agentManager.pr.copyLink": "Copy PR link",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} unresolved",
|
||||
"agentManager.pr.comment.resolvedGroup": "Resolved ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Send {{count}} unresolved to agent",
|
||||
|
||||
@@ -196,6 +196,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Comentar",
|
||||
"agentManager.review.sendToChat": "Enviar al chat",
|
||||
"agentManager.pr.comment.title": "Comentarios",
|
||||
"agentManager.pr.copyLink": "Copiar enlace del PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} sin resolver",
|
||||
"agentManager.pr.comment.resolvedGroup": "Resueltos ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Enviar {{count}} sin resolver al agente",
|
||||
|
||||
@@ -199,6 +199,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "نظر",
|
||||
"agentManager.review.sendToChat": "ارسال به چت",
|
||||
"agentManager.pr.comment.title": "نظرات",
|
||||
"agentManager.pr.copyLink": "کپی پیوند PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} حلنشده",
|
||||
"agentManager.pr.comment.resolvedGroup": "حلشده ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "ارسال {{count}} مورد حلنشده به عامل",
|
||||
|
||||
@@ -197,6 +197,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Commenter",
|
||||
"agentManager.review.sendToChat": "Envoyer au chat",
|
||||
"agentManager.pr.comment.title": "Commentaires",
|
||||
"agentManager.pr.copyLink": "Copier le lien du PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} non résolus",
|
||||
"agentManager.pr.comment.resolvedGroup": "Résolus ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Envoyer {{count}} non résolus à l’agent",
|
||||
|
||||
@@ -202,6 +202,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Commenta",
|
||||
"agentManager.review.sendToChat": "Invia alla chat",
|
||||
"agentManager.pr.comment.title": "Commenti",
|
||||
"agentManager.pr.copyLink": "Copia link PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} non risolti",
|
||||
"agentManager.pr.comment.resolvedGroup": "Risolti ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Invia {{count}} non risolti all'agente",
|
||||
|
||||
@@ -196,6 +196,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "コメント",
|
||||
"agentManager.review.sendToChat": "チャットに送信",
|
||||
"agentManager.pr.comment.title": "コメント",
|
||||
"agentManager.pr.copyLink": "PRリンクをコピー",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} 件未解決",
|
||||
"agentManager.pr.comment.resolvedGroup": "解決済み ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "{{count}} 件の未解決コメントをエージェントに送信",
|
||||
|
||||
@@ -194,6 +194,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "댓글",
|
||||
"agentManager.review.sendToChat": "채팅으로 보내기",
|
||||
"agentManager.pr.comment.title": "댓글",
|
||||
"agentManager.pr.copyLink": "PR 링크 복사",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}}개 미해결",
|
||||
"agentManager.pr.comment.resolvedGroup": "해결됨 ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "{{count}}개 미해결 댓글을 에이전트로 보내기",
|
||||
|
||||
@@ -201,6 +201,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Opmerking",
|
||||
"agentManager.review.sendToChat": "Naar chat sturen",
|
||||
"agentManager.pr.comment.title": "Opmerkingen",
|
||||
"agentManager.pr.copyLink": "PR-link kopiëren",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} onopgelost",
|
||||
"agentManager.pr.comment.resolvedGroup": "Opgelost ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "{{count}} onopgeloste opmerkingen naar agent sturen",
|
||||
|
||||
@@ -193,6 +193,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Kommenter",
|
||||
"agentManager.review.sendToChat": "Send til chat",
|
||||
"agentManager.pr.comment.title": "Kommentarer",
|
||||
"agentManager.pr.copyLink": "Kopier PR-lenke",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} uløste",
|
||||
"agentManager.pr.comment.resolvedGroup": "Løste ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Send {{count}} uløste til agenten",
|
||||
|
||||
@@ -195,6 +195,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Komentuj",
|
||||
"agentManager.review.sendToChat": "Wyślij do czatu",
|
||||
"agentManager.pr.comment.title": "Komentarze",
|
||||
"agentManager.pr.copyLink": "Kopiuj link do PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} nierozwiązanych",
|
||||
"agentManager.pr.comment.resolvedGroup": "Rozwiązane ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Wyślij {{count}} nierozwiązanych do agenta",
|
||||
|
||||
@@ -196,6 +196,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Комментировать",
|
||||
"agentManager.review.sendToChat": "Отправить в чат",
|
||||
"agentManager.pr.comment.title": "Комментарии",
|
||||
"agentManager.pr.copyLink": "Копировать ссылку на PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} нерешённых",
|
||||
"agentManager.pr.comment.resolvedGroup": "Решённые ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Отправить {{count}} нерешённых агенту",
|
||||
|
||||
@@ -190,6 +190,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "แสดงความคิดเห็น",
|
||||
"agentManager.review.sendToChat": "ส่งไปยังแชท",
|
||||
"agentManager.pr.comment.title": "ความคิดเห็น",
|
||||
"agentManager.pr.copyLink": "คัดลอกลิงก์ PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "ยังไม่แก้ไข {{count}} รายการ",
|
||||
"agentManager.pr.comment.resolvedGroup": "แก้ไขแล้ว ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "ส่งความคิดเห็นที่ยังไม่แก้ไข {{count}} รายการไปยังเอเจนต์",
|
||||
|
||||
@@ -203,6 +203,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Yorum Yap",
|
||||
"agentManager.review.sendToChat": "Sohbete gönder",
|
||||
"agentManager.pr.comment.title": "Yorumlar",
|
||||
"agentManager.pr.copyLink": "PR bağlantısını kopyala",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} çözülmemiş",
|
||||
"agentManager.pr.comment.resolvedGroup": "Çözüldü ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "{{count}} çözülmemiş yorumu ajana gönder",
|
||||
|
||||
@@ -204,6 +204,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "Коментувати",
|
||||
"agentManager.review.sendToChat": "Надіслати до чату",
|
||||
"agentManager.pr.comment.title": "Коментарі",
|
||||
"agentManager.pr.copyLink": "Копіювати посилання на PR",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} невирішених",
|
||||
"agentManager.pr.comment.resolvedGroup": "Вирішено ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "Надіслати {{count}} невирішених агенту",
|
||||
|
||||
@@ -188,6 +188,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "评论",
|
||||
"agentManager.review.sendToChat": "发送到聊天",
|
||||
"agentManager.pr.comment.title": "评论",
|
||||
"agentManager.pr.copyLink": "复制 PR 链接",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} 个未解决",
|
||||
"agentManager.pr.comment.resolvedGroup": "已解决 ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "将 {{count}} 个未解决评论发送给代理",
|
||||
|
||||
@@ -188,6 +188,7 @@ export const dict = {
|
||||
"agentManager.review.commentAction": "留言",
|
||||
"agentManager.review.sendToChat": "傳送到聊天",
|
||||
"agentManager.pr.comment.title": "留言",
|
||||
"agentManager.pr.copyLink": "複製 PR 連結",
|
||||
"agentManager.pr.comment.unresolvedCount": "{{count}} 個未解決",
|
||||
"agentManager.pr.comment.resolvedGroup": "已解決 ({{count}})",
|
||||
"agentManager.pr.comment.sendAll": "將 {{count}} 個未解決留言傳送給代理",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import { createSignal } from "solid-js"
|
||||
import { IconButton } from "@kilocode/kilo-ui/icon-button"
|
||||
import { useVSCode } from "../../src/context/vscode"
|
||||
|
||||
export function CopyButton(props: { text: string; label?: string; class?: string }) {
|
||||
const vscode = useVSCode()
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText(props.text)
|
||||
vscode.postMessage({ type: "agentManager.copyToClipboard", text: props.text })
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 1500)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { IconButton } from "@kilocode/kilo-ui/icon-button"
|
||||
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import type { WorktreeState } from "../../src/types/messages"
|
||||
import type { PRStatus } from "../../src/types/messages"
|
||||
import { useLanguage } from "../../src/context/language"
|
||||
import { PRBadge } from "./PRBadge"
|
||||
import { PROverview } from "./PROverview"
|
||||
import { PRReviewers } from "./PRReviewers"
|
||||
@@ -12,6 +13,7 @@ import { PRChecks } from "./PRChecks"
|
||||
import { PRComments } from "./PRComments"
|
||||
import { commentScroll, setCommentScroll } from "./pr-comment-state"
|
||||
import { PRSummary } from "./PRSummary"
|
||||
import { CopyButton } from "./CopyButton"
|
||||
import "./pr-panel.css"
|
||||
|
||||
interface PRPanelProps {
|
||||
@@ -27,6 +29,7 @@ interface PRPanelProps {
|
||||
}
|
||||
|
||||
export const PRPanel: Component<PRPanelProps> = (props) => {
|
||||
const { t } = useLanguage()
|
||||
let commentsRef: HTMLDivElement | undefined
|
||||
let bodyRef: HTMLDivElement | undefined
|
||||
let capture: number | undefined
|
||||
@@ -117,6 +120,9 @@ export const PRPanel: Component<PRPanelProps> = (props) => {
|
||||
<span class="am-pr-panel-number">#{props.pr.number}</span>
|
||||
</div>
|
||||
<div class="am-pr-panel-actions am-pr-row">
|
||||
<Tooltip value={t("agentManager.pr.copyLink")} placement="bottom">
|
||||
<CopyButton text={props.pr.url} label={t("agentManager.pr.copyLink")} />
|
||||
</Tooltip>
|
||||
<Tooltip value="Open in browser" placement="bottom">
|
||||
<IconButton
|
||||
icon="link"
|
||||
|
||||
@@ -183,7 +183,7 @@ export const MarkdownAnnotationLayer: Component<MarkdownAnnotationLayerProps> =
|
||||
layer.appendChild(row)
|
||||
}
|
||||
|
||||
observer?.observe(root, { childList: true, subtree: true })
|
||||
observer?.observe(pane, { childList: true, subtree: true })
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
@@ -199,14 +199,14 @@ export const MarkdownAnnotationLayer: Component<MarkdownAnnotationLayerProps> =
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const root = props.root()
|
||||
if (!root) return
|
||||
const pane = props.pane()
|
||||
if (!pane) return
|
||||
observer?.disconnect()
|
||||
observer = new MutationObserver((mutations) => {
|
||||
if (mutations.every(isAnnotationMutation)) return
|
||||
schedule()
|
||||
})
|
||||
observer.observe(root, { childList: true, subtree: true })
|
||||
observer.observe(pane, { childList: true, subtree: true })
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
|
||||
@@ -176,7 +176,9 @@ export function createDocumentInspector(
|
||||
return true
|
||||
}
|
||||
const openFile = (file: string, line?: number, column?: number, sessionId = context()) => {
|
||||
if (sessionId) vscode.postMessage({ type: "agentManager.openFile", sessionId, filePath: file, line, column })
|
||||
if (!sessionId) return false
|
||||
vscode.postMessage({ type: "agentManager.openFile", sessionId, filePath: file, line, column })
|
||||
return true
|
||||
}
|
||||
onMount(() => {
|
||||
const handler = (event: Event) => handleDocumentOpen(event, open, openFile)
|
||||
@@ -201,7 +203,7 @@ export function createDocumentInspector(
|
||||
export function handleDocumentOpen(
|
||||
event: Event,
|
||||
open: (file: string, sessionId?: string, line?: number, column?: number) => boolean,
|
||||
openFile?: (file: string, line?: number, column?: number, sessionId?: string) => void,
|
||||
openFile?: (file: string, line?: number, column?: number, sessionId?: string) => boolean,
|
||||
): void {
|
||||
const detail = (event as CustomEvent<{ filePath?: unknown; sessionID?: unknown; line?: unknown; column?: unknown }>)
|
||||
.detail
|
||||
@@ -211,8 +213,7 @@ export function handleDocumentOpen(
|
||||
const line = typeof detail.line === "number" ? detail.line : undefined
|
||||
const column = typeof detail.column === "number" ? detail.column : undefined
|
||||
if (!isMarkdownPath(file)) {
|
||||
if (!openFile) return
|
||||
openFile(file, line, column, sessionId)
|
||||
if (!openFile?.(file, line, column, sessionId)) return
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -14,8 +14,13 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-input > .session-dock + .prompt-input-container {
|
||||
margin-top: var(--prompt-gutter);
|
||||
/* A visible dock owns the spacing above the composer, so both of its states sit
|
||||
flush on the prompt and swapping the working indicator for the actions row
|
||||
cannot move either of them. The dock is hidden rather than unmounted while
|
||||
idle, so this must not match the inactive row: the composer keeps its own
|
||||
gutter there. */
|
||||
.chat-input > .session-dock[data-active] + .prompt-input-container {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
@@ -822,6 +822,11 @@ export interface OpenWorktreeRequest {
|
||||
worktreeId: string
|
||||
}
|
||||
|
||||
export interface AgentManagerCopyToClipboardRequest {
|
||||
type: "agentManager.copyToClipboard"
|
||||
text: string
|
||||
}
|
||||
|
||||
// Copy text to the system clipboard via the extension host
|
||||
export interface CopyToClipboardRequest {
|
||||
type: "copyToClipboard"
|
||||
@@ -1591,6 +1596,7 @@ export type WebviewMessage =
|
||||
| ShowLocalTerminalRequest
|
||||
| ShowWorktreeTerminalRequest
|
||||
| OpenWorktreeRequest
|
||||
| AgentManagerCopyToClipboardRequest
|
||||
| CopyToClipboardRequest
|
||||
| ShowExistingLocalTerminalRequest
|
||||
| AgentManagerOpenFileRequest
|
||||
|
||||
@@ -22,7 +22,7 @@ import { isRecord } from "@/util/record"
|
||||
import type { ConsoleState } from "@opencode-ai/core/v1/config/console-state"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect"
|
||||
import { Context, Duration, Effect, Fiber, Layer, Option, Schema } from "effect"
|
||||
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
||||
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
||||
import { containsPath, type InstanceContext } from "../project/instance-context"
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
IndexingSchema as KiloIndexingSchema,
|
||||
} from "@kilocode/kilo-indexing/config"
|
||||
import { unique } from "remeda"
|
||||
import { installLocalPluginDependency, needsLocalPluginDependency } from "@/kilocode/config/plugin-deps"
|
||||
// kilocode_change end
|
||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||
import * as Log from "@opencode-ai/core/util/log" // kilocode_change
|
||||
@@ -721,6 +722,7 @@ const layer = Layer.effect(
|
||||
|
||||
// kilocode_change start
|
||||
for (const dir of unique(directories)) {
|
||||
const plugins: ConfigPluginV1.Spec[] = [] // kilocode_change - track file plugins contributed by this directory
|
||||
const scope = primarySet.has(dir) ? "local" : undefined
|
||||
// kilocode_change - trust {file:}/{env:} only for global-scoped config dirs, never project ones
|
||||
const dirScope = scope ?? (yield* pluginScopeForSource(dir))
|
||||
@@ -736,18 +738,14 @@ const layer = Layer.effect(
|
||||
yield* Effect.logDebug(`loading config from ${source}`)
|
||||
// kilocode_change - untrusted config dirs confine {file:} reads to projectRoot
|
||||
const fileScope = dirTrusted ? undefined : { root: projectRoot, source }
|
||||
yield* merge(
|
||||
source,
|
||||
yield* loadFile(source, authEnv, dirTrusted, fileScope, dirTrusted ? undefined : warnings).pipe(
|
||||
// kilocode_change
|
||||
Effect.catchDefect((err: unknown) => {
|
||||
caughtWarning(warnings, source, err)
|
||||
return Effect.succeed({} as Info)
|
||||
}),
|
||||
),
|
||||
dirScope,
|
||||
dirTrusted,
|
||||
const next = yield* loadFile(source, authEnv, dirTrusted, fileScope, dirTrusted ? undefined : warnings).pipe(
|
||||
Effect.catchDefect((err: unknown) => {
|
||||
caughtWarning(warnings, source, err)
|
||||
return Effect.succeed({} as Info)
|
||||
}),
|
||||
)
|
||||
plugins.push(...(next.plugin ?? []))
|
||||
yield* merge(source, next, dirScope, dirTrusted)
|
||||
result.agent ??= {}
|
||||
result.mode ??= {}
|
||||
result.plugin ??= []
|
||||
@@ -757,27 +755,6 @@ const layer = Layer.effect(
|
||||
|
||||
yield* ensureGitignore(dir).pipe(Effect.orDie)
|
||||
|
||||
const dep = yield* npmSvc
|
||||
.install(dir, {
|
||||
add: [
|
||||
{
|
||||
name: "@kilocode/plugin",
|
||||
version: InstallationLocal ? undefined : InstallationVersion,
|
||||
},
|
||||
],
|
||||
})
|
||||
.pipe(
|
||||
Effect.exit,
|
||||
Effect.tap((exit) =>
|
||||
Exit.isFailure(exit)
|
||||
? Effect.logWarning("background dependency install failed", { dir, error: String(exit.cause) })
|
||||
: Effect.void,
|
||||
),
|
||||
Effect.asVoid,
|
||||
Effect.forkDetach,
|
||||
)
|
||||
deps.push(dep)
|
||||
|
||||
// kilocode_change start - propagate parse errors to the Warning accumulator
|
||||
const sourceScopes = (names: readonly string[]) => [
|
||||
...(dirSourceScope ? [dirSourceScope] : []),
|
||||
@@ -810,7 +787,14 @@ const layer = Layer.effect(
|
||||
// kilocode_change - Auto-discovered plugins under config directories are already local files, so ConfigPlugin.load
|
||||
// returns normalized Specs and we only need to attach origin metadata here.
|
||||
const list = yield* Effect.promise(() => ConfigPlugin.load(dir))
|
||||
plugins.push(...list) // kilocode_change
|
||||
yield* mergePluginOrigins(dir, list, dirScope) // kilocode_change
|
||||
|
||||
// kilocode_change start
|
||||
if (needsLocalPluginDependency(plugins)) {
|
||||
deps.push(yield* installLocalPluginDependency(npmSvc, dir, InstallationVersion, InstallationLocal))
|
||||
}
|
||||
// kilocode_change end
|
||||
}
|
||||
|
||||
if (process.env.KILO_CONFIG_CONTENT) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ConfigPlugin } from "@/config/plugin"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { ConfigPluginV1 } from "@opencode-ai/core/v1/config/plugin"
|
||||
import { Effect, Exit } from "effect"
|
||||
|
||||
export function needsLocalPluginDependency(plugins: readonly ConfigPluginV1.Spec[]) {
|
||||
return plugins.some((plugin) => ConfigPlugin.pluginSpecifier(plugin).startsWith("file://"))
|
||||
}
|
||||
|
||||
export function installLocalPluginDependency(npm: Npm.Interface, dir: string, version: string, local: boolean) {
|
||||
return npm
|
||||
.install(dir, {
|
||||
add: [
|
||||
{
|
||||
name: "@kilocode/plugin",
|
||||
version: local ? undefined : version,
|
||||
},
|
||||
],
|
||||
})
|
||||
.pipe(
|
||||
Effect.exit,
|
||||
Effect.tap((exit) =>
|
||||
Exit.isFailure(exit)
|
||||
? Effect.logWarning("background dependency install failed", { dir, error: String(exit.cause) })
|
||||
: Effect.void,
|
||||
),
|
||||
Effect.asVoid,
|
||||
Effect.forkDetach,
|
||||
)
|
||||
}
|
||||
@@ -1724,6 +1724,19 @@ export function maxOutputTokens(model: Provider.Model, outputTokenMax = OUTPUT_T
|
||||
return Math.min(model.limit.output, outputTokenMax) || outputTokenMax
|
||||
}
|
||||
|
||||
// kilocode_change start
|
||||
export function maxOutputTokensForRequest(input: {
|
||||
model: Provider.Model
|
||||
options: Record<string, any>
|
||||
maxOutputTokens: number | undefined
|
||||
}): number | undefined {
|
||||
if (input.model.api.npm === "@ai-sdk/cerebras" && input.options.max_completion_tokens !== undefined) {
|
||||
return undefined
|
||||
}
|
||||
return input.maxOutputTokens
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
type JsonRecord = Record<string, unknown>
|
||||
|
||||
function isPlainObject(value: unknown): value is JsonRecord {
|
||||
|
||||
@@ -312,7 +312,13 @@ const live: Layer.Layer<
|
||||
temperature: prepared.params.temperature,
|
||||
topP: prepared.params.topP,
|
||||
topK: prepared.params.topK,
|
||||
maxOutputTokens: prepared.params.maxOutputTokens,
|
||||
// kilocode_change start
|
||||
maxOutputTokens: ProviderTransform.maxOutputTokensForRequest({
|
||||
model: input.model,
|
||||
options: prepared.params.options,
|
||||
maxOutputTokens: prepared.params.maxOutputTokens,
|
||||
}),
|
||||
// kilocode_change end
|
||||
providerOptions: prepared.params.options,
|
||||
headers: prepared.headers,
|
||||
abort: input.abort,
|
||||
@@ -388,9 +394,15 @@ const live: Layer.Layer<
|
||||
topK: prepared.params.topK,
|
||||
providerOptions: ProviderTransform.providerOptions(input.model, prepared.params.options),
|
||||
activeTools: Object.keys(prepared.tools).filter((x) => x !== "invalid"),
|
||||
// kilocode_change start
|
||||
tools: prepared.tools,
|
||||
toolChoice: input.toolChoice,
|
||||
maxOutputTokens: prepared.params.maxOutputTokens,
|
||||
maxOutputTokens: ProviderTransform.maxOutputTokensForRequest({
|
||||
model: input.model,
|
||||
options: prepared.params.options,
|
||||
maxOutputTokens: prepared.params.maxOutputTokens,
|
||||
}),
|
||||
// kilocode_change end
|
||||
abortSignal: input.abort,
|
||||
...KiloLLM.timeout({ options: prepared.params.options, fallback: item.options, log: l }), // kilocode_change
|
||||
headers: prepared.headers,
|
||||
|
||||
@@ -33,8 +33,7 @@ export interface TaskPromptOps {
|
||||
const id = "task"
|
||||
const BACKGROUND_DESCRIPTION = [
|
||||
"Background mode: background=true launches the subagent asynchronously and returns immediately.",
|
||||
"Foreground is the default; use it when you need the result before continuing.",
|
||||
"Use background only for independent work that can run while you continue elsewhere.",
|
||||
"Use foreground when you need the result before proceeding; otherwise use background for non-overlapping work, but do not give the final answer until all required background results have arrived.", // kilocode_change
|
||||
"You will be notified automatically when it finishes.",
|
||||
].join(" ")
|
||||
const BACKGROUND_STARTED = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { $ } from "bun"
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Effect, Layer, Option, Schema } from "effect"
|
||||
import { Effect, Fiber, Layer, Logger, Option, Schema } from "effect"
|
||||
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
@@ -43,12 +43,14 @@ const noopNpm = Layer.mock(Npm.Service)({
|
||||
const unexpectedHttp = HttpClient.make((request) =>
|
||||
Effect.die(`unexpected http request: ${request.method} ${request.url}`),
|
||||
)
|
||||
const layer = AppNodeBuilder.build(Config.node, [
|
||||
[Auth.node, emptyAuth],
|
||||
[Account.node, emptyAccount],
|
||||
[Npm.node, noopNpm],
|
||||
[LayerNodePlatform.httpClient, Layer.succeed(HttpClient.HttpClient, unexpectedHttp)],
|
||||
]).pipe(Layer.provideMerge(infra))
|
||||
const make = (npm: Layer.Layer<Npm.Service>) =>
|
||||
AppNodeBuilder.build(Config.node, [
|
||||
[Auth.node, emptyAuth],
|
||||
[Account.node, emptyAccount],
|
||||
[Npm.node, npm],
|
||||
[LayerNodePlatform.httpClient, Layer.succeed(HttpClient.HttpClient, unexpectedHttp)],
|
||||
]).pipe(Layer.provideMerge(infra))
|
||||
const layer = make(noopNpm)
|
||||
|
||||
const load = () => Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(layer)))
|
||||
const clear = () =>
|
||||
@@ -945,6 +947,181 @@ describe("unset propagation across layered config files", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("project plugin dependencies", () => {
|
||||
async function sandbox(fn: (dir: string) => Promise<void>) {
|
||||
await using home = await tmpdir()
|
||||
await using tmp = await tmpdir()
|
||||
const prev = Global.Path.config
|
||||
;(Global.Path as { config: string }).config = home.path
|
||||
await disposeAllInstances()
|
||||
|
||||
try {
|
||||
await fn(tmp.path)
|
||||
} finally {
|
||||
;(Global.Path as { config: string }).config = prev
|
||||
await disposeAllInstances()
|
||||
}
|
||||
}
|
||||
|
||||
test("does not install dependencies for an ordinary project config directory", async () => {
|
||||
await sandbox(async (dir) => {
|
||||
await writeConfig(path.join(dir, ".kilo"), { username: "kilo" })
|
||||
const calls: Array<{ dir: string; name?: string }> = []
|
||||
const npm = Layer.mock(Npm.Service)({
|
||||
install: (dir, input) =>
|
||||
Effect.sync(() => calls.push({ dir, name: input?.add[0]?.name })).pipe(Effect.asVoid),
|
||||
add: () => Effect.die("not implemented"),
|
||||
which: () => Effect.succeed(undefined),
|
||||
})
|
||||
|
||||
await provideTestInstance({
|
||||
directory: dir,
|
||||
fn: () =>
|
||||
Effect.runPromise(
|
||||
Config.Service.use((svc) => svc.get().pipe(Effect.andThen(svc.waitForDependencies()))).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(make(npm)),
|
||||
),
|
||||
),
|
||||
})
|
||||
|
||||
expect(calls).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
test("installs dependencies for an auto-discovered file plugin and waits for completion", async () => {
|
||||
await sandbox(async (dir) => {
|
||||
const config = path.join(dir, ".kilo")
|
||||
await Filesystem.write(path.join(config, "plugin", "local.ts"), "export default {}")
|
||||
const gate = Promise.withResolvers<void>()
|
||||
const calls: Array<{ dir: string; name?: string }> = []
|
||||
const npm = Layer.mock(Npm.Service)({
|
||||
install: (dir, input) =>
|
||||
Effect.sync(() => calls.push({ dir, name: input?.add[0]?.name })).pipe(
|
||||
Effect.andThen(Effect.promise(() => gate.promise)),
|
||||
),
|
||||
add: () => Effect.die("not implemented"),
|
||||
which: () => Effect.succeed(undefined),
|
||||
})
|
||||
|
||||
const pending = await provideTestInstance({
|
||||
directory: dir,
|
||||
fn: () =>
|
||||
Effect.runPromise(
|
||||
Config.Service.use((svc) =>
|
||||
Effect.gen(function* () {
|
||||
yield* svc.get()
|
||||
const fiber = yield* svc.waitForDependencies().pipe(Effect.forkChild)
|
||||
const status = yield* Fiber.join(fiber).pipe(Effect.timeoutOption("10 millis"))
|
||||
gate.resolve()
|
||||
yield* Fiber.join(fiber)
|
||||
return Option.isNone(status)
|
||||
}),
|
||||
).pipe(Effect.scoped, Effect.provide(make(npm))),
|
||||
),
|
||||
})
|
||||
|
||||
expect(pending).toBe(true)
|
||||
expect(calls).toEqual([{ dir: config, name: "@kilocode/plugin" }])
|
||||
})
|
||||
})
|
||||
|
||||
test("installs dependencies for a file plugin declared in directory config", async () => {
|
||||
await sandbox(async (dir) => {
|
||||
const config = path.join(dir, ".kilo")
|
||||
await writeConfig(config, { plugin: ["./local.ts"] })
|
||||
await Filesystem.write(path.join(config, "local.ts"), "export default {}")
|
||||
const calls: Array<{ dir: string; name?: string }> = []
|
||||
const npm = Layer.mock(Npm.Service)({
|
||||
install: (dir, input) =>
|
||||
Effect.sync(() => calls.push({ dir, name: input?.add[0]?.name })).pipe(Effect.asVoid),
|
||||
add: () => Effect.die("not implemented"),
|
||||
which: () => Effect.succeed(undefined),
|
||||
})
|
||||
|
||||
await provideTestInstance({
|
||||
directory: dir,
|
||||
fn: () =>
|
||||
Effect.runPromise(
|
||||
Config.Service.use((svc) => svc.get().pipe(Effect.andThen(svc.waitForDependencies()))).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(make(npm)),
|
||||
),
|
||||
),
|
||||
})
|
||||
|
||||
expect(calls).toEqual([{ dir: config, name: "@kilocode/plugin" }])
|
||||
})
|
||||
})
|
||||
|
||||
test("does not install dependencies for built-in or package plugins", async () => {
|
||||
await sandbox(async (dir) => {
|
||||
await writeConfig(path.join(dir, ".kilo"), {
|
||||
plugin: ["@kilocode/kilo-indexing", "opencode-gitlab-auth"],
|
||||
})
|
||||
const calls: string[] = []
|
||||
const npm = Layer.mock(Npm.Service)({
|
||||
install: (dir) => Effect.sync(() => calls.push(dir)).pipe(Effect.asVoid),
|
||||
add: () => Effect.die("not implemented"),
|
||||
which: () => Effect.succeed(undefined),
|
||||
})
|
||||
|
||||
await provideTestInstance({
|
||||
directory: dir,
|
||||
fn: () =>
|
||||
Effect.runPromise(
|
||||
Config.Service.use((svc) => svc.get().pipe(Effect.andThen(svc.waitForDependencies()))).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(make(npm)),
|
||||
),
|
||||
),
|
||||
})
|
||||
|
||||
expect(calls).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
test("keeps a failed file plugin dependency install non-fatal and logs a warning", async () => {
|
||||
await sandbox(async (dir) => {
|
||||
const config = path.join(dir, ".kilo")
|
||||
await writeConfig(config, { username: "loaded" })
|
||||
await Filesystem.write(path.join(config, "plugins", "local.js"), "export default {}")
|
||||
const logs: string[] = []
|
||||
const logger = Logger.make(({ message }) => logs.push(String(message)))
|
||||
const npm = Layer.mock(Npm.Service)({
|
||||
install: (dir) =>
|
||||
Effect.fail(
|
||||
new Npm.InstallFailedError({
|
||||
dir,
|
||||
add: ["@kilocode/plugin"],
|
||||
cause: new Error("test install failure"),
|
||||
}),
|
||||
),
|
||||
add: () => Effect.die("not implemented"),
|
||||
which: () => Effect.succeed(undefined),
|
||||
})
|
||||
const testLayer = make(npm).pipe(Layer.provideMerge(Logger.layer([logger], { mergeWithExisting: false })))
|
||||
|
||||
const loaded = await provideTestInstance({
|
||||
directory: dir,
|
||||
fn: () =>
|
||||
Effect.runPromise(
|
||||
Config.Service.use((svc) =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* svc.get()
|
||||
yield* svc.waitForDependencies()
|
||||
return result.username
|
||||
}),
|
||||
).pipe(Effect.scoped, Effect.provide(testLayer)),
|
||||
),
|
||||
})
|
||||
|
||||
expect(loaded).toBe("loaded")
|
||||
expect(logs.some((message) => message.includes("background dependency install failed"))).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("agent config", () => {
|
||||
test("accepts delete sentinels for agent model and variant overrides", () => {
|
||||
const patch = decode({ agent: { explore: { model: null, variant: null } } })
|
||||
|
||||
@@ -4699,6 +4699,48 @@ describe("ProviderTransform.variants", () => {
|
||||
expect(result.low).toEqual({ reasoningEffort: "low" })
|
||||
expect(result.high).toEqual({ reasoningEffort: "high" })
|
||||
})
|
||||
|
||||
// kilocode_change start
|
||||
test("omits the generic output cap when an exact max_completion_tokens value is configured", () => {
|
||||
const model = createMockModel({
|
||||
id: "cerebras/gpt-oss-120b",
|
||||
providerID: "cerebras",
|
||||
api: {
|
||||
id: "gpt-oss-120b",
|
||||
url: "https://api.cerebras.ai/v1",
|
||||
npm: "@ai-sdk/cerebras",
|
||||
},
|
||||
})
|
||||
|
||||
const result = ProviderTransform.maxOutputTokensForRequest({
|
||||
model,
|
||||
options: { max_completion_tokens: 64 },
|
||||
maxOutputTokens: 32_000,
|
||||
})
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
test("keeps the generic output cap when no exact value is configured", () => {
|
||||
const model = createMockModel({
|
||||
id: "cerebras/gpt-oss-120b",
|
||||
providerID: "cerebras",
|
||||
api: {
|
||||
id: "gpt-oss-120b",
|
||||
url: "https://api.cerebras.ai/v1",
|
||||
npm: "@ai-sdk/cerebras",
|
||||
},
|
||||
})
|
||||
|
||||
const result = ProviderTransform.maxOutputTokensForRequest({
|
||||
model,
|
||||
options: {},
|
||||
maxOutputTokens: 32_000,
|
||||
})
|
||||
|
||||
expect(result).toBe(32_000)
|
||||
})
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
describe("@ai-sdk/togetherai", () => {
|
||||
|
||||
Reference in New Issue
Block a user