chore: checkpoint before merging main

This commit is contained in:
marius-kilocode
2026-08-19 14:50:11 +02:00
parent 97abdab66c
commit ba658fc8d7
42 changed files with 1141 additions and 28 deletions
+45
View File
@@ -1046,6 +1046,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
openSessions: (ids) => this.trackOpenSessions(ids),
speechToTextModels: () => this.fetchAndSendSpeechToTextModels(),
modelUsage: (msg) => handleModelUsageMessage(msg, this.extensionContext, (value) => this.postMessage(value)),
backgroundJobs: (sessionID) => this.fetchAndSendBackgroundJobs(sessionID),
cancelBackgroundJob: (jobID, sessionID) => this.cancelBackgroundJob(jobID, sessionID),
backgroundSubagents: (sessionID) => this.backgroundSubagents(sessionID),
})
) {
return
@@ -2815,6 +2818,48 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.postMessage({ type: "speechToTextModelsLoaded" as const, models: result.models })
}
private async fetchAndSendBackgroundJobs(sessionID = this.currentSession?.id): Promise<void> {
const client = this.client
if (!client || this.connectionState !== "connected") return
try {
const { data } = await client.kilocode.backgroundJobs(
{ directory: this.getWorkspaceDirectory(sessionID) },
{ throwOnError: true },
)
this.postMessage({ type: "backgroundJobsLoaded", sessionID, jobs: data })
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to fetch background jobs:", error)
}
}
private async cancelBackgroundJob(jobID: string, sessionID = this.currentSession?.id): Promise<void> {
const client = this.client
if (!client || this.connectionState !== "connected") return
try {
await client.kilocode.backgroundJob.cancel(
{ jobID, directory: this.getWorkspaceDirectory(sessionID) },
{ throwOnError: true },
)
await this.fetchAndSendBackgroundJobs(sessionID)
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to cancel background job:", error)
}
}
private async backgroundSubagents(sessionID: string): Promise<void> {
const client = this.client
if (!client || this.connectionState !== "connected") return
try {
await client.experimental.session.background(
{ sessionID, directory: this.getWorkspaceDirectory(sessionID) },
{ throwOnError: true },
)
await this.fetchAndSendBackgroundJobs(sessionID)
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to background subagents:", error)
}
}
/**
* Seed sessionStatusMap with current session statuses on connect.
* Without this, the Settings panel (which has no tracked sessions) would see
@@ -21,6 +21,29 @@ type Ctx = {
openSessions: (ids: string[]) => void
speechToTextModels: () => Promise<void>
modelUsage: (message: ModelUsageMessage) => Promise<void>
backgroundJobs: (sessionID?: string) => Promise<void>
cancelBackgroundJob: (jobID: string, sessionID?: string) => Promise<void>
backgroundSubagents: (sessionID: string) => Promise<void>
}
async function routeBackgroundMessage(
message: { type: string; sessionID?: unknown; jobID?: unknown },
ctx: Ctx,
): Promise<boolean | undefined> {
if (message.type === "requestBackgroundJobs") {
await ctx.backgroundJobs(typeof message.sessionID === "string" ? message.sessionID : undefined)
return true
}
if (message.type === "cancelBackgroundJob") {
if (typeof message.jobID === "string")
await ctx.cancelBackgroundJob(message.jobID, typeof message.sessionID === "string" ? message.sessionID : undefined)
return true
}
if (message.type === "backgroundSubagents") {
if (typeof message.sessionID === "string") await ctx.backgroundSubagents(message.sessionID)
return true
}
return undefined
}
export async function routeEarlyMessage(
@@ -84,5 +107,6 @@ export async function routeEarlyMessage(
ctx.browserSettings()
return true
}
return await routeInputToolMessage(message, { connection: ctx.connection, dir: ctx.dir, post: ctx.post })
const background = await routeBackgroundMessage(message, ctx)
return background ?? (await routeInputToolMessage(message, { connection: ctx.connection, dir: ctx.dir, post: ctx.post }))
}
@@ -18,6 +18,7 @@ import { calcTokenUsage, collapseCostBreakdown } from "../../context/session-uti
import { useLanguage } from "../../context/language"
import { useVSCode } from "../../context/vscode"
import { TaskTimeline } from "./TaskTimeline"
import { BackgroundAgents } from "./BackgroundAgents"
import { ContextProgress } from "./ContextProgress"
import { TaskUsage } from "./TaskUsage"
import { TranscriptSearch } from "./TranscriptSearch"
@@ -291,6 +292,7 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
<Show when={tokens()}>{(tk) => <TaskUsage tokens={tk()} usage={session.modelUsage()} />}</Show>
</div>
</Show>
<BackgroundAgents readonly={props.readonly} />
<Show when={hasTodos()}>
<div data-component="task-header-todos">
<button
@@ -20,6 +20,7 @@ import { useSession } from "../../context/session"
import { useVSCode } from "../../context/vscode"
import { useWorktreeMode } from "../../context/worktree-mode"
import { childID } from "../../context/session-utils"
import { openSubagent } from "./open-subagent"
import { taskResult, taskRunning, taskVisible } from "./task-tool-state"
const TaskToolRenderer: Component<ToolProps> = (props) => {
@@ -124,16 +125,13 @@ const TaskToolRenderer: Component<ToolProps> = (props) => {
e.stopPropagation()
const id = childSessionId()
if (!id) return
const title = description()
if (worktree) {
window.dispatchEvent(
new CustomEvent("agentManager.openSubagent", {
detail: { sessionID: id, title, parentSessionID: session.currentSessionID() },
}),
)
return
}
vscode.postMessage({ type: "openSubAgentViewer", sessionID: id, title })
openSubagent({
sessionID: id,
title: description(),
parentSessionID: session.currentSessionID(),
worktree: !!worktree,
post: vscode.postMessage,
})
}
const trigger = () => (
+17
View File
@@ -1207,6 +1207,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} مهام مكتملة",
"task.todos.allDone": "{{count}} مهام مكتملة",
"task.backgroundAgents.running.one": "وكيل خلفي واحد",
"task.backgroundAgents.running.many": "{{count}} وكلاء خلفيون",
"task.backgroundAgents.open": "فتح الوكيل الخلفي",
"task.backgroundAgents.openShort": "فتح",
"task.backgroundAgents.cancel": "إيقاف",
"task.backgroundAgents.continueInBackground": "متابعة في الخلفية",
"task.backgroundAgents.foreground": "الوكيل الأمامي قيد التشغيل",
"task.backgroundAgents.waiting": "وكيل خلفي يحتاج إلى إدخالك",
"task.backgroundAgents.needsInput": "الإدخال مطلوب",
"task.backgroundAgents.dismiss": "تجاهل",
"task.backgroundAgents.clearFinished": "مسح المكتمل",
"task.backgroundAgents.summary": "{{running}} من {{total}} وكلاء خلفيين قيد التشغيل",
"task.backgroundAgents.status.running": "قيد التشغيل",
"task.backgroundAgents.status.completed": "مكتمل",
"task.backgroundAgents.status.cancelled": "ملغى",
"task.backgroundAgents.status.error": "خطأ",
"task.backgroundAgents.untitled": "وكيل خلفي",
"settings.saveBar.unsavedChanges": "تغييرات غير محفوظة",
"settings.saveBar.discard": "تجاهل",
"settings.saveBar.save": "حفظ",
+17
View File
@@ -1250,6 +1250,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} tarefas concluídas",
"task.todos.allDone": "{{count}} tarefas concluídas",
"task.backgroundAgents.running.one": "1 agente em segundo plano",
"task.backgroundAgents.running.many": "{{count}} agentes em segundo plano",
"task.backgroundAgents.open": "Abrir agente em segundo plano",
"task.backgroundAgents.openShort": "Abrir",
"task.backgroundAgents.cancel": "Parar",
"task.backgroundAgents.continueInBackground": "Continuar em segundo plano",
"task.backgroundAgents.foreground": "O agente em primeiro plano está em execução",
"task.backgroundAgents.waiting": "Um agente em segundo plano precisa da sua entrada",
"task.backgroundAgents.needsInput": "Entrada necessária",
"task.backgroundAgents.dismiss": "Dispensar",
"task.backgroundAgents.clearFinished": "Limpar concluídos",
"task.backgroundAgents.summary": "{{running}} de {{total}} agentes em segundo plano em execução",
"task.backgroundAgents.status.running": "Em execução",
"task.backgroundAgents.status.completed": "Concluído",
"task.backgroundAgents.status.cancelled": "Cancelado",
"task.backgroundAgents.status.error": "Erro",
"task.backgroundAgents.untitled": "Agente em segundo plano",
"settings.saveBar.unsavedChanges": "Alterações não salvas",
"settings.saveBar.discard": "Descartar",
"settings.saveBar.save": "Salvar",
+17
View File
@@ -1241,6 +1241,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} zadataka završeno",
"task.todos.allDone": "{{count}} zadataka završeno",
"task.backgroundAgents.running.one": "1 agent u pozadini",
"task.backgroundAgents.running.many": "{{count}} agenata u pozadini",
"task.backgroundAgents.open": "Otvori agenta u pozadini",
"task.backgroundAgents.openShort": "Otvori",
"task.backgroundAgents.cancel": "Zaustavi",
"task.backgroundAgents.continueInBackground": "Nastavi u pozadini",
"task.backgroundAgents.foreground": "Agent u prvom planu radi",
"task.backgroundAgents.waiting": "Agent u pozadini treba vaš unos",
"task.backgroundAgents.needsInput": "Potreban unos",
"task.backgroundAgents.dismiss": "Odbaci",
"task.backgroundAgents.clearFinished": "Obriši završene",
"task.backgroundAgents.summary": "{{running}} od {{total}} agenata u pozadini radi",
"task.backgroundAgents.status.running": "Radi",
"task.backgroundAgents.status.completed": "Završeno",
"task.backgroundAgents.status.cancelled": "Otkazano",
"task.backgroundAgents.status.error": "Greška",
"task.backgroundAgents.untitled": "Agent u pozadini",
"settings.saveBar.unsavedChanges": "Nespremljene promjene",
"settings.saveBar.discard": "Odbaci",
"settings.saveBar.save": "Spremi",
+17
View File
@@ -1236,6 +1236,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} opgaver udført",
"task.todos.allDone": "{{count}} opgaver udført",
"task.backgroundAgents.running.one": "1 baggrundsagent",
"task.backgroundAgents.running.many": "{{count}} baggrundsagenter",
"task.backgroundAgents.open": "Åbn baggrundsagent",
"task.backgroundAgents.openShort": "Åbn",
"task.backgroundAgents.cancel": "Stop",
"task.backgroundAgents.continueInBackground": "Fortsæt i baggrunden",
"task.backgroundAgents.foreground": "Forgrundsagenten kører",
"task.backgroundAgents.waiting": "En baggrundsagent har brug for dit input",
"task.backgroundAgents.needsInput": "Input kræves",
"task.backgroundAgents.dismiss": "Afvis",
"task.backgroundAgents.clearFinished": "Ryd færdige",
"task.backgroundAgents.summary": "{{running}} af {{total}} baggrundsagenter kører",
"task.backgroundAgents.status.running": "Kører",
"task.backgroundAgents.status.completed": "Færdig",
"task.backgroundAgents.status.cancelled": "Annulleret",
"task.backgroundAgents.status.error": "Fejl",
"task.backgroundAgents.untitled": "Baggrundsagent",
"settings.saveBar.unsavedChanges": "Ikke-gemte ændringer",
"settings.saveBar.discard": "Kassér",
"settings.saveBar.save": "Gem",
@@ -1266,6 +1266,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} Aufgaben erledigt",
"task.todos.allDone": "{{count}} Aufgaben erledigt",
"task.backgroundAgents.running.one": "1 Hintergrund-Agent",
"task.backgroundAgents.running.many": "{{count}} Hintergrund-Agenten",
"task.backgroundAgents.open": "Hintergrund-Agent öffnen",
"task.backgroundAgents.openShort": "Öffnen",
"task.backgroundAgents.cancel": "Stoppen",
"task.backgroundAgents.continueInBackground": "Im Hintergrund fortsetzen",
"task.backgroundAgents.foreground": "Vordergrund-Agent läuft",
"task.backgroundAgents.waiting": "Ein Hintergrund-Agent benötigt deine Eingabe",
"task.backgroundAgents.needsInput": "Eingabe erforderlich",
"task.backgroundAgents.dismiss": "Ausblenden",
"task.backgroundAgents.clearFinished": "Abgeschlossene löschen",
"task.backgroundAgents.summary": "{{running}} von {{total}} Hintergrund-Agenten aktiv",
"task.backgroundAgents.status.running": "Läuft",
"task.backgroundAgents.status.completed": "Fertig",
"task.backgroundAgents.status.cancelled": "Abgebrochen",
"task.backgroundAgents.status.error": "Fehler",
"task.backgroundAgents.untitled": "Hintergrund-Agent",
"settings.saveBar.unsavedChanges": "Nicht gespeicherte Änderungen",
"settings.saveBar.discard": "Verwerfen",
"settings.saveBar.save": "Speichern",
@@ -1220,6 +1220,24 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} to-dos done",
"task.todos.allDone": "{{count}} to-dos done",
"task.backgroundAgents.running.one": "1 background agent",
"task.backgroundAgents.running.many": "{{count}} background agents",
"task.backgroundAgents.open": "Open background agent",
"task.backgroundAgents.openShort": "Open",
"task.backgroundAgents.cancel": "Stop",
"task.backgroundAgents.continueInBackground": "Continue in background",
"task.backgroundAgents.foreground": "Foreground subagent running",
"task.backgroundAgents.waiting": "A background agent needs your input",
"task.backgroundAgents.needsInput": "Needs input",
"task.backgroundAgents.dismiss": "Dismiss",
"task.backgroundAgents.clearFinished": "Clear finished",
"task.backgroundAgents.summary": "{{running}} of {{total}} background agents running",
"task.backgroundAgents.status.running": "Running",
"task.backgroundAgents.status.completed": "Done",
"task.backgroundAgents.status.cancelled": "Cancelled",
"task.backgroundAgents.status.error": "Error",
"task.backgroundAgents.untitled": "Background agent",
"settings.saveBar.unsavedChanges": "Unsaved changes",
"settings.saveBar.discard": "Discard",
"settings.saveBar.save": "Save",
+17
View File
@@ -1253,6 +1253,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} tareas completadas",
"task.todos.allDone": "{{count}} tareas completadas",
"task.backgroundAgents.running.one": "1 agente en segundo plano",
"task.backgroundAgents.running.many": "{{count}} agentes en segundo plano",
"task.backgroundAgents.open": "Abrir agente en segundo plano",
"task.backgroundAgents.openShort": "Abrir",
"task.backgroundAgents.cancel": "Detener",
"task.backgroundAgents.continueInBackground": "Continuar en segundo plano",
"task.backgroundAgents.foreground": "El agente en primer plano está ejecutándose",
"task.backgroundAgents.waiting": "Un agente en segundo plano necesita tu entrada",
"task.backgroundAgents.needsInput": "Necesita entrada",
"task.backgroundAgents.dismiss": "Descartar",
"task.backgroundAgents.clearFinished": "Borrar completados",
"task.backgroundAgents.summary": "{{running}} de {{total}} agentes en segundo plano en ejecución",
"task.backgroundAgents.status.running": "En ejecución",
"task.backgroundAgents.status.completed": "Completado",
"task.backgroundAgents.status.cancelled": "Cancelado",
"task.backgroundAgents.status.error": "Error",
"task.backgroundAgents.untitled": "Agente en segundo plano",
"settings.saveBar.unsavedChanges": "Cambios sin guardar",
"settings.saveBar.discard": "Descartar",
"settings.saveBar.save": "Guardar",
+17
View File
@@ -1232,6 +1232,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} کار انجام شد",
"task.todos.allDone": "{{count}} کار انجام شد",
"task.backgroundAgents.running.one": "1 عامل پس‌زمینه",
"task.backgroundAgents.running.many": "{{count}} عامل پس‌زمینه",
"task.backgroundAgents.open": "باز کردن عامل پس‌زمینه",
"task.backgroundAgents.openShort": "باز کردن",
"task.backgroundAgents.cancel": "توقف",
"task.backgroundAgents.continueInBackground": "ادامه در پس‌زمینه",
"task.backgroundAgents.foreground": "عامل پیش‌زمینه در حال اجراست",
"task.backgroundAgents.waiting": "یک عامل پس‌زمینه به ورودی شما نیاز دارد",
"task.backgroundAgents.needsInput": "ورودی لازم است",
"task.backgroundAgents.dismiss": "رد کردن",
"task.backgroundAgents.clearFinished": "پاک کردن موارد تکمیل‌شده",
"task.backgroundAgents.summary": "{{running}} از {{total}} عامل پس‌زمینه در حال اجرا هستند",
"task.backgroundAgents.status.running": "در حال اجرا",
"task.backgroundAgents.status.completed": "تمام شد",
"task.backgroundAgents.status.cancelled": "لغو شد",
"task.backgroundAgents.status.error": "خطا",
"task.backgroundAgents.untitled": "عامل پس‌زمینه",
"settings.saveBar.unsavedChanges": "تغییرات ذخیره‌نشده",
"settings.saveBar.discard": "رد کردن",
+17
View File
@@ -1273,6 +1273,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} tâches terminées",
"task.todos.allDone": "{{count}} tâches terminées",
"task.backgroundAgents.running.one": "1 agent en arrière-plan",
"task.backgroundAgents.running.many": "{{count}} agents en arrière-plan",
"task.backgroundAgents.open": "Ouvrir l'agent en arrière-plan",
"task.backgroundAgents.openShort": "Ouvrir",
"task.backgroundAgents.cancel": "Arrêter",
"task.backgroundAgents.continueInBackground": "Continuer en arrière-plan",
"task.backgroundAgents.foreground": "L'agent au premier plan est actif",
"task.backgroundAgents.waiting": "Un agent en arrière-plan attend votre saisie",
"task.backgroundAgents.needsInput": "Saisie requise",
"task.backgroundAgents.dismiss": "Ignorer",
"task.backgroundAgents.clearFinished": "Effacer les agents terminés",
"task.backgroundAgents.summary": "{{running}} agent(s) en arrière-plan sur {{total}} en cours",
"task.backgroundAgents.status.running": "En cours",
"task.backgroundAgents.status.completed": "Terminé",
"task.backgroundAgents.status.cancelled": "Annulé",
"task.backgroundAgents.status.error": "Erreur",
"task.backgroundAgents.untitled": "Agent en arrière-plan",
"settings.saveBar.unsavedChanges": "Modifications non enregistrées",
"settings.saveBar.discard": "Ignorer",
"settings.saveBar.save": "Enregistrer",
+17
View File
@@ -1084,6 +1084,23 @@ export const dict = {
"error.details.show": "Dettagli",
"task.todos.progress": "{{done}}/{{total}} to-do completati",
"task.todos.allDone": "{{count}} to-do completati",
"task.backgroundAgents.running.one": "1 agente in background",
"task.backgroundAgents.running.many": "{{count}} agenti in background",
"task.backgroundAgents.open": "Apri agente in background",
"task.backgroundAgents.openShort": "Apri",
"task.backgroundAgents.cancel": "Arresta",
"task.backgroundAgents.continueInBackground": "Continua in background",
"task.backgroundAgents.foreground": "L'agente in primo piano è in esecuzione",
"task.backgroundAgents.waiting": "Un agente in background richiede il tuo input",
"task.backgroundAgents.needsInput": "Input richiesto",
"task.backgroundAgents.dismiss": "Ignora",
"task.backgroundAgents.clearFinished": "Cancella completati",
"task.backgroundAgents.summary": "{{running}} di {{total}} agenti in background in esecuzione",
"task.backgroundAgents.status.running": "In esecuzione",
"task.backgroundAgents.status.completed": "Completato",
"task.backgroundAgents.status.cancelled": "Annullato",
"task.backgroundAgents.status.error": "Errore",
"task.backgroundAgents.untitled": "Agente in background",
"settings.saveBar.unsavedChanges": "Modifiche non salvate",
"settings.saveBar.discard": "Scarta",
"settings.saveBar.save": "Salva",
+17
View File
@@ -1228,6 +1228,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} タスク完了",
"task.todos.allDone": "{{count}} タスク完了",
"task.backgroundAgents.running.one": "バックグラウンドエージェント 1 件",
"task.backgroundAgents.running.many": "バックグラウンドエージェント {{count}} 件",
"task.backgroundAgents.open": "バックグラウンドエージェントを開く",
"task.backgroundAgents.openShort": "開く",
"task.backgroundAgents.cancel": "停止",
"task.backgroundAgents.continueInBackground": "バックグラウンドで続行",
"task.backgroundAgents.foreground": "フォアグラウンドエージェントが実行中",
"task.backgroundAgents.waiting": "バックグラウンドエージェントが入力を待っています",
"task.backgroundAgents.needsInput": "入力が必要",
"task.backgroundAgents.dismiss": "閉じる",
"task.backgroundAgents.clearFinished": "完了済みを消去",
"task.backgroundAgents.summary": "{{total}} 件中 {{running}} 件のバックグラウンドエージェントが実行中",
"task.backgroundAgents.status.running": "実行中",
"task.backgroundAgents.status.completed": "完了",
"task.backgroundAgents.status.cancelled": "キャンセル済み",
"task.backgroundAgents.status.error": "エラー",
"task.backgroundAgents.untitled": "バックグラウンドエージェント",
"settings.saveBar.unsavedChanges": "未保存の変更",
"settings.saveBar.discard": "破棄",
"settings.saveBar.save": "保存",
+17
View File
@@ -1215,6 +1215,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} 할 일 완료",
"task.todos.allDone": "{{count}} 할 일 완료",
"task.backgroundAgents.running.one": "백그라운드 에이전트 1개",
"task.backgroundAgents.running.many": "백그라운드 에이전트 {{count}}개",
"task.backgroundAgents.open": "백그라운드 에이전트 열기",
"task.backgroundAgents.openShort": "열기",
"task.backgroundAgents.cancel": "중지",
"task.backgroundAgents.continueInBackground": "백그라운드에서 계속",
"task.backgroundAgents.foreground": "포그라운드 에이전트 실행 중",
"task.backgroundAgents.waiting": "백그라운드 에이전트에 입력이 필요합니다",
"task.backgroundAgents.needsInput": "입력 필요",
"task.backgroundAgents.dismiss": "닫기",
"task.backgroundAgents.clearFinished": "완료된 항목 지우기",
"task.backgroundAgents.summary": "백그라운드 에이전트 {{total}}개 중 {{running}}개 실행 중",
"task.backgroundAgents.status.running": "실행 중",
"task.backgroundAgents.status.completed": "완료",
"task.backgroundAgents.status.cancelled": "취소됨",
"task.backgroundAgents.status.error": "오류",
"task.backgroundAgents.untitled": "백그라운드 에이전트",
"settings.saveBar.unsavedChanges": "저장되지 않은 변경 사항",
"settings.saveBar.discard": "취소",
"settings.saveBar.save": "저장",
+17
View File
@@ -1223,6 +1223,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} to-do's voltooid",
"task.todos.allDone": "{{count}} to-do's voltooid",
"task.backgroundAgents.running.one": "1 achtergrondagent",
"task.backgroundAgents.running.many": "{{count}} achtergrondagenten",
"task.backgroundAgents.open": "Achtergrondagent openen",
"task.backgroundAgents.openShort": "Openen",
"task.backgroundAgents.cancel": "Stoppen",
"task.backgroundAgents.continueInBackground": "Doorgaan op de achtergrond",
"task.backgroundAgents.foreground": "Voorgrondagent actief",
"task.backgroundAgents.waiting": "Een achtergrondagent heeft je invoer nodig",
"task.backgroundAgents.needsInput": "Invoer vereist",
"task.backgroundAgents.dismiss": "Negeren",
"task.backgroundAgents.clearFinished": "Voltooide wissen",
"task.backgroundAgents.summary": "{{running}} van {{total}} achtergrondagenten actief",
"task.backgroundAgents.status.running": "Actief",
"task.backgroundAgents.status.completed": "Voltooid",
"task.backgroundAgents.status.cancelled": "Geannuleerd",
"task.backgroundAgents.status.error": "Fout",
"task.backgroundAgents.untitled": "Achtergrondagent",
"settings.saveBar.unsavedChanges": "Niet-opgeslagen wijzigingen",
"settings.saveBar.discard": "Verwerpen",
+17
View File
@@ -1232,6 +1232,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} oppgaver fullført",
"task.todos.allDone": "{{count}} oppgaver fullført",
"task.backgroundAgents.running.one": "1 bakgrunnsagent",
"task.backgroundAgents.running.many": "{{count}} bakgrunnsagenter",
"task.backgroundAgents.open": "Åpne bakgrunnsagent",
"task.backgroundAgents.openShort": "Åpne",
"task.backgroundAgents.cancel": "Stopp",
"task.backgroundAgents.continueInBackground": "Fortsett i bakgrunnen",
"task.backgroundAgents.foreground": "Forgrunnsagenten kjører",
"task.backgroundAgents.waiting": "En bakgrunnsagent trenger innspill fra deg",
"task.backgroundAgents.needsInput": "Innspill kreves",
"task.backgroundAgents.dismiss": "Avvis",
"task.backgroundAgents.clearFinished": "Fjern fullførte",
"task.backgroundAgents.summary": "{{running}} av {{total}} bakgrunnsagenter kjører",
"task.backgroundAgents.status.running": "Kjører",
"task.backgroundAgents.status.completed": "Ferdig",
"task.backgroundAgents.status.cancelled": "Avbrutt",
"task.backgroundAgents.status.error": "Feil",
"task.backgroundAgents.untitled": "Bakgrunnsagent",
"settings.saveBar.unsavedChanges": "Ulagrede endringer",
"settings.saveBar.discard": "Forkast",
"settings.saveBar.save": "Lagre",
+17
View File
@@ -1241,6 +1241,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} zadań ukończono",
"task.todos.allDone": "{{count}} zadań ukończono",
"task.backgroundAgents.running.one": "1 agent w tle",
"task.backgroundAgents.running.many": "{{count}} agentów w tle",
"task.backgroundAgents.open": "Otwórz agenta w tle",
"task.backgroundAgents.openShort": "Otwórz",
"task.backgroundAgents.cancel": "Zatrzymaj",
"task.backgroundAgents.continueInBackground": "Kontynuuj w tle",
"task.backgroundAgents.foreground": "Agent pierwszoplanowy działa",
"task.backgroundAgents.waiting": "Agent w tle potrzebuje danych wejściowych",
"task.backgroundAgents.needsInput": "Wymagane dane wejściowe",
"task.backgroundAgents.dismiss": "Odrzuć",
"task.backgroundAgents.clearFinished": "Wyczyść ukończone",
"task.backgroundAgents.summary": "{{running}} z {{total}} agentów w tle działa",
"task.backgroundAgents.status.running": "Działa",
"task.backgroundAgents.status.completed": "Ukończono",
"task.backgroundAgents.status.cancelled": "Anulowano",
"task.backgroundAgents.status.error": "Błąd",
"task.backgroundAgents.untitled": "Agent w tle",
"settings.saveBar.unsavedChanges": "Niezapisane zmiany",
"settings.saveBar.discard": "Odrzuć",
"settings.saveBar.save": "Zapisz",
+17
View File
@@ -1235,6 +1235,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} задач выполнено",
"task.todos.allDone": "{{count}} задач выполнено",
"task.backgroundAgents.running.one": "1 фоновый агент",
"task.backgroundAgents.running.many": "Фоновых агентов: {{count}}",
"task.backgroundAgents.open": "Открыть фонового агента",
"task.backgroundAgents.openShort": "Открыть",
"task.backgroundAgents.cancel": "Остановить",
"task.backgroundAgents.continueInBackground": "Продолжить в фоне",
"task.backgroundAgents.foreground": "Агент на переднем плане выполняется",
"task.backgroundAgents.waiting": "Фоновому агенту требуется ваш ввод",
"task.backgroundAgents.needsInput": "Требуется ввод",
"task.backgroundAgents.dismiss": "Скрыть",
"task.backgroundAgents.clearFinished": "Очистить завершённые",
"task.backgroundAgents.summary": "Фоновые агенты: {{running}} из {{total}} выполняются",
"task.backgroundAgents.status.running": "Выполняется",
"task.backgroundAgents.status.completed": "Готово",
"task.backgroundAgents.status.cancelled": "Отменено",
"task.backgroundAgents.status.error": "Ошибка",
"task.backgroundAgents.untitled": "Фоновый агент",
"settings.saveBar.unsavedChanges": "Несохранённые изменения",
"settings.saveBar.discard": "Отменить",
"settings.saveBar.save": "Сохранить",
+17
View File
@@ -1212,6 +1212,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} งานเสร็จแล้ว",
"task.todos.allDone": "{{count}} งานเสร็จแล้ว",
"task.backgroundAgents.running.one": "เอเจนต์เบื้องหลัง 1 ตัว",
"task.backgroundAgents.running.many": "เอเจนต์เบื้องหลัง {{count}} ตัว",
"task.backgroundAgents.open": "เปิดเอเจนต์เบื้องหลัง",
"task.backgroundAgents.openShort": "เปิด",
"task.backgroundAgents.cancel": "หยุด",
"task.backgroundAgents.continueInBackground": "ทำต่อในเบื้องหลัง",
"task.backgroundAgents.foreground": "เอเจนต์เบื้องหน้ากำลังทำงาน",
"task.backgroundAgents.waiting": "เอเจนต์เบื้องหลังต้องการข้อมูลจากคุณ",
"task.backgroundAgents.needsInput": "ต้องการข้อมูล",
"task.backgroundAgents.dismiss": "ยกเลิก",
"task.backgroundAgents.clearFinished": "ล้างรายการที่เสร็จแล้ว",
"task.backgroundAgents.summary": "เอเจนต์เบื้องหลัง {{running}} จาก {{total}} ตัวกำลังทำงาน",
"task.backgroundAgents.status.running": "กำลังทำงาน",
"task.backgroundAgents.status.completed": "เสร็จแล้ว",
"task.backgroundAgents.status.cancelled": "ยกเลิกแล้ว",
"task.backgroundAgents.status.error": "ข้อผิดพลาด",
"task.backgroundAgents.untitled": "เอเจนต์เบื้องหลัง",
"settings.saveBar.unsavedChanges": "การเปลี่ยนแปลงที่ยังไม่ได้บันทึก",
"settings.saveBar.discard": "ยกเลิก",
"settings.saveBar.save": "บันทึก",
+17
View File
@@ -1209,6 +1209,23 @@ export const dict = {
"task.todos.progress": "{{total}} görevden {{done}} tanesi tamamlandı",
"task.todos.allDone": "{{count}} görev tamamlandı",
"task.backgroundAgents.running.one": "1 arka plan ajanı",
"task.backgroundAgents.running.many": "{{count}} arka plan ajanı",
"task.backgroundAgents.open": "Arka plan ajanını aç",
"task.backgroundAgents.openShort": "Aç",
"task.backgroundAgents.cancel": "Durdur",
"task.backgroundAgents.continueInBackground": "Arka planda devam et",
"task.backgroundAgents.foreground": "Ön plan ajanı çalışıyor",
"task.backgroundAgents.waiting": "Bir arka plan ajanı girişinizi bekliyor",
"task.backgroundAgents.needsInput": "Giriş gerekli",
"task.backgroundAgents.dismiss": "Kapat",
"task.backgroundAgents.clearFinished": "Tamamlananları temizle",
"task.backgroundAgents.summary": "{{total}} arka plan ajanından {{running}} tanesi çalışıyor",
"task.backgroundAgents.status.running": "Çalışıyor",
"task.backgroundAgents.status.completed": "Tamamlandı",
"task.backgroundAgents.status.cancelled": "İptal edildi",
"task.backgroundAgents.status.error": "Hata",
"task.backgroundAgents.untitled": "Arka plan ajanı",
"settings.saveBar.unsavedChanges": "Kaydedilmemiş değişiklikler",
"settings.saveBar.discard": "Geri Al",
+17
View File
@@ -1208,6 +1208,23 @@ export const dict = {
"task.todos.progress": "{{done}} з {{total}} завдань виконано",
"task.todos.allDone": "{{count}} завдань виконано",
"task.backgroundAgents.running.one": "1 фоновий агент",
"task.backgroundAgents.running.many": "Фонових агентів: {{count}}",
"task.backgroundAgents.open": "Відкрити фонового агента",
"task.backgroundAgents.openShort": "Відкрити",
"task.backgroundAgents.cancel": "Зупинити",
"task.backgroundAgents.continueInBackground": "Продовжити у фоні",
"task.backgroundAgents.foreground": "Агент на передньому плані працює",
"task.backgroundAgents.waiting": "Фоновому агенту потрібен ваш ввід",
"task.backgroundAgents.needsInput": "Потрібен ввід",
"task.backgroundAgents.dismiss": "Сховати",
"task.backgroundAgents.clearFinished": "Очистити завершені",
"task.backgroundAgents.summary": "Працює {{running}} із {{total}} фонових агентів",
"task.backgroundAgents.status.running": "Виконується",
"task.backgroundAgents.status.completed": "Завершено",
"task.backgroundAgents.status.cancelled": "Скасовано",
"task.backgroundAgents.status.error": "Помилка",
"task.backgroundAgents.untitled": "Фоновий агент",
"settings.saveBar.unsavedChanges": "Незбережені зміни",
"settings.saveBar.discard": "Скасувати",
+17
View File
@@ -1170,6 +1170,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} 个待办已完成",
"task.todos.allDone": "{{count}} 个待办已完成",
"task.backgroundAgents.running.one": "1 个后台智能体",
"task.backgroundAgents.running.many": "{{count}} 个后台智能体",
"task.backgroundAgents.open": "打开后台智能体",
"task.backgroundAgents.openShort": "打开",
"task.backgroundAgents.cancel": "停止",
"task.backgroundAgents.continueInBackground": "在后台继续",
"task.backgroundAgents.foreground": "前台智能体正在运行",
"task.backgroundAgents.waiting": "后台智能体需要你的输入",
"task.backgroundAgents.needsInput": "需要输入",
"task.backgroundAgents.dismiss": "关闭",
"task.backgroundAgents.clearFinished": "清除已完成",
"task.backgroundAgents.summary": "{{running}}/{{total}} 个后台智能体运行中",
"task.backgroundAgents.status.running": "运行中",
"task.backgroundAgents.status.completed": "已完成",
"task.backgroundAgents.status.cancelled": "已取消",
"task.backgroundAgents.status.error": "错误",
"task.backgroundAgents.untitled": "后台智能体",
"settings.saveBar.unsavedChanges": "未保存的更改",
"settings.saveBar.discard": "放弃",
"settings.saveBar.save": "保存",
+17
View File
@@ -1174,6 +1174,23 @@ export const dict = {
"task.todos.progress": "{{done}}/{{total}} 個待辦已完成",
"task.todos.allDone": "{{count}} 個待辦已完成",
"task.backgroundAgents.running.one": "1 個背景 Agent",
"task.backgroundAgents.running.many": "{{count}} 個背景 Agent",
"task.backgroundAgents.open": "開啟背景 Agent",
"task.backgroundAgents.openShort": "開啟",
"task.backgroundAgents.cancel": "停止",
"task.backgroundAgents.continueInBackground": "在背景繼續",
"task.backgroundAgents.foreground": "前景 Agent 執行中",
"task.backgroundAgents.waiting": "背景 Agent 需要你的輸入",
"task.backgroundAgents.needsInput": "需要輸入",
"task.backgroundAgents.dismiss": "關閉",
"task.backgroundAgents.clearFinished": "清除已完成",
"task.backgroundAgents.summary": "{{running}}/{{total}} 個背景 Agent 執行中",
"task.backgroundAgents.status.running": "執行中",
"task.backgroundAgents.status.completed": "已完成",
"task.backgroundAgents.status.cancelled": "已取消",
"task.backgroundAgents.status.error": "錯誤",
"task.backgroundAgents.untitled": "背景 Agent",
"settings.saveBar.unsavedChanges": "未儲存的變更",
"settings.saveBar.discard": "捨棄",
"settings.saveBar.save": "儲存",
@@ -60,7 +60,29 @@
Task Header Todos
============================================ */
[data-component="task-header-todos"] {
/* The background agent strip reuses these slots so both strips look identical. */
[data-component="task-header-todos"],
[data-component="task-header-agents"] {
display: flex;
flex-direction: column;
[data-slot="task-header-agents-toolbar"] {
display: flex;
align-items: center;
min-width: 0;
width: 100%;
}
[data-slot="task-header-agents-toolbar"] > [data-component="button"],
[data-slot="task-header-agents-toolbar"] > [data-component="icon-button"] {
flex-shrink: 0;
margin-right: 8px;
}
[data-slot="task-header-agents-toolbar"] [data-slot="task-header-todos-trigger"] {
flex: 1;
min-width: 0;
}
border-bottom: 1px solid var(--border-weak-base);
background-color: var(--background-base);
flex-shrink: 0;
@@ -116,6 +138,110 @@
}
}
/* ============================================
Task Header Background Agents
Strip chrome comes from the to-do strip rules above. Only the row
affordances below are specific to background agents.
============================================ */
[data-component="task-header-agents"] {
/* The shared spinner svg has no intrinsic size, so it must be constrained
or it stretches to the full row width. */
[data-component="spinner"] {
width: 12px;
height: 12px;
flex-shrink: 0;
color: var(--text-weak);
}
[data-slot="task-header-agent"] {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
min-height: 28px;
padding: 2px 0;
box-sizing: border-box;
font-size: var(--kilo-font-size-12);
color: var(--text-base);
}
[data-slot="task-header-agent-attention"] {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 0 6px;
color: var(--text-warning, var(--vscode-editorWarning-foreground));
font-size: var(--kilo-font-size-11);
}
[data-slot="task-header-agent-attention-label"] {
color: var(--text-warning, var(--vscode-editorWarning-foreground));
font-size: var(--kilo-font-size-11);
white-space: nowrap;
}
[data-slot="task-header-agent-main"] {
all: unset;
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
flex: 1;
cursor: pointer;
border-radius: 3px;
padding: 3px 4px;
&:hover,
&:focus-visible {
color: var(--text-base);
background: var(--surface-base-hover);
outline: none;
}
}
[data-slot="task-header-agent-label"] {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-slot="task-header-agent-status"] {
flex-shrink: 0;
margin-left: 2px;
color: var(--text-weak);
}
[data-slot="task-header-agent-status-label"] {
color: var(--text-weak);
font-size: var(--kilo-font-size-11);
white-space: nowrap;
}
[data-component="icon-button"],
[data-component="button"] {
flex-shrink: 0;
}
&[data-wide-actions="true"] [data-slot="task-header-agent-main"] {
flex: 0 1 auto;
width: auto;
max-width: min(100%, 760px);
padding-block: 5px;
}
&[data-wide-actions="true"] [data-slot="task-header-agent-status-label"] {
margin-left: 4px;
}
&[data-wide-actions="true"] [data-slot="task-header-agent-label"] {
flex: 0 1 auto;
max-width: 620px;
}
}
/* ============================================
Task Timeline
============================================ */
@@ -17,6 +17,23 @@ import type {
import type { AgentManagerSidebarTarget } from "./webview-messages"
import type { PermissionRequest } from "./permissions"
import type { AnacondaDesktopExtensionMessage } from "../../../../src/shared/anaconda-desktop-messages"
export interface BackgroundJobsLoadedMessage {
type: "backgroundJobsLoaded"
sessionID?: string
jobs: BackgroundJobInfo[]
}
export interface BackgroundJobInfo {
id: string
type: string
title?: string
status: "running" | "completed" | "error" | "cancelled"
started_at: number
completed_at?: number
error?: string
metadata?: Record<string, unknown>
}
import type { QuestionRequest, SuggestionRequest, TodoItem } from "./questions"
import type { ModelSelection, ModelUsageMap, Provider, ProviderAuthState } from "./providers"
import type { SpeechToTextModelDef } from "../../../../src/speech-to-text/models"
@@ -1471,3 +1488,4 @@ export type ExtensionMessage =
| MemoryLoadedMessage
| MemoryEventMessage
| MemoryOperationResultMessage
| BackgroundJobsLoadedMessage
@@ -43,6 +43,22 @@ export interface AbortRequest {
sessionID: string
}
export interface RequestBackgroundJobsMessage {
type: "requestBackgroundJobs"
sessionID?: string
}
export interface CancelBackgroundJobMessage {
type: "cancelBackgroundJob"
jobID: string
sessionID?: string
}
export interface BackgroundSubagentsMessage {
type: "backgroundSubagents"
sessionID: string
}
export interface RevertSessionRequest {
type: "revertSession"
sessionID: string
@@ -1406,6 +1422,9 @@ export interface DismissAgentMigrationBannerMessage {
export type WebviewMessage =
| SendMessageRequest
| AbortRequest
| RequestBackgroundJobsMessage
| CancelBackgroundJobMessage
| BackgroundSubagentsMessage
| RevertSessionRequest
| UnrevertSessionRequest
| DeleteMessageRequest
@@ -44,7 +44,11 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
enableQuestionTool: bool("KILO_ENABLE_QUESTION_TOOL"),
experimentalScout: enabledByExperimental("KILO_EXPERIMENTAL_SCOUT"), // kilocode_change
experimentalReferences: enabledByExperimental("KILO_EXPERIMENTAL_REFERENCES"),
experimentalBackgroundSubagents: enabledByExperimental("KILO_EXPERIMENTAL_BACKGROUND_SUBAGENTS"),
// kilocode_change start - enabled by default, with an opt-out kill switch
experimentalBackgroundSubagents: Config.boolean("KILO_EXPERIMENTAL_BACKGROUND_SUBAGENTS").pipe(
Config.withDefault(true),
),
// kilocode_change end
experimentalLspTy: bool("KILO_EXPERIMENTAL_LSP_TY"),
experimentalLspTool: enabledByExperimental("KILO_EXPERIMENTAL_LSP_TOOL"),
experimentalOxfmt: enabledByExperimental("KILO_EXPERIMENTAL_OXFMT"),
@@ -29,6 +29,17 @@ import { CommandFiles } from "@/kilocode/command-files"
const root = "/kilocode"
const Scope = Schema.Literals(["global", "project"])
export const BackgroundJobInfo = Schema.Struct({
id: Schema.String,
type: Schema.String,
title: Schema.optional(Schema.String),
status: Schema.Literals(["running", "completed", "error", "cancelled"]),
started_at: Schema.Number,
completed_at: Schema.optional(Schema.Number),
error: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
})
export const RemoveSkillPayload = Schema.Struct({
location: Schema.String,
})
@@ -65,6 +76,8 @@ export const KilocodePaths = {
agentManagerReply: `${root}/agent-manager/:requestID/reply`,
agentManagerReject: `${root}/agent-manager/:requestID/reject`,
sessionModelUsage: `/session/:sessionID/model-usage`,
backgroundJobs: `${root}/background-jobs`,
backgroundJobCancel: `${root}/background-jobs/:jobID/cancel`,
} as const
export const KilocodeApi = HttpApi.make("kilocode")
@@ -223,6 +236,28 @@ export const KilocodeApi = HttpApi.make("kilocode")
description: "Get token usage and direct cost by model for the complete top-level session tree.",
}),
),
HttpApiEndpoint.get("backgroundJobs", KilocodePaths.backgroundJobs, {
query: WorkspaceRoutingQuery,
success: described(Schema.Array(BackgroundJobInfo), "Background jobs"),
}).annotateMerge(
OpenApi.annotations({
identifier: "kilocode.backgroundJobs",
summary: "List background jobs",
description: "List background subagent jobs for the current instance.",
}),
),
HttpApiEndpoint.post("backgroundJobCancel", KilocodePaths.backgroundJobCancel, {
params: { jobID: Schema.String },
query: WorkspaceRoutingQuery,
success: described(Schema.Boolean, "Background job cancelled"),
error: HttpApiError.NotFound,
}).annotateMerge(
OpenApi.annotations({
identifier: "kilocode.backgroundJob.cancel",
summary: "Cancel background job",
description: "Cancel one background subagent job.",
}),
),
)
.annotateMerge(
OpenApi.annotations({
@@ -17,6 +17,7 @@ import { InstanceStore } from "@/project/instance-store"
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
import { InvalidRequestError } from "@/server/routes/instance/httpapi/errors"
import { Skill } from "@/skill"
import { BackgroundJob } from "@/background/job"
import type { SessionID } from "@/session/schema"
import {
AgentManagerRejectPayload,
@@ -27,6 +28,7 @@ import {
RemoveCommandPayload,
RemoveSkillPayload,
} from "../groups/kilocode"
import { BackgroundJobInfo } from "../groups/kilocode"
export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode", (handlers) =>
Effect.gen(function* () {
@@ -37,6 +39,7 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
const store = yield* InstanceStore.Service
const manager = yield* AgentManager.Service
const notebook = yield* Notebook.Service
const background = yield* BackgroundJob.Service
const heapSnapshot = Effect.fn("KilocodeHttpApi.heapSnapshot")(function* () {
return yield* Effect.sync(() => HeapSnapshot.write())
@@ -175,6 +178,28 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
return usage
})
const backgroundJobs = Effect.fn("KilocodeHttpApi.backgroundJobs")(function* () {
return (yield* background.list()).map((job) => ({
id: job.id,
type: job.type,
title: job.title,
status: job.status,
started_at: job.started_at,
completed_at: job.completed_at,
error: job.error,
metadata: job.metadata,
})) satisfies (typeof BackgroundJobInfo.Type)[]
})
const backgroundJobCancel = Effect.fn("KilocodeHttpApi.backgroundJobCancel")(function* (ctx: {
params: { jobID: string }
}) {
const job = yield* background.get(ctx.params.jobID)
if (!job) return yield* new HttpApiError.NotFound({})
yield* background.cancel(ctx.params.jobID)
return true
})
return handlers
.handle("heapSnapshot", heapSnapshot)
.handle("agentRequirements", agentRequirements)
@@ -189,5 +214,7 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
.handle("agentManagerReply", agentManagerReply)
.handle("agentManagerReject", agentManagerReject)
.handle("sessionModelUsage", sessionModelUsage)
.handle("backgroundJobs", backgroundJobs)
.handle("backgroundJobCancel", backgroundJobCancel)
}),
)
@@ -15,9 +15,22 @@ describe("RuntimeFlags", () => {
const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
expect(flags.autoShare).toBe(false)
expect(flags.experimentalBackgroundSubagents).toBe(true) // kilocode_change
}),
)
// kilocode_change start - preserve the background-subagent kill switch
it.effect("allows disabling background subagents explicitly", () =>
Effect.gen(function* () {
const flags = yield* readFlags.pipe(
Effect.provide(fromConfig({ KILO_EXPERIMENTAL_BACKGROUND_SUBAGENTS: "false" })),
)
expect(flags.experimentalBackgroundSubagents).toBe(false)
}),
)
// kilocode_change end
it.effect("layer parses plugin flags from the active ConfigProvider", () =>
Effect.gen(function* () {
const flags = yield* readFlags.pipe(
@@ -51,7 +64,6 @@ describe("RuntimeFlags", () => {
expect(flags.enableExperimentalModels).toBe(true)
expect(flags.enableQuestionTool).toBe(true)
expect(flags.experimentalReferences).toBe(true)
expect(flags.experimentalBackgroundSubagents).toBe(true)
expect(flags.experimentalLspTy).toBe(false)
expect(flags.experimentalLspTool).toBe(true)
expect(flags.experimentalOxfmt).toBe(true)
@@ -547,6 +547,21 @@ export const kiloScenarios: Scenario[] = [
object(body.totals)
check(body.models.length === 0, "a new session should have no model usage")
}),
http.protected.get("/kilocode/background-jobs", "kilocode.backgroundJobs").json(200, (body) => {
array(body)
for (const item of body) {
object(item)
check(typeof item.id === "string", "background job should include an id")
check(typeof item.status === "string", "background job should include a status")
}
}),
http.protected
.post("/kilocode/background-jobs/{jobID}/cancel", "kilocode.backgroundJob.cancel")
.at((ctx) => ({
path: route("/kilocode/background-jobs/{jobID}/cancel", { jobID: "job_httpapi_missing" }),
headers: ctx.headers(),
}))
.status(404),
http.protected
.post("/kilocode/heap/snapshot", "kilocode.heap.snapshot")
.mutating()
@@ -641,8 +641,12 @@ const scenarios: Scenario[] = [
.at((ctx) => ({ path: "/experimental/session?roots=false&archived=false", headers: ctx.headers() }))
.json(200, array),
http.protected.get("/experimental/capabilities", "experimental.capabilities.get").json(200, (body) => {
check(typeof body === "object" && body !== null, "capabilities should be an object")
check("backgroundSubagents" in body, "capabilities should report background subagents")
// kilocode_change start - background subagents are always available
check(
typeof body === "object" && body !== null && "backgroundSubagents" in body && body.backgroundSubagents === true,
"capabilities should report background subagents as available",
)
// kilocode_change end
}),
http.protected
.post("/experimental/session/{sessionID}/background", "experimental.session.background")
@@ -1,12 +1,17 @@
import { afterEach, describe, expect, mock } from "bun:test"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Effect, Layer } from "effect"
import { Effect, Fiber, Layer } from "effect" // kilocode_change
import { BackgroundJob } from "@/background/job" // kilocode_change
import { Session as SessionNs } from "@/session/session"
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { pollWithTimeout, testEffect } from "../lib/effect" // kilocode_change
import { httpApiLayer, requestInDirectory } from "./httpapi-layer"
const it = testEffect(Layer.mergeAll(LayerNode.compile(SessionNs.node), httpApiLayer))
// kilocode_change start - provide the background-job service for promotion coverage
const it = testEffect(
Layer.mergeAll(LayerNode.compile(SessionNs.node), LayerNode.compile(BackgroundJob.node), httpApiLayer), // kilocode_change
)
// kilocode_change end
afterEach(async () => {
mock.restore()
@@ -14,6 +19,21 @@ afterEach(async () => {
})
describe("session action routes", () => {
// kilocode_change start - background subagents are enabled by default
it.instance(
"reports background subagents as available",
() =>
Effect.gen(function* () {
const test = yield* TestInstance
const res = yield* requestInDirectory("/experimental/capabilities", test.directory)
expect(res.status).toBe(200)
expect(yield* res.json).toEqual({ backgroundSubagents: true })
}),
{ git: true },
)
// kilocode_change end
it.instance(
"session routes expose metadata on create, update, get, and fork",
() =>
@@ -71,7 +91,6 @@ describe("session action routes", () => {
}),
{ git: true },
)
it.instance(
"abort route returns success",
() =>
@@ -107,4 +126,39 @@ describe("session action routes", () => {
}),
{ git: true },
)
// kilocode_change start - verify HTTP promotion of a running task
it.instance(
"experimental background route backgrounds a synchronous subagent",
() =>
Effect.gen(function* () {
const test = yield* TestInstance
const session = yield* Effect.acquireRelease(SessionNs.use.create({}), (created) =>
SessionNs.use.remove(created.id).pipe(Effect.ignore),
)
const jobs = yield* BackgroundJob.Service
const job = yield* jobs.start({
type: "task",
metadata: { parentSessionId: session.id },
run: Effect.never,
})
const waiting = yield* jobs.waitForPromotion(job.id).pipe(Effect.forkChild)
const backgrounded = yield* pollWithTimeout(
requestInDirectory(`/experimental/session/${session.id}/background`, test.directory, {
method: "POST",
}).pipe(
Effect.flatMap((res) => res.json),
Effect.map((value) => (value === true ? true : undefined)),
),
"background route never promoted the synchronous subagent",
)
expect(backgrounded).toBe(true)
expect((yield* Fiber.join(waiting)).metadata?.background).toBe(true)
yield* jobs.cancel(job.id)
}),
{ git: true },
)
// kilocode_change end
})
+6 -3
View File
@@ -307,7 +307,8 @@ describe("tool.registry", () => {
)
// kilocode_change end
it.instance("hides task background parameter unless experimental background subagents are enabled", () =>
// kilocode_change start - background task parameters are available by default
it.instance("exposes the task background parameter by default", () =>
Effect.gen(function* () {
const registry = yield* ToolRegistry.Service
const agent = yield* Agent.Service
@@ -319,10 +320,12 @@ describe("tool.registry", () => {
agent: build,
})).find((tool) => tool.id === "task")
expect(task?.jsonSchema).toBeDefined()
expect((task?.jsonSchema?.properties as Record<string, unknown> | undefined)?.background).toBeUndefined()
if (!task) throw new Error("task tool not found")
const jsonSchema = ToolJsonSchema.fromTool(task)
expect((jsonSchema.properties as Record<string, unknown> | undefined)?.background).toBeDefined()
}),
)
// kilocode_change end
it.instance("loads tools from .kilo/tool (singular)" /* kilocode_change */, () =>
Effect.gen(function* () {
+5 -2
View File
@@ -59,7 +59,8 @@ const layer = (flags: Partial<RuntimeFlags.Info> = {}) =>
)
const it = testEffect(layer())
const background = testEffect(layer({ experimentalBackgroundSubagents: true }))
const background = it // kilocode_change - background subagents are enabled by default
const disabled = testEffect(layer({ experimentalBackgroundSubagents: false })) // kilocode_change
function defer<T>() {
let resolve!: (value: T | PromiseLike<T>) => void
@@ -781,7 +782,8 @@ describe("tool.task", () => {
}),
)
// kilocode_change end
it.instance("rejects background execution when the experiment is disabled", () =>
// kilocode_change start - preserve the disabled-background regression test
disabled.instance("rejects background execution when the experiment is disabled", () =>
Effect.gen(function* () {
const { chat, assistant } = yield* seed()
const tool = yield* TaskTool
@@ -811,6 +813,7 @@ describe("tool.task", () => {
expect(Exit.isFailure(exit)).toBe(true)
}),
)
// kilocode_change end
it.instance("promotes a running foreground task without restarting it", () =>
Effect.gen(function* () {
+81
View File
@@ -183,6 +183,10 @@ import type {
KilocodeAgentManagerReplyResponses,
KilocodeAgentRequirementsErrors,
KilocodeAgentRequirementsResponses,
KilocodeBackgroundJobCancelErrors,
KilocodeBackgroundJobCancelResponses,
KilocodeBackgroundJobsErrors,
KilocodeBackgroundJobsResponses,
KilocodeCommandFilesErrors,
KilocodeCommandFilesResponses,
KilocodeHeapSnapshotErrors,
@@ -7688,6 +7692,44 @@ export class AgentManager extends HeyApiClient {
}
}
export class BackgroundJob extends HeyApiClient {
/**
* Cancel background job
*
* Cancel one background subagent job.
*/
public cancel<ThrowOnError extends boolean = false>(
parameters: {
jobID: string
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "path", key: "jobID" },
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).post<
KilocodeBackgroundJobCancelResponses,
KilocodeBackgroundJobCancelErrors,
ThrowOnError
>({
url: "/kilocode/background-jobs/{jobID}/cancel",
...options,
...params,
})
}
}
export class SessionImport extends HeyApiClient {
/**
* Insert project for session import
@@ -8301,6 +8343,40 @@ export class Kilocode extends HeyApiClient {
})
}
/**
* List background jobs
*
* List background subagent jobs for the current instance.
*/
public backgroundJobs<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<
KilocodeBackgroundJobsResponses,
KilocodeBackgroundJobsErrors,
ThrowOnError
>({
url: "/kilocode/background-jobs",
...options,
...params,
})
}
private _heap?: Heap
get heap(): Heap {
return (this._heap ??= new Heap({ client: this.client }))
@@ -8316,6 +8392,11 @@ export class Kilocode extends HeyApiClient {
return (this._agentManager ??= new AgentManager({ client: this.client }))
}
private _backgroundJob?: BackgroundJob
get backgroundJob(): BackgroundJob {
return (this._backgroundJob ??= new BackgroundJob({ client: this.client }))
}
private _sessionImport?: SessionImport
get sessionImport(): SessionImport {
return (this._sessionImport ??= new SessionImport({ client: this.client }))
+75
View File
@@ -16961,6 +16961,81 @@ export type KilocodeSessionModelUsageResponses = {
export type KilocodeSessionModelUsageResponse =
KilocodeSessionModelUsageResponses[keyof KilocodeSessionModelUsageResponses]
export type KilocodeBackgroundJobsData = {
body?: never
path?: never
query?: {
directory?: string
workspace?: string
}
url: "/kilocode/background-jobs"
}
export type KilocodeBackgroundJobsErrors = {
/**
* Bad request
*/
400: BadRequestError
}
export type KilocodeBackgroundJobsError = KilocodeBackgroundJobsErrors[keyof KilocodeBackgroundJobsErrors]
export type KilocodeBackgroundJobsResponses = {
/**
* Background jobs
*/
200: Array<{
id: string
type: string
title?: string
status: "running" | "completed" | "error" | "cancelled"
started_at: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
completed_at?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
error?: string
metadata?: {
[key: string]: unknown
}
}>
}
export type KilocodeBackgroundJobsResponse = KilocodeBackgroundJobsResponses[keyof KilocodeBackgroundJobsResponses]
export type KilocodeBackgroundJobCancelData = {
body?: never
path: {
jobID: string
}
query?: {
directory?: string
workspace?: string
}
url: "/kilocode/background-jobs/{jobID}/cancel"
}
export type KilocodeBackgroundJobCancelErrors = {
/**
* Bad request
*/
400: BadRequestError
/**
* Not found
*/
404: NotFoundError
}
export type KilocodeBackgroundJobCancelError =
KilocodeBackgroundJobCancelErrors[keyof KilocodeBackgroundJobCancelErrors]
export type KilocodeBackgroundJobCancelResponses = {
/**
* Background job cancelled
*/
200: boolean
}
export type KilocodeBackgroundJobCancelResponse =
KilocodeBackgroundJobCancelResponses[keyof KilocodeBackgroundJobCancelResponses]
export type AnacondaDesktopStatusData = {
body?: never
path?: never
+204
View File
@@ -15964,6 +15964,206 @@
]
}
},
"/kilocode/background-jobs": {
"get": {
"tags": ["kilocode"],
"operationId": "kilocode.backgroundJobs",
"parameters": [
{
"name": "directory",
"in": "query",
"schema": {
"type": "string"
},
"required": false
},
{
"name": "workspace",
"in": "query",
"schema": {
"type": "string"
},
"required": false
}
],
"responses": {
"200": {
"description": "Background jobs",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"title": {
"type": "string"
},
"status": {
"type": "string",
"enum": ["running", "completed", "error", "cancelled"]
},
"started_at": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
},
{
"type": "string",
"enum": ["Infinity", "-Infinity", "NaN"]
}
]
},
"completed_at": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
},
{
"type": "string",
"enum": ["Infinity", "-Infinity", "NaN"]
}
]
},
"error": {
"type": "string"
},
"metadata": {
"type": "object"
}
},
"required": ["id", "type", "status", "started_at"],
"additionalProperties": false
},
"description": "Background jobs"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
}
},
"description": "List background subagent jobs for the current instance.",
"summary": "List background jobs",
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\"\n\nconst client = createKiloClient()\nawait client.kilocode.backgroundJobs({\n ...\n})"
}
]
}
},
"/kilocode/background-jobs/{jobID}/cancel": {
"post": {
"tags": ["kilocode"],
"operationId": "kilocode.backgroundJob.cancel",
"parameters": [
{
"name": "jobID",
"in": "path",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "directory",
"in": "query",
"schema": {
"type": "string"
},
"required": false
},
{
"name": "workspace",
"in": "query",
"schema": {
"type": "string"
},
"required": false
}
],
"responses": {
"200": {
"description": "Background job cancelled",
"content": {
"application/json": {
"schema": {
"type": "boolean",
"description": "Background job cancelled"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundError"
}
}
}
}
},
"description": "Cancel one background subagent job.",
"summary": "Cancel background job",
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\"\n\nconst client = createKiloClient()\nawait client.kilocode.backgroundJob.cancel({\n ...\n})"
}
]
}
},
"/kilocode/anaconda-desktop/status": {
"get": {
"tags": ["anaconda-desktop"],
@@ -33667,6 +33867,10 @@
"type": "string",
"enum": ["expanded", "collapsed"]
},
"mcp_tool_display": {
"type": "string",
"enum": ["expanded", "collapsed"]
},
"hide_prompt_training_models": {
"type": "boolean"
},
+8 -2
View File
@@ -132,7 +132,7 @@ export const {
},
console_state: emptyConsoleState,
capabilities: {
experimentalBackgroundSubagents: false,
experimentalBackgroundSubagents: true, // kilocode_change - background subagents are enabled by default
},
provider_auth: {},
config: {},
@@ -833,7 +833,13 @@ export const {
setStore("provider", reconcile(providers.providers))
setStore("provider_default", reconcile(providers.default))
setStore("provider_next", reconcile(providerList))
setStore("capabilities", "experimentalBackgroundSubagents", capabilities?.backgroundSubagents === true)
// kilocode_change start - fail closed when the backend omits the capability
setStore(
"capabilities",
"experimentalBackgroundSubagents",
capabilities?.backgroundSubagents === true,
)
// kilocode_change end
setStore("console_state", reconcile(consoleState))
setStore("agent", reconcile(agents))
setStore("config", reconcile(config))
+1 -1
View File
@@ -95,7 +95,7 @@ export function createFetch(override?: FetchHandler, events?: ReturnType<typeof
// kilocode_change end
if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
if (url.pathname === "/experimental/capabilities") return json({ backgroundSubagents: false })
if (url.pathname === "/experimental/capabilities") return json({ backgroundSubagents: true }) // kilocode_change
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
if (url.pathname === "/api/location") return json({ directory, project: { id: "proj_test", directory: worktree } })
if (