mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 14:07:20 +08:00
Merge branch 'main' into pie-parmesan
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { Component, Show, createSignal } from "solid-js"
|
||||
import { Switch } from "@kilocode/kilo-ui/switch"
|
||||
import { TextField } from "@kilocode/kilo-ui/text-field"
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { useConfig } from "../../context/config"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import SettingsRow from "./SettingsRow"
|
||||
|
||||
const CommitMessageTab: Component = () => {
|
||||
const { config, updateConfig } = useConfig()
|
||||
const language = useLanguage()
|
||||
|
||||
const [expanded, setExpanded] = createSignal(Boolean(config().commit_message?.prompt))
|
||||
|
||||
const toggle = (checked: boolean) => {
|
||||
setExpanded(checked)
|
||||
if (!checked) {
|
||||
updateConfig({ commit_message: { prompt: "" } })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card>
|
||||
<SettingsRow
|
||||
title={language.t("settings.commitMessage.override.title")}
|
||||
description={language.t("settings.commitMessage.override.description")}
|
||||
last={!expanded()}
|
||||
>
|
||||
<Switch checked={expanded()} onChange={toggle} hideLabel>
|
||||
{language.t("settings.commitMessage.override.title")}
|
||||
</Switch>
|
||||
</SettingsRow>
|
||||
|
||||
<Show when={expanded()}>
|
||||
<div style={{ "padding-top": "8px" }}>
|
||||
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "4px" }}>
|
||||
{language.t("settings.commitMessage.prompt.title")}
|
||||
</div>
|
||||
<div data-slot="settings-row-label-subtitle" style={{ "margin-bottom": "8px" }}>
|
||||
{language.t("settings.commitMessage.prompt.description")}
|
||||
</div>
|
||||
<div style={{ "max-height": "300px", overflow: "auto" }}>
|
||||
<TextField
|
||||
value={config().commit_message?.prompt ?? ""}
|
||||
placeholder={language.t("settings.commitMessage.prompt.placeholder")}
|
||||
multiline
|
||||
onChange={(val) => {
|
||||
updateConfig({ commit_message: { prompt: val } })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CommitMessageTab
|
||||
@@ -18,6 +18,7 @@ import AutocompleteTab from "./AutocompleteTab"
|
||||
import NotificationsTab from "./NotificationsTab"
|
||||
import ContextTab from "./ContextTab"
|
||||
|
||||
import CommitMessageTab from "./CommitMessageTab"
|
||||
import ExperimentalTab from "./ExperimentalTab"
|
||||
import LanguageTab from "./LanguageTab"
|
||||
import AboutKiloCodeTab from "./AboutKiloCodeTab"
|
||||
@@ -138,6 +139,10 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||
<span class="label">{language.t("settings.context.title")}</span>
|
||||
</Tabs.Trigger>
|
||||
|
||||
<Tabs.Trigger value="commitMessage">
|
||||
<Icon name="edit" />
|
||||
<span class="label">{language.t("settings.commitMessage.title")}</span>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="experimental">
|
||||
<Icon name="settings-gear" />
|
||||
<span class="label">{language.t("settings.experimental.title")}</span>
|
||||
@@ -193,6 +198,10 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||
<ContextTab />
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="commitMessage">
|
||||
<h3>{language.t("settings.commitMessage.title")}</h3>
|
||||
<CommitMessageTab />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="experimental">
|
||||
<h3>{language.t("settings.experimental.title")}</h3>
|
||||
<ExperimentalTab />
|
||||
|
||||
@@ -29,6 +29,7 @@ export const KNOWN_KEYS: ReadonlyArray<string> = [
|
||||
"formatter",
|
||||
"lsp",
|
||||
"compaction",
|
||||
"commit_message",
|
||||
"tools",
|
||||
"layout",
|
||||
"experimental",
|
||||
|
||||
+11
@@ -1249,6 +1249,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "إزالة مخرجات الأدوات القديمة أثناء الضغط",
|
||||
"settings.context.watcherPatterns": "أنماط تجاهل مراقب الملفات",
|
||||
"settings.context.watcherPatterns.description": "أنماط glob للملفات التي يجب على المراقب تجاهلها",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "استخدام prompt مخصص",
|
||||
"settings.commitMessage.override.description":
|
||||
"تجاوز prompt الـ commit message الافتراضي. عند التفعيل، سيستبدل الـ prompt المخصص الخاص بك الـ prompt المدمج لـ conventional commits بالكامل.",
|
||||
"settings.commitMessage.prompt.title": "prompt مخصص",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"الـ prompt النظامي المرسل إلى الذكاء الاصطناعي عند إنشاء commit messages. هذا يستبدل الـ prompt الافتراضي بالكامل.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"على سبيل المثال: قم بإنشاء commit messages باللغة الإسبانية باتباع تنسيق conventional commits. أرجع الـ commit message فقط.",
|
||||
|
||||
"settings.display.username.title": "اسم المستخدم",
|
||||
"settings.display.username.description": "اسم مستخدم مخصص في المحادثات",
|
||||
"settings.display.layout.title": "التخطيط",
|
||||
|
||||
+11
@@ -1278,6 +1278,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Remover saídas antigas de ferramentas durante a compactação",
|
||||
"settings.context.watcherPatterns": "Padrões de ignorar do observador",
|
||||
"settings.context.watcherPatterns.description": "Padrões glob para arquivos que o observador deve ignorar",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Usar prompt personalizado",
|
||||
"settings.commitMessage.override.description":
|
||||
"Substituir o prompt padrão de commit message. Quando ativado, o seu prompt personalizado substitui totalmente o prompt integrado de conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "prompt personalizado",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"O prompt de sistema enviado à IA ao gerar commit messages. Isso substitui totalmente o prompt padrão.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"ex: Gere commit messages em espanhol seguindo o formato conventional commits. Retorne APENAS o commit message.",
|
||||
|
||||
"settings.display.username.title": "Nome de usuário",
|
||||
"settings.display.username.description": "Nome de usuário personalizado nas conversas",
|
||||
"settings.display.layout.title": "Layout",
|
||||
|
||||
+11
@@ -1274,6 +1274,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Ukloni stare izlaze alata tokom kompresije",
|
||||
"settings.context.watcherPatterns": "Uzorci ignoriranja za promatrač datoteka",
|
||||
"settings.context.watcherPatterns.description": "Glob uzorci za datoteke koje promatrač treba ignorirati",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Koristi prilagođeni prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"Nadjačaj podrazumijevani prompt za commit message. Kada je omogućeno, vaš prilagođeni prompt u potpunosti zamjenjuje ugrađeni prompt za conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "Prilagođeni prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"Sistemski prompt koji se šalje AI-u prilikom generisanja commit messages. Ovo u potpunosti zamjenjuje podrazumijevani prompt.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"npr. Generiši commit messages na španskom jeziku prateći conventional commits format. Vrati SAMO commit message.",
|
||||
|
||||
"settings.display.username.title": "Korisničko ime",
|
||||
"settings.display.username.description": "Prilagođeno korisničko ime u razgovorima",
|
||||
"settings.display.layout.title": "Raspored",
|
||||
|
||||
+11
@@ -1264,6 +1264,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Fjern gamle værktøjsoutput under komprimering",
|
||||
"settings.context.watcherPatterns": "Filvagt-ignormønstre",
|
||||
"settings.context.watcherPatterns.description": "Glob-mønstre for filer, som vagten skal ignorere",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Brug brugerdefineret prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"Tilsidesæt standard prompt for commit message. Når dette er aktiveret, erstatter din brugerdefinerede prompt fuldstændigt den indbyggede prompt for conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "Brugerdefineret prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"System prompt sendt til AI'en ved generering af commit messages. Dette erstatter fuldstændigt standard prompt.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"f.eks. Generer commit messages på spansk efter conventional commits formatet. Returner KUN commit message.",
|
||||
|
||||
"settings.display.username.title": "Brugernavn",
|
||||
"settings.display.username.description": "Brugerdefineret brugernavn i samtaler",
|
||||
"settings.display.layout.title": "Layout",
|
||||
|
||||
+11
@@ -1290,6 +1290,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Alte Werkzeugausgaben während der Komprimierung entfernen",
|
||||
"settings.context.watcherPatterns": "Datei-Watcher-Ignorierungsmuster",
|
||||
"settings.context.watcherPatterns.description": "Glob-Muster für Dateien, die der Watcher ignorieren soll",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Benutzerdefinierten prompt verwenden",
|
||||
"settings.commitMessage.override.description":
|
||||
"Den Standard-prompt für die commit message überschreiben. Wenn diese Option aktiviert ist, ersetzt Ihr benutzerdefinierter prompt den integrierten prompt für conventional commits vollständig.",
|
||||
"settings.commitMessage.prompt.title": "Benutzerdefinierter prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"System-prompt, der beim Generieren von commit messages an die KI gesendet wird. Dies ersetzt den Standard-prompt vollständig.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"z. B. Generiere commit messages auf Spanisch nach dem conventional commits Format. Gib NUR die commit message zurück.",
|
||||
|
||||
"settings.display.username.title": "Benutzername",
|
||||
"settings.display.username.description": "Benutzerdefinierter Benutzername in Gesprächen",
|
||||
"settings.display.layout.title": "Layout",
|
||||
|
||||
@@ -1266,6 +1266,16 @@ export const dict = {
|
||||
"settings.context.watcherPatterns": "File Watcher Ignore Patterns",
|
||||
"settings.context.watcherPatterns.description": "Glob patterns for files the watcher should ignore",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Use Custom Prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"Override the default commit message prompt. When enabled, your custom prompt fully replaces the built-in conventional commits prompt.",
|
||||
"settings.commitMessage.prompt.title": "Custom Prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"System prompt sent to the AI when generating commit messages. This replaces the default prompt entirely.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"e.g. Generate commit messages in Spanish following conventional commits format. Return ONLY the commit message.",
|
||||
|
||||
"settings.display.username.title": "Username",
|
||||
"settings.display.username.description": "Custom username displayed in conversations",
|
||||
"settings.display.layout.title": "Layout",
|
||||
|
||||
+11
@@ -1282,6 +1282,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Eliminar salidas de herramientas antiguas durante la compactación",
|
||||
"settings.context.watcherPatterns": "Patrones de ignorar del observador",
|
||||
"settings.context.watcherPatterns.description": "Patrones glob para archivos que el observador debe ignorar",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Usar prompt personalizado",
|
||||
"settings.commitMessage.override.description":
|
||||
"Sobrescribir el prompt por defecto para el commit message. Cuando está habilitado, tu prompt personalizado reemplaza completamente el prompt integrado para conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "prompt personalizado",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"prompt del sistema enviado a la IA al generar commit messages. Esto reemplaza el prompt por defecto por completo.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"ej. Genera commit messages en español siguiendo el formato conventional commits. Devuelve SOLO el commit message.",
|
||||
|
||||
"settings.display.username.title": "Nombre de usuario",
|
||||
"settings.display.username.description": "Nombre de usuario personalizado en conversaciones",
|
||||
"settings.display.layout.title": "Diseño",
|
||||
|
||||
+11
@@ -1295,6 +1295,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Supprimer les anciennes sorties d'outils pendant la compaction",
|
||||
"settings.context.watcherPatterns": "Motifs d'ignorance de l'observateur",
|
||||
"settings.context.watcherPatterns.description": "Motifs glob pour les fichiers que l'observateur doit ignorer",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Utiliser un prompt personnalisé",
|
||||
"settings.commitMessage.override.description":
|
||||
"Remplacer le prompt par défaut pour le commit message. Lorsque cette option est activée, votre prompt personnalisé remplace entièrement le prompt intégré pour les conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "prompt personnalisé",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"prompt système envoyé à l'IA lors de la génération des commit messages. Cela remplace entièrement le prompt par défaut.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"par ex. Générer des commit messages en espagnol en suivant le format conventional commits. Retourner UNIQUEMENT le commit message.",
|
||||
|
||||
"settings.display.username.title": "Nom d'utilisateur",
|
||||
"settings.display.username.description": "Nom d'utilisateur personnalisé dans les conversations",
|
||||
"settings.display.layout.title": "Disposition",
|
||||
|
||||
+11
@@ -1265,6 +1265,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "圧縮時に古いツール出力を削除",
|
||||
"settings.context.watcherPatterns": "ファイルウォッチャー無視パターン",
|
||||
"settings.context.watcherPatterns.description": "ウォッチャーが無視すべきファイルのglobパターン",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "カスタム prompt を使用",
|
||||
"settings.commitMessage.override.description":
|
||||
"デフォルトの commit message の prompt を上書きします。有効にすると、カスタム prompt が組み込みの conventional commits の prompt を完全に置き換えます。",
|
||||
"settings.commitMessage.prompt.title": "カスタム prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"commit messages を生成する際に AI に送信されるシステム prompt。これはデフォルトの prompt を完全に置き換えます。",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"例: スペイン語で conventional commits 形式に従って commit messages を生成して。commit message のみを返して。",
|
||||
|
||||
"settings.display.username.title": "ユーザー名",
|
||||
"settings.display.username.description": "会話に表示されるカスタムユーザー名",
|
||||
"settings.display.layout.title": "レイアウト",
|
||||
|
||||
+11
@@ -1253,6 +1253,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "압축 중 이전 도구 출력 제거",
|
||||
"settings.context.watcherPatterns": "파일 감시자 무시 패턴",
|
||||
"settings.context.watcherPatterns.description": "감시자가 무시해야 할 파일의 글로브 패턴",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "사용자 지정 prompt 사용",
|
||||
"settings.commitMessage.override.description":
|
||||
"기본 commit message의 prompt를 재정의합니다. 활성화되면 사용자 지정 prompt가 기본 제공되는 conventional commits의 prompt를 완전히 대체합니다.",
|
||||
"settings.commitMessage.prompt.title": "사용자 지정 prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"commit messages를 생성할 때 AI로 전송되는 시스템 prompt입니다. 이는 기본 prompt를 완전히 대체합니다.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"예: 스페인어로 conventional commits 형식을 따라 commit messages를 생성해줘. 오직 commit message만 반환할 것.",
|
||||
|
||||
"settings.display.username.title": "사용자 이름",
|
||||
"settings.display.username.description": "대화에 표시되는 사용자 정의 사용자 이름",
|
||||
"settings.display.layout.title": "레이아웃",
|
||||
|
||||
+10
@@ -1248,6 +1248,16 @@ export const dict = {
|
||||
"settings.context.watcherPatterns": "File Watcher Negeer Patronen",
|
||||
"settings.context.watcherPatterns.description": "Glob-patronen voor bestanden die de watcher moet negeren",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Aangepaste prompt gebruiken",
|
||||
"settings.commitMessage.override.description":
|
||||
"Overschrijf de standaard prompt voor de commit message. Indien ingeschakeld, vervangt uw aangepaste prompt de ingebouwde prompt voor conventional commits volledig.",
|
||||
"settings.commitMessage.prompt.title": "Aangepaste prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"Systeem prompt die naar de AI wordt gestuurd bij het genereren van commit messages. Dit vervangt de standaard prompt volledig.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"bijv. Genereer commit messages in het Spaans volgens het conventional commits formaat. Retourneer ALLEEN de commit message.",
|
||||
|
||||
"settings.display.username.title": "Gebruikersnaam",
|
||||
"settings.display.username.description": "Aangepaste gebruikersnaam weergegeven in gesprekken",
|
||||
"settings.display.layout.title": "Lay-out",
|
||||
|
||||
+11
@@ -1266,6 +1266,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Fjern gamle verktøyutdata under komprimering",
|
||||
"settings.context.watcherPatterns": "Filvakt-ignormønstre",
|
||||
"settings.context.watcherPatterns.description": "Glob-mønstre for filer som vakten skal ignorere",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Bruk egendefinert prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"Overstyr standard prompt for commit message. Når aktivert, erstatter din egendefinerte prompt fullstendig den innebygde prompten for conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "Egendefinert prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"System prompt sendt til AI-en ved generering av commit messages. Dette erstatter standard prompt fullstendig.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"f.eks. Generer commit messages på spansk i henhold til conventional commits-formatet. Returner KUN commit message.",
|
||||
|
||||
"settings.display.username.title": "Brukernavn",
|
||||
"settings.display.username.description": "Egendefinert brukernavn i samtaler",
|
||||
"settings.display.layout.title": "Layout",
|
||||
|
||||
+11
@@ -1274,6 +1274,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Usuń stare wyjścia narzędzi podczas kompakcji",
|
||||
"settings.context.watcherPatterns": "Wzorce ignorowania obserwatora plików",
|
||||
"settings.context.watcherPatterns.description": "Wzorce glob dla plików do ignorowania",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Użyj niestandardowego prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"Zastąp domyślny prompt dla commit message. Po włączeniu, Twój niestandardowy prompt całkowicie zastępuje wbudowany prompt dla conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "Niestandardowy prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"Systemowy prompt wysyłany do AI podczas generowania commit messages. Zastępuje to całkowicie domyślny prompt.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"np. Generuj commit messages w języku hiszpańskim zgodnie z formatem conventional commits. Zwróć TYLKO commit message.",
|
||||
|
||||
"settings.display.username.title": "Nazwa użytkownika",
|
||||
"settings.display.username.description": "Niestandardowa nazwa użytkownika w rozmowach",
|
||||
"settings.display.layout.title": "Układ",
|
||||
|
||||
+11
@@ -1273,6 +1273,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "Удалить старые выходные данные инструментов при сжатии",
|
||||
"settings.context.watcherPatterns": "Шаблоны игнорирования наблюдателя файлов",
|
||||
"settings.context.watcherPatterns.description": "Glob-шаблоны для файлов, которые наблюдатель должен игнорировать",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Использовать пользовательский prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"Переопределить prompt по умолчанию для commit message. Если включено, ваш пользовательский prompt полностью заменяет встроенный prompt для conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "Пользовательский prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"Системный prompt, отправляемый ИИ при генерации commit messages. Это полностью заменяет prompt по умолчанию.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"напр., Сгенерируй commit messages на испанском языке в формате conventional commits. Верни ТОЛЬКО commit message.",
|
||||
|
||||
"settings.display.username.title": "Имя пользователя",
|
||||
"settings.display.username.description": "Пользовательское имя в разговорах",
|
||||
"settings.display.layout.title": "Макет",
|
||||
|
||||
+11
@@ -1249,6 +1249,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "ลบผลลัพธ์เครื่องมือเก่าระหว่างการบีบอัด",
|
||||
"settings.context.watcherPatterns": "รูปแบบการละเว้นตัวเฝ้าดูไฟล์",
|
||||
"settings.context.watcherPatterns.description": "รูปแบบ glob สำหรับไฟล์ที่ตัวเฝ้าดูควรละเว้น",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "ใช้ prompt แบบกำหนดเอง",
|
||||
"settings.commitMessage.override.description":
|
||||
"แทนที่ prompt เริ่มต้นของ commit message เมื่อเปิดใช้งาน prompt แบบกำหนดเองของคุณจะแทนที่ prompt เริ่มต้นของ conventional commits ทั้งหมด",
|
||||
"settings.commitMessage.prompt.title": "prompt แบบกำหนดเอง",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"System prompt ที่ส่งไปยัง AI เมื่อสร้าง commit messages สิ่งนี้จะแทนที่ prompt เริ่มต้นทั้งหมด",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"เช่น สร้าง commit messages เป็นภาษาสเปนตามรูปแบบ conventional commits คืนค่าเฉพาะ commit message เท่านั้น",
|
||||
|
||||
"settings.display.username.title": "ชื่อผู้ใช้",
|
||||
"settings.display.username.description": "ชื่อผู้ใช้กำหนดเองในบทสนทนา",
|
||||
"settings.display.layout.title": "เค้าโครง",
|
||||
|
||||
+10
@@ -1241,6 +1241,16 @@ export const dict = {
|
||||
"settings.context.watcherPatterns": "Dosya İzleyici Yok Sayma Kalıpları",
|
||||
"settings.context.watcherPatterns.description": "İzleyicinin yok sayması gereken dosyalar için glob kalıpları",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Özel prompt Kullan",
|
||||
"settings.commitMessage.override.description":
|
||||
"Varsayılan commit message için olan prompt değerini geçersiz kıl. Etkinleştirildiğinde, özel prompt değeriniz yerleşik conventional commits için olan prompt değerini tamamen değiştirir.",
|
||||
"settings.commitMessage.prompt.title": "Özel prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"commit messages oluşturulurken yapay zekaya gönderilen sistem prompt'u. Bu, varsayılan prompt'un tamamen yerini alır.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"örn. conventional commits formatını izleyerek İspanyolca commit messages oluştur. SADECE commit message döndür.",
|
||||
|
||||
"settings.display.username.title": "Kullanıcı Adı",
|
||||
"settings.display.username.description": "Sohbetlerde görüntülenen özel kullanıcı adı",
|
||||
"settings.display.layout.title": "Düzen",
|
||||
|
||||
+10
@@ -1243,6 +1243,16 @@ export const dict = {
|
||||
"settings.context.watcherPatterns": "Шаблони ігнорування спостерігача файлів",
|
||||
"settings.context.watcherPatterns.description": "Glob-шаблони для файлів, які спостерігач має ігнорувати",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "Використовувати власний prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"Перевизначити prompt за замовчуванням для commit message. Якщо ввімкнено, ваш власний prompt повністю замінює вбудований prompt для conventional commits.",
|
||||
"settings.commitMessage.prompt.title": "Власний prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"Системний prompt, що надсилається ШІ під час генерації commit messages. Це повністю замінює prompt за замовчуванням.",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"напр., Згенеруй commit messages іспанською мовою у форматі conventional commits. Поверни ЛИШЕ commit message.",
|
||||
|
||||
"settings.display.username.title": "Ім'я користувача",
|
||||
"settings.display.username.description": "Власне ім'я користувача, що відображається в чатах",
|
||||
"settings.display.layout.title": "Макет",
|
||||
|
||||
+11
@@ -1225,6 +1225,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "压缩期间移除旧的工具输出",
|
||||
"settings.context.watcherPatterns": "文件监视器忽略模式",
|
||||
"settings.context.watcherPatterns.description": "监视器应忽略的文件的 glob 模式",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "使用自定义 prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"覆盖默认的 commit message 的 prompt。启用后,你的自定义 prompt 将完全替换内置的 conventional commits 的 prompt。",
|
||||
"settings.commitMessage.prompt.title": "自定义 prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"在生成 commit messages 时发送给 AI 的系统 prompt。这将完全替换默认的 prompt。",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"例如:按照 conventional commits 格式用西班牙语生成 commit messages。只返回 commit message。",
|
||||
|
||||
"settings.display.username.title": "用户名",
|
||||
"settings.display.username.description": "对话中显示的自定义用户名",
|
||||
"settings.display.layout.title": "布局",
|
||||
|
||||
+11
@@ -1229,6 +1229,17 @@ export const dict = {
|
||||
"settings.context.prune.description": "壓縮期間移除舊的工具輸出",
|
||||
"settings.context.watcherPatterns": "檔案監視器忽略模式",
|
||||
"settings.context.watcherPatterns.description": "監視器應忽略的檔案的 glob 模式",
|
||||
|
||||
"settings.commitMessage.title": "Commit Message",
|
||||
"settings.commitMessage.override.title": "使用自訂 prompt",
|
||||
"settings.commitMessage.override.description":
|
||||
"覆寫預設的 commit message 的 prompt。啟用後,您的自訂 prompt 將完全取代內建的 conventional commits 的 prompt。",
|
||||
"settings.commitMessage.prompt.title": "自訂 prompt",
|
||||
"settings.commitMessage.prompt.description":
|
||||
"在產生 commit messages 時發送給 AI 的系統 prompt。這將完全取代預設的 prompt。",
|
||||
"settings.commitMessage.prompt.placeholder":
|
||||
"例如:按照 conventional commits 格式用西班牙語產生 commit messages。只回傳 commit message。",
|
||||
|
||||
"settings.display.username.title": "使用者名稱",
|
||||
"settings.display.username.description": "對話中顯示的自訂使用者名稱",
|
||||
"settings.display.layout.title": "佈局",
|
||||
|
||||
@@ -439,6 +439,10 @@ export interface ExperimentalConfig {
|
||||
mcp_timeout?: number
|
||||
}
|
||||
|
||||
export interface CommitMessageConfig {
|
||||
prompt?: string
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
permission?: PermissionConfig
|
||||
model?: string | null
|
||||
@@ -460,6 +464,7 @@ export interface Config {
|
||||
formatter?: false | Record<string, unknown>
|
||||
lsp?: false | Record<string, unknown>
|
||||
compaction?: CompactionConfig
|
||||
commit_message?: CommitMessageConfig
|
||||
tools?: Record<string, boolean>
|
||||
layout?: "auto" | "stretch"
|
||||
experimental?: ExperimentalConfig
|
||||
|
||||
@@ -107,15 +107,18 @@
|
||||
"@hono/node-ws": "1.3.0",
|
||||
"@hono/standard-validator": "0.1.5",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"@kilocode/kilo-telemetry": "workspace:*",
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@morphllm/morphsdk": "0.2.148",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@octokit/graphql": "9.0.2",
|
||||
"@octokit/rest": "catalog:",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "2.5.1",
|
||||
"@opentui/core": "0.1.97",
|
||||
@@ -155,8 +158,11 @@
|
||||
"opentui-spinner": "0.0.6",
|
||||
"partial-json": "0.1.7",
|
||||
"remeda": "catalog:",
|
||||
"rotating-file-stream": "3.2.9",
|
||||
"semver": "^7.6.3",
|
||||
"simple-git": "3.35.2",
|
||||
"solid-js": "catalog:",
|
||||
"stream-chat": "9.38.0",
|
||||
"strip-ansi": "7.1.2",
|
||||
"tree-sitter-bash": "0.25.0",
|
||||
"tree-sitter-powershell": "0.25.10",
|
||||
@@ -169,13 +175,7 @@
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
"zod": "catalog:",
|
||||
"zod-to-json-schema": "3.24.5",
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"@kilocode/kilo-telemetry": "workspace:*",
|
||||
"@morphllm/morphsdk": "0.2.148",
|
||||
"rotating-file-stream": "3.2.9",
|
||||
"simple-git": "3.35.2",
|
||||
"stream-chat": "9.38.0"
|
||||
"zod-to-json-schema": "3.24.5"
|
||||
},
|
||||
"overrides": {
|
||||
"drizzle-orm": "catalog:"
|
||||
|
||||
@@ -1162,6 +1162,7 @@ export namespace Config {
|
||||
url: z.string().optional().describe("Enterprise URL"),
|
||||
})
|
||||
.optional(),
|
||||
commit_message: KilocodeConfig.CommitMessageSchema, // kilocode_change
|
||||
compaction: z
|
||||
.object({
|
||||
auto: z.boolean().optional().describe("Enable automatic compaction when context is full (default: true)"),
|
||||
|
||||
+1
-1
@@ -137,7 +137,7 @@ export async function generateCommitMessage(request: CommitMessageRequest): Prom
|
||||
hidden: true,
|
||||
options: {},
|
||||
permission: [],
|
||||
prompt: SYSTEM_PROMPT,
|
||||
prompt: request.prompt || SYSTEM_PROMPT,
|
||||
temperature: 0.3,
|
||||
}
|
||||
|
||||
+2
@@ -5,6 +5,8 @@ export interface CommitMessageRequest {
|
||||
selectedFiles?: string[]
|
||||
/** Previously generated message — when set, the LLM is asked to produce a different one */
|
||||
previousMessage?: string
|
||||
/** Optional custom system prompt — overrides the default conventional commits prompt */
|
||||
prompt?: string
|
||||
}
|
||||
|
||||
export interface CommitMessageResponse {
|
||||
@@ -23,6 +23,21 @@ import { IgnoreMigrator } from "../ignore-migrator"
|
||||
export namespace KilocodeConfig {
|
||||
const log = Log.create({ service: "kilocode.config" })
|
||||
|
||||
// ── Config schema extensions ─────────────────────────────────────────
|
||||
|
||||
/** Schema for AI-generated commit message configuration. */
|
||||
export const CommitMessageSchema = z
|
||||
.object({
|
||||
prompt: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Custom system prompt for AI commit message generation. When set, replaces the default conventional commits prompt entirely.",
|
||||
),
|
||||
})
|
||||
.optional()
|
||||
.describe("Configuration for AI-generated commit messages")
|
||||
|
||||
// ── Config file constants ────────────────────────────────────────────
|
||||
|
||||
/** Kilo-specific config file names (highest-to-lowest precedence within kilo). */
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Hono } from "hono"
|
||||
import { describeRoute, validator, resolver } from "hono-openapi"
|
||||
import z from "zod"
|
||||
import { TelemetryRoutes } from "../../server/routes/telemetry"
|
||||
import { CommitMessageRoutes } from "../../server/routes/commit-message"
|
||||
import { CommitMessageRoutes } from "./routes/commit-message"
|
||||
import { EnhancePromptRoutes } from "../../server/routes/enhance-prompt"
|
||||
import { KilocodeRoutes } from "../../server/routes/kilocode"
|
||||
import { PermissionKilocodeRoutes } from "../permission/routes"
|
||||
|
||||
+6
-3
@@ -2,8 +2,9 @@ import { Hono } from "hono"
|
||||
import { describeRoute, resolver, validator } from "hono-openapi"
|
||||
import z from "zod"
|
||||
import { generateCommitMessage } from "../../commit-message"
|
||||
import { lazy } from "../../util/lazy"
|
||||
import { errors } from "../error"
|
||||
import { Config } from "../../../config/config"
|
||||
import { lazy } from "../../../util/lazy"
|
||||
import { errors } from "../../../server/error"
|
||||
|
||||
export const CommitMessageRoutes = lazy(() =>
|
||||
new Hono().post(
|
||||
@@ -37,7 +38,9 @@ export const CommitMessageRoutes = lazy(() =>
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json")
|
||||
const result = await generateCommitMessage(body)
|
||||
const config = await Config.get()
|
||||
const prompt = config.commit_message?.prompt || undefined
|
||||
const result = await generateCommitMessage({ ...body, prompt })
|
||||
return c.json({ message: result.message })
|
||||
},
|
||||
),
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Session } from "@/session"
|
||||
import { MessageV2 } from "@/session/message-v2"
|
||||
import { SessionID, PartID } from "@/session/schema"
|
||||
import { Log } from "@/util/log"
|
||||
|
||||
const log = Log.create({ service: "session.fork" })
|
||||
|
||||
/**
|
||||
* Extracts the child session ID from a task tool part.
|
||||
*/
|
||||
function childID(part: MessageV2.Part): string | undefined {
|
||||
if (part.type !== "tool" || part.tool !== "task") return undefined
|
||||
return (part.state as { metadata?: { sessionId?: string } }).metadata?.sessionId
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively fork all child (subagent) sessions referenced by task tool parts
|
||||
* in the given session, then update the parts to point at the forked copies.
|
||||
*
|
||||
* This prevents subagent state from leaking between forked sessions in the
|
||||
* same worktree: without remapping, two forked sessions would share the same
|
||||
* child session references, causing SSE events and permission prompts to bleed
|
||||
* across sessions.
|
||||
*/
|
||||
export async function remapChildren(sid: SessionID): Promise<void> {
|
||||
const msgs = await Session.messages({ sessionID: sid })
|
||||
const refs: { part: MessageV2.ToolPart; child: string }[] = []
|
||||
for (const msg of msgs) {
|
||||
for (const part of msg.parts) {
|
||||
const child = childID(part)
|
||||
if (child) refs.push({ part: part as MessageV2.ToolPart, child })
|
||||
}
|
||||
}
|
||||
if (refs.length === 0) return
|
||||
|
||||
const remapped = new Map<string, SessionID>()
|
||||
for (const ref of refs) {
|
||||
if (remapped.has(ref.child)) continue
|
||||
const exists = await Session.get(SessionID.make(ref.child)).catch(() => undefined)
|
||||
if (!exists) continue
|
||||
// Session.fork() already calls remapChildren on the forked child,
|
||||
// so nested subagents are handled recursively without an explicit call here.
|
||||
const forked = await Session.fork({ sessionID: SessionID.make(ref.child) })
|
||||
remapped.set(ref.child, forked.id)
|
||||
}
|
||||
|
||||
if (remapped.size === 0) return
|
||||
|
||||
for (const ref of refs) {
|
||||
const replacement = remapped.get(ref.child)
|
||||
if (!replacement) continue
|
||||
const meta = (ref.part.state as { metadata?: Record<string, unknown> }).metadata
|
||||
if (!meta) continue
|
||||
await Session.updatePart({
|
||||
...ref.part,
|
||||
id: PartID.make(ref.part.id),
|
||||
sessionID: SessionID.make(ref.part.sessionID),
|
||||
state: {
|
||||
...ref.part.state,
|
||||
metadata: { ...meta, sessionId: replacement },
|
||||
},
|
||||
} as MessageV2.ToolPart)
|
||||
}
|
||||
|
||||
log.info("remapped child sessions", { session: sid, count: remapped.size })
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
// kilocode_change - new file
|
||||
import { remapChildren as _remapChildren } from "./fork"
|
||||
import z from "zod"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { Session } from "@/session"
|
||||
import { MessageID, SessionID } from "@/session/schema"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { fn } from "@/util/fn"
|
||||
import { Database, eq, and, gte, isNull, desc, like, inArray, lt, or } from "@/storage/db"
|
||||
import type { SQL } from "@/storage/db"
|
||||
import { ProjectTable } from "@/project/project.sql"
|
||||
@@ -282,4 +287,16 @@ export namespace KiloSession {
|
||||
yield { ...input.fromRow(row), project } as T & { project: ProjectInfo | null }
|
||||
}
|
||||
}
|
||||
|
||||
export const remapChildren = _remapChildren
|
||||
}
|
||||
|
||||
export const kiloSessionFork = fn(
|
||||
z.object({ sessionID: SessionID.zod, messageID: MessageID.zod.optional() }),
|
||||
async (input) => {
|
||||
const { runPromise } = makeRuntime(Session.Service, Session.defaultLayer)
|
||||
const session = await runPromise((svc) => svc.fork(input))
|
||||
await KiloSession.remapChildren(session.id)
|
||||
return session
|
||||
},
|
||||
)
|
||||
|
||||
@@ -174,6 +174,20 @@ export namespace Suggestion {
|
||||
}
|
||||
}
|
||||
|
||||
export async function dismissAll(sessionID: string): Promise<void> {
|
||||
const s = await state()
|
||||
for (const [id, entry] of Object.entries(s.pending)) {
|
||||
if (entry.info.sessionID !== sessionID) continue
|
||||
delete s.pending[id]
|
||||
log.info("dismissed", { requestID: id })
|
||||
Bus.publish(Event.Dismissed, {
|
||||
sessionID: entry.info.sessionID,
|
||||
requestID: entry.info.id,
|
||||
})
|
||||
entry.reject(new DismissedError())
|
||||
}
|
||||
}
|
||||
|
||||
export async function list() {
|
||||
return state().then((state) => Object.values(state.pending).map((item) => item.info))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import { Snapshot } from "@/snapshot"
|
||||
import { ProjectID } from "../project/schema"
|
||||
import { WorkspaceID } from "../control-plane/schema"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { KiloSession } from "@/kilocode/session" // kilocode_change
|
||||
import { KiloSession, kiloSessionFork } from "@/kilocode/session" // kilocode_change
|
||||
|
||||
import type { Provider } from "@/provider/provider"
|
||||
import { Permission } from "@/permission"
|
||||
@@ -745,9 +745,7 @@ export namespace Session {
|
||||
(input) => runPromise((svc) => svc.create(input)),
|
||||
)
|
||||
|
||||
export const fork = fn(z.object({ sessionID: SessionID.zod, messageID: MessageID.zod.optional() }), (input) =>
|
||||
runPromise((svc) => svc.fork(input)),
|
||||
)
|
||||
export const fork = kiloSessionFork // kilocode_change
|
||||
|
||||
export const get = fn(SessionID.zod, (id) => runPromise((svc) => svc.get(id)))
|
||||
export const share = fn(SessionID.zod, (id) => runPromise((svc) => svc.share(id)))
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from "os"
|
||||
import fs from "fs/promises"
|
||||
import { KiloSessionPrompt } from "@/kilocode/session/prompt" // kilocode_change
|
||||
import { KiloSession } from "@/kilocode/session" // kilocode_change
|
||||
import { Suggestion } from "@/kilocode/suggestion" // kilocode_change
|
||||
import z from "zod"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
@@ -1287,6 +1288,11 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
}
|
||||
|
||||
if (input.noReply === true) return message
|
||||
// kilocode_change start — dismiss pending suggestions and cancel the session
|
||||
// before starting a new loop to avoid the runner ignoring the new work
|
||||
yield* Effect.promise(() => Suggestion.dismissAll(input.sessionID))
|
||||
yield* state.cancel(input.sessionID)
|
||||
// kilocode_change end
|
||||
return yield* loop({ sessionID: input.sessionID })
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, spyOn, test } from "bun:test"
|
||||
import { getGitContext } from "../../src/commit-message/git-context"
|
||||
import { getGitContext } from "../../src/kilocode/commit-message/git-context"
|
||||
|
||||
describe("commit-message git context", () => {
|
||||
test("hides Windows console windows for git subprocesses", async () => {
|
||||
|
||||
+50
-41
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test, mock, beforeEach } from "bun:test"
|
||||
import type { GitContext } from "../types"
|
||||
import type { GitContext } from "@/kilocode/commit-message/types"
|
||||
|
||||
// Mock dependencies before importing the module under test.
|
||||
// IMPORTANT: Bun's mock.module() is process-wide and permanent. To avoid
|
||||
@@ -10,19 +10,33 @@ const realLog = await import("@/util/log")
|
||||
const realProvider = await import("@/provider/provider")
|
||||
const realLLM = await import("@/session/llm")
|
||||
const realAgent = await import("@/agent/agent")
|
||||
|
||||
let mockGitContext: GitContext = {
|
||||
branch: "main",
|
||||
recentCommits: ["abc1234 initial commit"],
|
||||
files: [{ status: "modified" as const, path: "src/index.ts", diff: "+console.log('hello')" }],
|
||||
}
|
||||
|
||||
mock.module("../git-context", () => ({
|
||||
getGitContext: async () => mockGitContext,
|
||||
}))
|
||||
const realGitContext = await import("@/kilocode/commit-message/git-context")
|
||||
|
||||
let mockStreamText = "feat(src): add hello world logging"
|
||||
|
||||
const defaultGitContext: GitContext = {
|
||||
branch: "main",
|
||||
recentCommits: ["abc1234 initial commit"],
|
||||
files: [
|
||||
{
|
||||
status: "modified",
|
||||
path: "src/index.ts",
|
||||
diff: "+console.log('hello')",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let mockGitContext: GitContext = { ...defaultGitContext }
|
||||
let captured: { path: string; selected?: string[] } = { path: "" }
|
||||
|
||||
mock.module("@/kilocode/commit-message/git-context", () => ({
|
||||
...realGitContext,
|
||||
getGitContext: async (repoPath: string, selectedFiles?: string[]) => {
|
||||
captured = { path: repoPath, selected: selectedFiles }
|
||||
return mockGitContext
|
||||
},
|
||||
}))
|
||||
|
||||
mock.module("@/provider/provider", () => ({
|
||||
...realProvider,
|
||||
Provider: {
|
||||
@@ -36,7 +50,6 @@ mock.module("@/provider/provider", () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
// kilocode_change start — upstream switched from stream.text to stream.textStream
|
||||
mock.module("@/session/llm", () => ({
|
||||
...realLLM,
|
||||
LLM: {
|
||||
@@ -49,7 +62,6 @@ mock.module("@/session/llm", () => ({
|
||||
}),
|
||||
},
|
||||
}))
|
||||
// kilocode_change end
|
||||
|
||||
mock.module("@/agent/agent", () => ({
|
||||
...realAgent,
|
||||
@@ -69,36 +81,32 @@ mock.module("@/util/log", () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
import { generateCommitMessage } from "../generate"
|
||||
import { generateCommitMessage } from "../../../src/kilocode/commit-message/generate"
|
||||
|
||||
describe("commit-message.generate", () => {
|
||||
beforeEach(() => {
|
||||
mockGitContext = {
|
||||
branch: "main",
|
||||
recentCommits: ["abc1234 initial commit"],
|
||||
files: [{ status: "modified" as const, path: "src/index.ts", diff: "+console.log('hello')" }],
|
||||
}
|
||||
mockStreamText = "feat(src): add hello world logging"
|
||||
mockGitContext = { ...defaultGitContext }
|
||||
captured = { path: "" }
|
||||
})
|
||||
|
||||
describe("prompt construction", () => {
|
||||
test("passes path to getGitContext", async () => {
|
||||
const result = await generateCommitMessage({ path: "/my/repo" })
|
||||
// If getGitContext is called, it returns our mock context and generates a message
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBeTruthy()
|
||||
expect(captured.path).toBe("/repo")
|
||||
})
|
||||
|
||||
test("generates message from git context with multiple files", async () => {
|
||||
mockStreamText = "feat(api): add api module"
|
||||
mockGitContext = {
|
||||
branch: "feature/api",
|
||||
recentCommits: ["abc feat: add api", "def fix: typo"],
|
||||
branch: "main",
|
||||
recentCommits: ["abc1234 initial commit"],
|
||||
files: [
|
||||
{ status: "added" as const, path: "src/api.ts", diff: "+export function api() {}" },
|
||||
{ status: "modified" as const, path: "src/index.ts", diff: "+import { api } from './api'" },
|
||||
{ status: "added", path: "src/api.ts", diff: "+export function api() {}" },
|
||||
{ status: "modified", path: "src/index.ts", diff: "+import { api } from './api'" },
|
||||
],
|
||||
}
|
||||
mockStreamText = "feat(api): add api module"
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("feat(api): add api module")
|
||||
})
|
||||
@@ -107,49 +115,42 @@ describe("commit-message.generate", () => {
|
||||
describe("response cleaning", () => {
|
||||
test("strips code block markers from response", async () => {
|
||||
mockStreamText = "```\nfeat: add feature\n```"
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("feat: add feature")
|
||||
})
|
||||
|
||||
test("strips code block markers with language tag", async () => {
|
||||
mockStreamText = "```text\nfix(auth): resolve token refresh\n```"
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("fix(auth): resolve token refresh")
|
||||
})
|
||||
|
||||
test("strips surrounding double quotes", async () => {
|
||||
mockStreamText = '"feat: add new feature"'
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("feat: add new feature")
|
||||
})
|
||||
|
||||
test("strips surrounding single quotes", async () => {
|
||||
mockStreamText = "'fix: resolve bug'"
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("fix: resolve bug")
|
||||
})
|
||||
|
||||
test("strips whitespace around the message", async () => {
|
||||
mockStreamText = " \n chore: update deps \n "
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("chore: update deps")
|
||||
})
|
||||
|
||||
test("strips code blocks AND quotes together", async () => {
|
||||
mockStreamText = '```\n"refactor: simplify logic"\n```'
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("refactor: simplify logic")
|
||||
})
|
||||
|
||||
test("returns clean message when no markers present", async () => {
|
||||
mockStreamText = "docs: update readme"
|
||||
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBe("docs: update readme")
|
||||
})
|
||||
@@ -157,12 +158,7 @@ describe("commit-message.generate", () => {
|
||||
|
||||
describe("error on no changes", () => {
|
||||
test("throws when no git changes are found", async () => {
|
||||
mockGitContext = {
|
||||
branch: "main",
|
||||
recentCommits: [],
|
||||
files: [],
|
||||
}
|
||||
|
||||
mockGitContext = { branch: "main", recentCommits: [], files: [] }
|
||||
await expect(generateCommitMessage({ path: "/repo" })).rejects.toThrow(
|
||||
"No changes found to generate a commit message for",
|
||||
)
|
||||
@@ -171,12 +167,25 @@ describe("commit-message.generate", () => {
|
||||
|
||||
describe("selectedFiles pass-through", () => {
|
||||
test("passes selectedFiles to getGitContext", async () => {
|
||||
// This verifies the function doesn't crash when selectedFiles is provided
|
||||
const result = await generateCommitMessage({
|
||||
path: "/repo",
|
||||
selectedFiles: ["src/a.ts"],
|
||||
})
|
||||
expect(result.message).toBeTruthy()
|
||||
expect(captured.path).toBe("/repo")
|
||||
expect(captured.selected).toEqual(["src/a.ts"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("custom prompt", () => {
|
||||
test("uses default prompt when no custom prompt provided", async () => {
|
||||
const result = await generateCommitMessage({ path: "/repo" })
|
||||
expect(result.message).toBeTruthy()
|
||||
})
|
||||
|
||||
test("uses custom prompt when provided", async () => {
|
||||
const result = await generateCommitMessage({ path: "/repo", prompt: "Write a haiku commit message." })
|
||||
expect(result.message).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -16,7 +16,7 @@ function clearGitOutputs() {
|
||||
|
||||
// Override the git-context module with a version that uses our mock spawnSync.
|
||||
// This avoids conflicts with generate.test.ts which also mocks this module.
|
||||
mock.module("../git-context", () => {
|
||||
mock.module("../../../src/kilocode/commit-message/git-context", () => {
|
||||
function git(args: string[], cwd: string): string {
|
||||
const key = args.join(" ")
|
||||
return spawnSyncResults[key] ?? ""
|
||||
@@ -180,7 +180,7 @@ mock.module("../git-context", () => {
|
||||
return { getGitContext }
|
||||
})
|
||||
|
||||
import { getGitContext } from "../git-context"
|
||||
import { getGitContext } from "../../../src/kilocode/commit-message/git-context"
|
||||
|
||||
describe("commit-message.git-context", () => {
|
||||
beforeEach(() => {
|
||||
@@ -0,0 +1,251 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { Session } from "../../src/session"
|
||||
import { MessageV2 } from "../../src/session/message-v2"
|
||||
import { MessageID, PartID, SessionID } from "../../src/session/schema"
|
||||
import { Log } from "../../src/util/log"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
Log.init({ print: false })
|
||||
|
||||
afterEach(async () => {
|
||||
await Instance.disposeAll()
|
||||
})
|
||||
|
||||
function taskPart(input: { messageID: string; sessionID: string; childSessionID: string }): MessageV2.ToolPart {
|
||||
return {
|
||||
id: PartID.ascending(),
|
||||
messageID: MessageID.make(input.messageID),
|
||||
sessionID: SessionID.make(input.sessionID),
|
||||
type: "tool",
|
||||
callID: "call_1",
|
||||
tool: "task",
|
||||
state: {
|
||||
status: "completed",
|
||||
input: { description: "test task", prompt: "do something" },
|
||||
output: `task_id: ${input.childSessionID}`,
|
||||
title: "test task",
|
||||
metadata: {
|
||||
sessionId: input.childSessionID,
|
||||
model: { modelID: "test", providerID: "test" },
|
||||
},
|
||||
time: { start: Date.now(), end: Date.now() },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function userMsg(sid: string) {
|
||||
const id = MessageID.ascending()
|
||||
await Session.updateMessage({
|
||||
id,
|
||||
sessionID: SessionID.make(sid),
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: "test",
|
||||
model: { providerID: "test", modelID: "test" },
|
||||
tools: {},
|
||||
} as MessageV2.User)
|
||||
return id
|
||||
}
|
||||
|
||||
async function asstMsg(sid: string, parent: string) {
|
||||
const id = MessageID.ascending()
|
||||
await Session.updateMessage({
|
||||
id,
|
||||
sessionID: SessionID.make(sid),
|
||||
role: "assistant",
|
||||
time: { created: Date.now() },
|
||||
parentID: MessageID.make(parent),
|
||||
modelID: "test",
|
||||
providerID: "test",
|
||||
mode: "",
|
||||
agent: "test",
|
||||
path: { cwd: "/tmp", root: "/tmp" },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
} as MessageV2.Assistant)
|
||||
return id
|
||||
}
|
||||
|
||||
describe("Session.fork child session remapping", () => {
|
||||
test(
|
||||
"forked session gets its own copy of child sessions",
|
||||
async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const parent = await Session.create({ title: "parent" })
|
||||
const child = await Session.create({ parentID: parent.id, title: "child subagent" })
|
||||
|
||||
// Add a user message to the child so it has content
|
||||
const childMsgId = await userMsg(child.id)
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: childMsgId,
|
||||
sessionID: child.id,
|
||||
type: "text",
|
||||
text: "child message content",
|
||||
} as MessageV2.TextPart)
|
||||
|
||||
// Add a user message then an assistant message with a task tool part referencing the child
|
||||
const parentUserMsg = await userMsg(parent.id)
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: parentUserMsg,
|
||||
sessionID: parent.id,
|
||||
type: "text",
|
||||
text: "do something",
|
||||
} as MessageV2.TextPart)
|
||||
|
||||
const parentAsstMsg = await asstMsg(parent.id, parentUserMsg)
|
||||
await Session.updatePart(
|
||||
taskPart({
|
||||
messageID: parentAsstMsg,
|
||||
sessionID: parent.id,
|
||||
childSessionID: child.id,
|
||||
}),
|
||||
)
|
||||
|
||||
// Fork the parent session
|
||||
const forked = await Session.fork({ sessionID: parent.id })
|
||||
expect(forked.id).not.toBe(parent.id)
|
||||
|
||||
// Check that the forked session's task part references a DIFFERENT child session
|
||||
const forkedMsgs = await Session.messages({ sessionID: forked.id })
|
||||
const parts = forkedMsgs.flatMap((m) => m.parts)
|
||||
const tools = parts.filter((p) => p.type === "tool" && p.tool === "task") as MessageV2.ToolPart[]
|
||||
|
||||
expect(tools).toHaveLength(1)
|
||||
const meta = (tools[0].state as unknown as { metadata: { sessionId: string } }).metadata
|
||||
expect(meta.sessionId).not.toBe(child.id)
|
||||
|
||||
// Verify the forked child session actually exists and has content
|
||||
const forkedChild = await Session.get(SessionID.make(meta.sessionId))
|
||||
expect(forkedChild).toBeDefined()
|
||||
expect(forkedChild.id).not.toBe(child.id)
|
||||
|
||||
const forkedChildMsgs = await Session.messages({ sessionID: forkedChild.id })
|
||||
expect(forkedChildMsgs).toHaveLength(1)
|
||||
expect(forkedChildMsgs[0].parts[0].type).toBe("text")
|
||||
},
|
||||
})
|
||||
},
|
||||
{ timeout: 30000 },
|
||||
)
|
||||
|
||||
test(
|
||||
"nested child sessions are also remapped",
|
||||
async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
// grandchild -> child -> parent
|
||||
const parent = await Session.create({ title: "parent" })
|
||||
const child = await Session.create({ parentID: parent.id, title: "child" })
|
||||
const grandchild = await Session.create({ parentID: child.id, title: "grandchild" })
|
||||
|
||||
// grandchild has a text message
|
||||
const gcMsgId = await userMsg(grandchild.id)
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: gcMsgId,
|
||||
sessionID: grandchild.id,
|
||||
type: "text",
|
||||
text: "grandchild content",
|
||||
} as MessageV2.TextPart)
|
||||
|
||||
// child references grandchild via task part
|
||||
const childUserMsg = await userMsg(child.id)
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: childUserMsg,
|
||||
sessionID: child.id,
|
||||
type: "text",
|
||||
text: "question",
|
||||
} as MessageV2.TextPart)
|
||||
const childAsstMsg = await asstMsg(child.id, childUserMsg)
|
||||
await Session.updatePart(
|
||||
taskPart({
|
||||
messageID: childAsstMsg,
|
||||
sessionID: child.id,
|
||||
childSessionID: grandchild.id,
|
||||
}),
|
||||
)
|
||||
|
||||
// parent references child via task part
|
||||
const parentUserMsg = await userMsg(parent.id)
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: parentUserMsg,
|
||||
sessionID: parent.id,
|
||||
type: "text",
|
||||
text: "request",
|
||||
} as MessageV2.TextPart)
|
||||
const parentAsstMsg = await asstMsg(parent.id, parentUserMsg)
|
||||
await Session.updatePart(
|
||||
taskPart({
|
||||
messageID: parentAsstMsg,
|
||||
sessionID: parent.id,
|
||||
childSessionID: child.id,
|
||||
}),
|
||||
)
|
||||
|
||||
const forked = await Session.fork({ sessionID: parent.id })
|
||||
|
||||
// Verify parent-level remap
|
||||
const forkedMsgs = await Session.messages({ sessionID: forked.id })
|
||||
const tools = forkedMsgs
|
||||
.flatMap((m) => m.parts)
|
||||
.filter((p) => p.type === "tool" && p.tool === "task") as MessageV2.ToolPart[]
|
||||
const forkedChildID = (tools[0].state as unknown as { metadata: { sessionId: string } }).metadata.sessionId
|
||||
expect(forkedChildID).not.toBe(child.id)
|
||||
|
||||
// Verify child-level remap (grandchild)
|
||||
const forkedChildMsgs = await Session.messages({ sessionID: SessionID.make(forkedChildID) })
|
||||
const childTools = forkedChildMsgs
|
||||
.flatMap((m) => m.parts)
|
||||
.filter((p) => p.type === "tool" && p.tool === "task") as MessageV2.ToolPart[]
|
||||
expect(childTools).toHaveLength(1)
|
||||
const forkedGrandchildID = (childTools[0].state as unknown as { metadata: { sessionId: string } }).metadata
|
||||
.sessionId
|
||||
expect(forkedGrandchildID).not.toBe(grandchild.id)
|
||||
|
||||
// Verify grandchild content was copied
|
||||
const gcMsgs = await Session.messages({ sessionID: SessionID.make(forkedGrandchildID) })
|
||||
expect(gcMsgs).toHaveLength(1)
|
||||
},
|
||||
})
|
||||
},
|
||||
{ timeout: 30000 },
|
||||
)
|
||||
|
||||
test(
|
||||
"non-task tool parts are not affected",
|
||||
async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const parent = await Session.create({ title: "parent" })
|
||||
const parentUserMsg = await userMsg(parent.id)
|
||||
await Session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: parentUserMsg,
|
||||
sessionID: parent.id,
|
||||
type: "text",
|
||||
text: "hello",
|
||||
} as MessageV2.TextPart)
|
||||
|
||||
const forked = await Session.fork({ sessionID: parent.id })
|
||||
const forkedMsgs = await Session.messages({ sessionID: forked.id })
|
||||
expect(forkedMsgs).toHaveLength(1)
|
||||
expect(forkedMsgs[0].parts[0].type).toBe("text")
|
||||
expect((forkedMsgs[0].parts[0] as MessageV2.TextPart).text).toBe("hello")
|
||||
},
|
||||
})
|
||||
},
|
||||
{ timeout: 30000 },
|
||||
)
|
||||
})
|
||||
@@ -26,7 +26,9 @@ const getHeapMB = () => {
|
||||
}
|
||||
|
||||
describe("memory: abort controller leak", () => {
|
||||
test("webfetch does not leak memory over many invocations", async () => {
|
||||
// kilocode_change start - TODO(#8990): skip flaky test on Linux CI
|
||||
test.skip("webfetch does not leak memory over many invocations", async () => {
|
||||
// kilocode_change end
|
||||
await Instance.provide({
|
||||
directory: projectRoot,
|
||||
fn: async () => {
|
||||
|
||||
@@ -190,6 +190,7 @@ function makeHttp() {
|
||||
|
||||
const it = testEffect(makeHttp())
|
||||
const unix = process.platform !== "win32" ? it.live : it.live.skip
|
||||
const unixSkip = it.live.skip // kilocode_change - TODO(#8990): skip flaky cancel tests on Linux CI
|
||||
|
||||
// Config that registers a custom "test" provider with a "test-model" model
|
||||
// so Provider.getModel("test", "test-model") succeeds inside the loop.
|
||||
@@ -1197,7 +1198,8 @@ it.live(
|
||||
3_000,
|
||||
)
|
||||
|
||||
unix(
|
||||
// kilocode_change start - TODO(#8990): flaky on Linux CI
|
||||
unixSkip(
|
||||
"cancel interrupts shell and resolves cleanly",
|
||||
() =>
|
||||
withSh(() =>
|
||||
@@ -1233,8 +1235,10 @@ unix(
|
||||
),
|
||||
30_000,
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
unix(
|
||||
// kilocode_change start - TODO(#8990): flaky on Linux CI
|
||||
unixSkip(
|
||||
"cancel persists aborted shell result when shell ignores TERM",
|
||||
() =>
|
||||
withSh(() =>
|
||||
@@ -1265,6 +1269,7 @@ unix(
|
||||
),
|
||||
30_000,
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
unix(
|
||||
"cancel finalizes interrupted bash tool output through normal truncation",
|
||||
@@ -1317,7 +1322,8 @@ unix(
|
||||
30_000,
|
||||
)
|
||||
|
||||
unix(
|
||||
// kilocode_change start - TODO(#8990): flaky on Linux CI
|
||||
unixSkip(
|
||||
"cancel interrupts loop queued behind shell",
|
||||
() =>
|
||||
provideTmpdirInstance(
|
||||
@@ -1344,6 +1350,7 @@ unix(
|
||||
),
|
||||
30_000,
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
unix(
|
||||
"shell rejects when another shell is already running",
|
||||
|
||||
@@ -3086,109 +3086,6 @@ export class Question extends HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
export class Suggestion extends HeyApiClient {
|
||||
/**
|
||||
* List pending suggestions
|
||||
*
|
||||
* Get all pending suggestion requests across all sessions.
|
||||
*/
|
||||
public list<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).get<SuggestionListResponses, unknown, ThrowOnError>({
|
||||
url: "/suggestion",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept suggestion request
|
||||
*
|
||||
* Accept a suggestion request from the AI assistant.
|
||||
*/
|
||||
public accept<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
requestID: string
|
||||
directory?: string
|
||||
workspace?: string
|
||||
index?: number
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "requestID" },
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "body", key: "index" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<SuggestionAcceptResponses, SuggestionAcceptErrors, ThrowOnError>({
|
||||
url: "/suggestion/{requestID}/accept",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss suggestion request
|
||||
*
|
||||
* Dismiss a suggestion request from the AI assistant.
|
||||
*/
|
||||
public dismiss<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
requestID: string
|
||||
directory?: string
|
||||
workspace?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "requestID" },
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<SuggestionDismissResponses, SuggestionDismissErrors, ThrowOnError>({
|
||||
url: "/suggestion/{requestID}/dismiss",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class Oauth extends HeyApiClient {
|
||||
/**
|
||||
* OAuth authorize
|
||||
@@ -4624,6 +4521,109 @@ export class Network extends HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
export class Suggestion extends HeyApiClient {
|
||||
/**
|
||||
* List pending suggestions
|
||||
*
|
||||
* Get all pending suggestion requests across all sessions.
|
||||
*/
|
||||
public list<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).get<SuggestionListResponses, unknown, ThrowOnError>({
|
||||
url: "/suggestion",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept suggestion request
|
||||
*
|
||||
* Accept a suggestion request from the AI assistant.
|
||||
*/
|
||||
public accept<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
requestID: string
|
||||
directory?: string
|
||||
workspace?: string
|
||||
index?: number
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "requestID" },
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "body", key: "index" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<SuggestionAcceptResponses, SuggestionAcceptErrors, ThrowOnError>({
|
||||
url: "/suggestion/{requestID}/accept",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss suggestion request
|
||||
*
|
||||
* Dismiss a suggestion request from the AI assistant.
|
||||
*/
|
||||
public dismiss<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
requestID: string
|
||||
directory?: string
|
||||
workspace?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "requestID" },
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<SuggestionDismissResponses, SuggestionDismissErrors, ThrowOnError>({
|
||||
url: "/suggestion/{requestID}/dismiss",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class Telemetry extends HeyApiClient {
|
||||
/**
|
||||
* Capture telemetry event
|
||||
@@ -5764,11 +5764,6 @@ export class KiloClient extends HeyApiClient {
|
||||
return (this._question ??= new Question({ client: this.client }))
|
||||
}
|
||||
|
||||
private _suggestion?: Suggestion
|
||||
get suggestion(): Suggestion {
|
||||
return (this._suggestion ??= new Suggestion({ client: this.client }))
|
||||
}
|
||||
|
||||
private _provider?: Provider
|
||||
get provider(): Provider {
|
||||
return (this._provider ??= new Provider({ client: this.client }))
|
||||
@@ -5834,6 +5829,11 @@ export class KiloClient extends HeyApiClient {
|
||||
return (this._network ??= new Network({ client: this.client }))
|
||||
}
|
||||
|
||||
private _suggestion?: Suggestion
|
||||
get suggestion(): Suggestion {
|
||||
return (this._suggestion ??= new Suggestion({ client: this.client }))
|
||||
}
|
||||
|
||||
private _telemetry?: Telemetry
|
||||
get telemetry(): Telemetry {
|
||||
return (this._telemetry ??= new Telemetry({ client: this.client }))
|
||||
|
||||
@@ -509,6 +509,65 @@ export type EventSessionIdle = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SuggestionAction = {
|
||||
/**
|
||||
* Button or option label (1-5 words)
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* Brief explanation of what this action does
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* Synthetic user prompt to inject when this action is accepted
|
||||
*/
|
||||
prompt: string
|
||||
}
|
||||
|
||||
export type SuggestionRequest = {
|
||||
id: string
|
||||
sessionID: string
|
||||
/**
|
||||
* Suggestion text shown to the user
|
||||
*/
|
||||
text: string
|
||||
/**
|
||||
* Available actions the user can take
|
||||
*/
|
||||
actions: Array<SuggestionAction>
|
||||
/**
|
||||
* Whether this suggestion blocks prompt input (default: true)
|
||||
*/
|
||||
blocking?: boolean
|
||||
tool?: {
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSuggestionShown = {
|
||||
type: "suggestion.shown"
|
||||
properties: SuggestionRequest
|
||||
}
|
||||
|
||||
export type EventSuggestionAccepted = {
|
||||
type: "suggestion.accepted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
index: number
|
||||
action: SuggestionAction
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSuggestionDismissed = {
|
||||
type: "suggestion.dismissed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionCompacted = {
|
||||
type: "session.compacted"
|
||||
properties: {
|
||||
@@ -979,65 +1038,6 @@ export type EventMessagePartRemoved = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SuggestionAction = {
|
||||
/**
|
||||
* Button or option label (1-5 words)
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* Brief explanation of what this action does
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* Synthetic user prompt to inject when this action is accepted
|
||||
*/
|
||||
prompt: string
|
||||
}
|
||||
|
||||
export type SuggestionRequest = {
|
||||
id: string
|
||||
sessionID: string
|
||||
/**
|
||||
* Suggestion text shown to the user
|
||||
*/
|
||||
text: string
|
||||
/**
|
||||
* Available actions the user can take
|
||||
*/
|
||||
actions: Array<SuggestionAction>
|
||||
/**
|
||||
* Whether this suggestion blocks prompt input (default: true)
|
||||
*/
|
||||
blocking?: boolean
|
||||
tool?: {
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSuggestionShown = {
|
||||
type: "suggestion.shown"
|
||||
properties: SuggestionRequest
|
||||
}
|
||||
|
||||
export type EventSuggestionAccepted = {
|
||||
type: "suggestion.accepted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
index: number
|
||||
action: SuggestionAction
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSuggestionDismissed = {
|
||||
type: "suggestion.dismissed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionAction = "allow" | "deny" | "ask"
|
||||
|
||||
export type PermissionRule = {
|
||||
@@ -1120,9 +1120,6 @@ export type Event =
|
||||
| EventGlobalConfigUpdated
|
||||
| EventLspClientDiagnostics
|
||||
| EventLspUpdated
|
||||
| EventSuggestionShown
|
||||
| EventSuggestionAccepted
|
||||
| EventSuggestionDismissed
|
||||
| EventTuiPromptAppend
|
||||
| EventTuiCommandExecute
|
||||
| EventTuiToastShow
|
||||
@@ -1150,6 +1147,9 @@ export type Event =
|
||||
| EventTodoUpdated
|
||||
| EventSessionStatus
|
||||
| EventSessionIdle
|
||||
| EventSuggestionShown
|
||||
| EventSuggestionAccepted
|
||||
| EventSuggestionDismissed
|
||||
| EventSessionCompacted
|
||||
| EventKiloSessionsRemoteStatusChanged
|
||||
| EventWorkspaceReady
|
||||
@@ -1756,6 +1756,15 @@ export type Config = {
|
||||
*/
|
||||
url?: string
|
||||
}
|
||||
/**
|
||||
* Configuration for AI-generated commit messages
|
||||
*/
|
||||
commit_message?: {
|
||||
/**
|
||||
* Custom system prompt for AI commit message generation. When set, replaces the default conventional commits prompt entirely.
|
||||
*/
|
||||
prompt?: string
|
||||
}
|
||||
compaction?: {
|
||||
/**
|
||||
* Enable automatic compaction when context is full (default: true)
|
||||
@@ -4611,98 +4620,6 @@ export type QuestionRejectResponses = {
|
||||
|
||||
export type QuestionRejectResponse = QuestionRejectResponses[keyof QuestionRejectResponses]
|
||||
|
||||
export type SuggestionListData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/suggestion"
|
||||
}
|
||||
|
||||
export type SuggestionListResponses = {
|
||||
/**
|
||||
* List of pending suggestions
|
||||
*/
|
||||
200: Array<SuggestionRequest>
|
||||
}
|
||||
|
||||
export type SuggestionListResponse = SuggestionListResponses[keyof SuggestionListResponses]
|
||||
|
||||
export type SuggestionAcceptData = {
|
||||
body?: {
|
||||
/**
|
||||
* Zero-based action index to accept
|
||||
*/
|
||||
index: number
|
||||
}
|
||||
path: {
|
||||
requestID: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/suggestion/{requestID}/accept"
|
||||
}
|
||||
|
||||
export type SuggestionAcceptErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type SuggestionAcceptError = SuggestionAcceptErrors[keyof SuggestionAcceptErrors]
|
||||
|
||||
export type SuggestionAcceptResponses = {
|
||||
/**
|
||||
* Suggestion accepted successfully
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type SuggestionAcceptResponse = SuggestionAcceptResponses[keyof SuggestionAcceptResponses]
|
||||
|
||||
export type SuggestionDismissData = {
|
||||
body?: never
|
||||
path: {
|
||||
requestID: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/suggestion/{requestID}/dismiss"
|
||||
}
|
||||
|
||||
export type SuggestionDismissErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type SuggestionDismissError = SuggestionDismissErrors[keyof SuggestionDismissErrors]
|
||||
|
||||
export type SuggestionDismissResponses = {
|
||||
/**
|
||||
* Suggestion dismissed successfully
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type SuggestionDismissResponse = SuggestionDismissResponses[keyof SuggestionDismissResponses]
|
||||
|
||||
export type ProviderListData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@@ -5840,6 +5757,98 @@ export type NetworkRejectResponses = {
|
||||
|
||||
export type NetworkRejectResponse = NetworkRejectResponses[keyof NetworkRejectResponses]
|
||||
|
||||
export type SuggestionListData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/suggestion"
|
||||
}
|
||||
|
||||
export type SuggestionListResponses = {
|
||||
/**
|
||||
* List of pending suggestions
|
||||
*/
|
||||
200: Array<SuggestionRequest>
|
||||
}
|
||||
|
||||
export type SuggestionListResponse = SuggestionListResponses[keyof SuggestionListResponses]
|
||||
|
||||
export type SuggestionAcceptData = {
|
||||
body?: {
|
||||
/**
|
||||
* Zero-based action index to accept
|
||||
*/
|
||||
index: number
|
||||
}
|
||||
path: {
|
||||
requestID: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/suggestion/{requestID}/accept"
|
||||
}
|
||||
|
||||
export type SuggestionAcceptErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type SuggestionAcceptError = SuggestionAcceptErrors[keyof SuggestionAcceptErrors]
|
||||
|
||||
export type SuggestionAcceptResponses = {
|
||||
/**
|
||||
* Suggestion accepted successfully
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type SuggestionAcceptResponse = SuggestionAcceptResponses[keyof SuggestionAcceptResponses]
|
||||
|
||||
export type SuggestionDismissData = {
|
||||
body?: never
|
||||
path: {
|
||||
requestID: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/suggestion/{requestID}/dismiss"
|
||||
}
|
||||
|
||||
export type SuggestionDismissErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type SuggestionDismissError = SuggestionDismissErrors[keyof SuggestionDismissErrors]
|
||||
|
||||
export type SuggestionDismissResponses = {
|
||||
/**
|
||||
* Suggestion dismissed successfully
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type SuggestionDismissResponse = SuggestionDismissResponses[keyof SuggestionDismissResponses]
|
||||
|
||||
export type TelemetryCaptureData = {
|
||||
body?: {
|
||||
/**
|
||||
|
||||
@@ -14887,6 +14887,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commit_message": {
|
||||
"description": "Configuration for AI-generated commit messages",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {
|
||||
"description": "Custom system prompt for AI commit message generation. When set, replaces the default conventional commits prompt entirely.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"compaction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user