Merge pull request #6955 from Kilo-Org/kirillk/cli-init-failure-6209

feat(vscode): surface CLI startup errors in the chat view
This commit is contained in:
Kirill Kalishev
2026-03-12 12:57:55 -04:00
committed by GitHub
43 changed files with 471 additions and 17 deletions
+26 -2
View File
@@ -11,7 +11,7 @@ import type {
FilePartInput,
Config,
} from "@kilocode/sdk/v2/client"
import { type KiloConnectionService, type KilocodeNotification } from "./services/cli-backend"
import { type KiloConnectionService, type KilocodeNotification, ServerStartupError } from "./services/cli-backend"
import type { EditorContext, CloudSessionData } from "./services/cli-backend/types"
import { FileIgnoreController } from "./services/autocomplete/shims/FileIgnoreController"
import { handleChatCompletionRequest } from "./services/autocomplete/chat-autocomplete/handleChatCompletionRequest"
@@ -75,6 +75,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
/** Guard to prevent checkAndShowMigrationWizard running concurrently. */ // legacy-migration
private migrationCheckInFlight = false // legacy-migration
private unsubscribeNotificationDismiss: (() => void) | null = null
private initConnectionPromise: Promise<void> | null = null
private webviewMessageDisposable: vscode.Disposable | null = null
/** Lazily initialized ignore controller for .kilocodeignore filtering */
@@ -397,6 +398,12 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "openChanges":
vscode.commands.executeCommand("kilo-code.new.showChanges")
break
case "retryConnection":
console.log("[Kilo New] KiloProvider: 🔄 Retrying connection...")
this.initializeConnection().catch((e) =>
console.error("[Kilo New] KiloProvider: ❌ Retry connection failed:", e),
)
break
case "openSubAgentViewer":
vscode.commands.executeCommand("kilo-code.new.openSubAgentViewer", message.sessionID, message.title)
break
@@ -610,9 +617,22 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
* Initialize connection to the CLI backend server.
* Subscribes to the shared KiloConnectionService.
*/
private async initializeConnection(): Promise<void> {
private initializeConnection(): Promise<void> {
if (this.initConnectionPromise) {
return this.initConnectionPromise
}
this.initConnectionPromise = this.doInitializeConnection().finally(() => {
this.initConnectionPromise = null
})
return this.initConnectionPromise
}
private async doInitializeConnection(): Promise<void> {
console.log("[Kilo New] KiloProvider: 🔧 Starting initializeConnection...")
this.connectionState = "connecting"
this.postMessage({ type: "connectionState", state: "connecting" })
// Clean up any existing subscriptions (e.g., sidebar re-shown)
this.unsubscribeEvent?.()
this.unsubscribeState?.()
@@ -710,6 +730,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
type: "connectionState",
state: "error",
error: getErrorMessage(error) || "Failed to connect to CLI backend",
...(error instanceof ServerStartupError && {
userMessage: error.userMessage,
userDetails: error.userDetails,
}),
})
}
}
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "انتهت عملية CLI بالرمز {{code}} قبل بدء الخادم",
"server.startupTimeout": "انتهت مهلة بدء تشغيل الخادم بعد {{seconds}} ثانية",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "O processo da CLI foi encerrado com o código {{code}} antes que o servidor fosse iniciado",
"server.startupTimeout": "Tempo limite de inicialização do servidor esgotado após {{seconds}} segundos",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "CLI proces je izašao sa kodom {{code}} prije nego što se server pokrenuo",
"server.startupTimeout": "Vrijeme pokretanja servera je isteklo nakon {{seconds}} sekundi",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "CLI-processen afsluttede med kode {{code}} før serveren startede",
"server.startupTimeout": "Serverens opstartstid udløb efter {{seconds}} sekunder",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "Der CLI-Prozess wurde mit dem Code {{code}} beendet, bevor der Server gestartet wurde",
"server.startupTimeout": "Zeitüberschreitung beim Serverstart nach {{seconds}} Sekunden",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "CLI process exited with code {{code}} before server started",
"server.startupTimeout": "Server startup timeout after {{seconds}} seconds",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "El proceso de la CLI finalizó con el código {{code}} antes de que se iniciara el servidor",
"server.startupTimeout": "Tiempo de espera de inicio del servidor agotado después de {{seconds}} segundos",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "Le processus CLI s'est terminé avec le code {{code}} avant le démarrage du serveur",
"server.startupTimeout": "Délai de démarrage du serveur dépassé après {{seconds}} secondes",
} as const
@@ -0,0 +1,66 @@
import { dict as ar } from "./ar"
import { dict as br } from "./br"
import { dict as bs } from "./bs"
import { dict as da } from "./da"
import { dict as de } from "./de"
import { dict as en } from "./en"
import { dict as es } from "./es"
import { dict as fr } from "./fr"
import { dict as ja } from "./ja"
import { dict as ko } from "./ko"
import { dict as no } from "./no"
import { dict as pl } from "./pl"
import { dict as ru } from "./ru"
import { dict as th } from "./th"
import { dict as zh } from "./zh"
import { dict as zht } from "./zht"
import { type dict as enDict } from "./en"
const bundles: Record<string, Record<string, string>> = {
ar,
br,
bs,
da,
de,
en,
es,
fr,
ja,
ko,
no,
pl,
ru,
th,
zh,
zht,
}
function resolveLocale(lang: string): string {
const lower = lang.toLowerCase()
if (lower.startsWith("zh")) return lower.includes("hant") ? "zht" : "zh"
if (lower.startsWith("nb") || lower.startsWith("nn")) return "no"
if (lower.startsWith("pt")) return "br"
for (const key of Object.keys(bundles)) {
if (lower.startsWith(key)) return key
}
return "en"
}
function loadTranslations(): Record<string, string> {
// vscode.env.language is available at module load time in the extension host
const vscode = require("vscode") as typeof import("vscode")
const locale = resolveLocale(vscode.env.language)
return { ...en, ...(bundles[locale] ?? {}) }
}
const translations: Record<string, string> = loadTranslations()
export function t(key: keyof typeof enDict, vars?: Record<string, string | number>): string {
let text = translations[key] ?? key
if (vars) {
for (const [k, v] of Object.entries(vars)) {
text = text.replaceAll(`{{${k}}}`, String(v))
}
}
return text
}
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "サーバーが起動する前に、CLI プロセスがコード {{code}} で終了しました",
"server.startupTimeout": "サーバーの起動が {{seconds}} 秒後にタイムアウトしました",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "서버가 시작되기 전에 CLI 프로세스가 코드 {{code}}로 종료되었습니다",
"server.startupTimeout": "{{seconds}}초 후 서버 시작 시간이 초과되었습니다",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "CLI-prosessen avsluttet med kode {{code}} før serveren startet",
"server.startupTimeout": "Tidsavbrudd for serveroppstart etter {{seconds}} sekunder",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "Proces CLI zakończył się z kodem {{code}} przed uruchomieniem serwera",
"server.startupTimeout": "Przekroczono limit czasu uruchamiania serwera po {{seconds}} sekundach",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "Процесс CLI завершился с кодом {{code}} до запуска сервера",
"server.startupTimeout": "Время ожидания запуска сервера истекло через {{seconds}} секунд",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "กระบวนการ CLI ออกด้วยรหัส {{code}} ก่อนที่เซิร์ฟเวอร์จะเริ่มทำงาน",
"server.startupTimeout": "หมดเวลาการเริ่มต้นเซิร์ฟเวอร์หลังจาก {{seconds}} วินาที",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "在服务器启动之前,CLI 进程已退出,代码为 {{code}}",
"server.startupTimeout": "服务器启动在 {{seconds}} 秒后超时",
} as const
@@ -0,0 +1,4 @@
export const dict = {
"server.processExited": "在伺服器啟動之前,CLI 處理程序已退出,代碼為 {{code}}",
"server.startupTimeout": "伺服器啟動在 {{seconds}} 秒後逾時",
} as const
@@ -3,3 +3,4 @@
export type { KilocodeNotification } from "./types"
export { KiloConnectionService } from "./connection-service"
export { ServerStartupError } from "./server-manager"
@@ -3,6 +3,7 @@ import * as crypto from "crypto"
import * as fs from "fs"
import * as path from "path"
import * as vscode from "vscode"
import { t } from "./i18n"
import { parseServerPort } from "./server-utils"
export interface ServerInstance {
@@ -11,6 +12,8 @@ export interface ServerInstance {
process: ChildProcess
}
const STARTUP_TIMEOUT_SECONDS = 30
export class ServerManager {
private instance: ServerInstance | null = null
private startupPromise: Promise<ServerInstance> | null = null
@@ -84,6 +87,7 @@ export class ServerManager {
console.log("[Kilo New] ServerManager: 📦 Process spawned with PID:", serverProcess.pid)
let resolved = false
const stderrLines: string[] = []
serverProcess.stdout?.on("data", (data: Buffer) => {
const output = data.toString()
@@ -100,6 +104,7 @@ export class ServerManager {
serverProcess.stderr?.on("data", (data: Buffer) => {
const errorOutput = data.toString()
console.error("[Kilo New] ServerManager: ⚠️ CLI Server stderr:", errorOutput)
stderrLines.push(errorOutput)
})
serverProcess.on("error", (error) => {
@@ -115,18 +120,27 @@ export class ServerManager {
this.instance = null
}
if (!resolved) {
reject(new Error(`CLI process exited with code ${code} before server started`))
const { userMessage, userDetails } = toErrorMessage(
t("server.processExited", { code: code ?? "null" }),
stderrLines,
cliPath,
)
reject(new ServerStartupError(userMessage, userDetails))
}
})
// Timeout after 30 seconds
setTimeout(() => {
if (!resolved) {
console.error("[Kilo New] ServerManager: ⏰ Server startup timeout (30s)")
console.error(`[Kilo New] ServerManager: ⏰ Server startup timeout (${STARTUP_TIMEOUT_SECONDS}s)`)
ServerManager.killProcess(serverProcess)
reject(new Error("Server startup timeout"))
const { userMessage, userDetails } = toErrorMessage(
t("server.startupTimeout", { seconds: STARTUP_TIMEOUT_SECONDS }),
stderrLines,
cliPath,
)
reject(new ServerStartupError(userMessage, userDetails))
}
}, 30000)
}, STARTUP_TIMEOUT_SECONDS * 1000)
})
}
@@ -184,3 +198,48 @@ export class ServerManager {
proc.on("exit", () => clearTimeout(timer))
}
}
export class ServerStartupError extends Error {
readonly userMessage: string
readonly userDetails: string
constructor(userMessage: string, userDetails: string) {
super(userDetails)
this.name = "ServerStartupError"
this.userMessage = userMessage
this.userDetails = userDetails
}
}
function stripAnsi(str: string): string {
return str.replace(/\x1b\[[0-9;]*m/g, "")
}
export function toErrorMessage(
error: string,
stderrLines: string[],
cliPath?: string,
): {
userMessage: string
userDetails: string
error: string
} {
let lines = stderrLines.flatMap((line) => line.split("\n"))
const errorLine = lines.map(stripAnsi).find((line) => /Error:\s+/.test(line))
const userMessage = errorLine
? errorLine.match(/Error:\s+(.+)/)![1].trim()
: stripAnsi([...lines].reverse().find((line) => line.trim() !== "") ?? error).trim()
lines = [error, ...lines]
if (cliPath && cliPath.trim() !== "") {
lines = [`CLI path: ${cliPath}`, ...lines]
}
const detailsText = lines.map(stripAnsi).join("\n").trim()
return {
userMessage,
userDetails: detailsText,
error,
}
}
@@ -1,5 +1,6 @@
import { describe, it, expect } from "bun:test"
import { parseServerPort } from "../../src/services/cli-backend/server-utils"
import { toErrorMessage } from "../../src/services/cli-backend/server-manager"
describe("parseServerPort", () => {
it("parses port from standard CLI startup message", () => {
@@ -44,3 +45,61 @@ describe("parseServerPort", () => {
expect(parseServerPort(output)).toBe(3000)
})
})
describe("toErrorMessage", () => {
it("uses last non-empty stderr line as userMessage when no Error: line", () => {
const result = toErrorMessage("startup failed", ["line one", "line two", ""])
expect(result.userMessage).toBe("line two")
})
it("extracts message after Error: when present", () => {
const result = toErrorMessage("startup failed", ["some noise", "Error: something went wrong"])
expect(result.userMessage).toBe("something went wrong")
})
it("strips ANSI codes before matching Error:", () => {
const ansiError = "\x1b[91m\x1b[1mError: \x1b[0mConfig file at /path/kilo.json is not valid JSON(C):"
const result = toErrorMessage("startup failed", [ansiError])
expect(result.userMessage).toBe("Config file at /path/kilo.json is not valid JSON(C):")
})
it("finds Error: line anywhere, not just the last line", () => {
const result = toErrorMessage("startup failed", ["Error: the real problem", "subsequent noise", "more noise"])
expect(result.userMessage).toBe("the real problem")
})
it("falls back to last non-empty line when no Error: match", () => {
const result = toErrorMessage("startup failed", ["", "just some output", ""])
expect(result.userMessage).toBe("just some output")
})
it("falls back to error arg when stderr is empty", () => {
const result = toErrorMessage("startup failed", [])
expect(result.userMessage).toBe("startup failed")
})
it("strips ANSI from fallback last non-empty line", () => {
const result = toErrorMessage("startup failed", ["\x1b[31msome colored output\x1b[0m"])
expect(result.userMessage).toBe("some colored output")
})
it("includes error arg in userDetails", () => {
const result = toErrorMessage("startup failed", ["some output"])
expect(result.userDetails).toContain("startup failed")
})
it("includes CLI path in userDetails when provided", () => {
const result = toErrorMessage("startup failed", [], "/usr/local/bin/kilo")
expect(result.userDetails).toContain("CLI path: /usr/local/bin/kilo")
})
it("does not include CLI path in userDetails when not provided", () => {
const result = toErrorMessage("startup failed", [])
expect(result.userDetails).not.toContain("CLI path:")
})
it("returns original error string as error field", () => {
const result = toErrorMessage("startup failed", ["some output"])
expect(result.error).toBe("startup failed")
})
})
@@ -11,10 +11,12 @@ import { MessageList } from "./MessageList"
import { PromptInput } from "./PromptInput"
import { QuestionDock } from "./QuestionDock"
import { PermissionDock } from "./PermissionDock"
import { StartupErrorBanner } from "./StartupErrorBanner"
import { useSession } from "../../context/session"
import { useVSCode } from "../../context/vscode"
import { useLanguage } from "../../context/language"
import { useWorktreeMode } from "../../context/worktree-mode"
import { useServer } from "../../context/server"
interface ChatViewProps {
onSelectSession?: (id: string) => void
@@ -26,6 +28,7 @@ export const ChatView: Component<ChatViewProps> = (props) => {
const vscode = useVSCode()
const language = useLanguage()
const worktreeMode = useWorktreeMode()
const server = useServer()
// Show "Show Changes" only in the standalone sidebar, not inside Agent Manager
const isSidebar = () => worktreeMode === undefined
@@ -90,6 +93,9 @@ export const ChatView: Component<ChatViewProps> = (props) => {
<Show when={dock()}>
<div class="chat-input">
<Show when={server.connectionState() === "error" && server.errorMessage()}>
<StartupErrorBanner errorMessage={server.errorMessage()!} errorDetails={server.errorDetails()!} />
</Show>
<Show when={questionRequest()} keyed>
{(req) => <QuestionDock request={req} />}
</Show>
@@ -175,6 +175,16 @@ export const PromptInput: Component = () => {
(text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0) &&
!isBusy() &&
!isDisabled()
const placeholder = () => {
switch (server.connectionState()) {
case "connecting":
return language.t("prompt.placeholder.connecting")
case "error":
return language.t("prompt.placeholder.error")
default:
return language.t("prompt.placeholder.default")
}
}
const unsubscribe = vscode.onMessage((message) => {
if (message.type === "chatCompletionResult") {
@@ -557,9 +567,7 @@ export const PromptInput: Component = () => {
<textarea
ref={textareaRef}
class="prompt-input"
placeholder={
isDisabled() ? language.t("prompt.placeholder.connecting") : language.t("prompt.placeholder.default")
}
placeholder={placeholder()}
value={text()}
onInput={handleInput}
onKeyDown={handleKeyDown}
@@ -0,0 +1,50 @@
/**
* StartupErrorBanner
* Shown in the chat view when the CLI server fails to start.
*/
import { Component, createSignal, Show } from "solid-js"
import { Icon } from "@kilocode/kilo-ui/icon"
import { useLanguage } from "../../context/language"
import { useVSCode } from "../../context/vscode"
interface StartupErrorBannerProps {
errorMessage: string
errorDetails: string
}
export const StartupErrorBanner: Component<StartupErrorBannerProps> = (props) => {
const language = useLanguage()
const vscode = useVSCode()
const [expanded, setExpanded] = createSignal(false)
const retry = () => {
vscode.postMessage({ type: "retryConnection" })
}
return (
<div class="startup-error-banner">
<div class="startup-error-header" onClick={() => setExpanded((v) => !v)} role="button" aria-expanded={expanded()}>
<span class={`startup-error-chevron${expanded() ? " startup-error-chevron-expanded" : ""}`}>
<Icon name="chevron-right" size="small" />
</span>
<span class="startup-error-title">
{language.t("error.startup.title")}: <span class="startup-error-firstline">{props.errorMessage}</span>
</span>
<button
class="startup-error-retry"
onClick={(e) => {
e.stopPropagation()
retry()
}}
aria-label={language.t("common.retry")}
>
{language.t("common.retry")}
</button>
</div>
<Show when={expanded()}>
<pre class="startup-error-details">{props.errorDetails}</pre>
</Show>
</div>
)
}
@@ -11,7 +11,8 @@ interface ServerContextValue {
connectionState: Accessor<ConnectionState>
serverInfo: Accessor<ServerInfo | undefined>
extensionVersion: Accessor<string | undefined>
error: Accessor<string | undefined>
errorMessage: Accessor<string | undefined>
errorDetails: Accessor<string | undefined>
isConnected: Accessor<boolean>
profileData: Accessor<ProfileData | null>
deviceAuth: Accessor<DeviceAuthState>
@@ -31,7 +32,8 @@ export const ServerProvider: ParentComponent = (props) => {
const [connectionState, setConnectionState] = createSignal<ConnectionState>("connecting")
const [serverInfo, setServerInfo] = createSignal<ServerInfo | undefined>()
const [extensionVersion, setExtensionVersion] = createSignal<string | undefined>()
const [error, setError] = createSignal<string | undefined>()
const [errorMessage, setErrorMessage] = createSignal<string | undefined>()
const [errorDetails, setErrorDetails] = createSignal<string | undefined>()
const [profileData, setProfileData] = createSignal<ProfileData | null>(null)
const [deviceAuth, setDeviceAuth] = createSignal<DeviceAuthState>(initialDeviceAuth)
const [vscodeLanguage, setVscodeLanguage] = createSignal<string | undefined>()
@@ -46,7 +48,8 @@ export const ServerProvider: ParentComponent = (props) => {
setServerInfo(message.serverInfo)
if (message.extensionVersion) setExtensionVersion(message.extensionVersion)
setConnectionState("connected")
setError(undefined)
setErrorMessage(undefined)
setErrorDetails(undefined)
if (message.vscodeLanguage) {
setVscodeLanguage(message.vscodeLanguage)
}
@@ -66,15 +69,18 @@ export const ServerProvider: ParentComponent = (props) => {
console.log("[Kilo New] Connection state changed:", message.state)
setConnectionState(message.state)
if (message.error) {
setError(message.error)
setErrorMessage(message.userMessage ?? message.error)
setErrorDetails(message.userDetails ?? message.error)
} else if (message.state === "connected") {
setError(undefined)
setErrorMessage(undefined)
setErrorDetails(undefined)
}
break
case "error":
console.error("[Kilo New] Server error:", message.message)
setError(message.message)
setErrorMessage(message.message)
setErrorDetails(message.message)
break
case "profileData":
@@ -128,7 +134,8 @@ export const ServerProvider: ParentComponent = (props) => {
connectionState,
serverInfo,
extensionVersion,
error,
errorMessage,
errorDetails,
isConnected: () => connectionState() === "connected",
profileData,
deviceAuth,
@@ -407,6 +407,8 @@ export const dict = {
"error.globalSync.connectFailed": "تعذر الاتصال بالخادم. هل هناك خادم يعمل في `{{url}}`؟",
"error.startup.title": "فشل الاتصال بالخادم",
"error.paidModel.title": "يجب عليك تسجيل الدخول لاستخدام هذا النموذج",
"error.paidModel.description":
"سجّل الدخول أو أنشئ حسابًا للوصول إلى أكثر من 500 نموذج، واستخدم الرصيد بسعر التكلفة، أو استخدم مفتاحك الخاص.",
@@ -407,6 +407,8 @@ export const dict = {
"error.globalSync.connectFailed": "Não foi possível conectar ao servidor. Há um servidor executando em `{{url}}`?",
"error.startup.title": "Falha na conexão com o servidor",
"error.paidModel.title": "Você precisa fazer login para usar este modelo",
"error.paidModel.description":
"Faça login ou crie uma conta para acessar mais de 500 modelos, usar créditos a preço de custo ou trazer sua própria chave.",
@@ -412,6 +412,8 @@ export const dict = {
"error.globalSync.connectFailed": "Nije moguće povezati se na server. Da li server radi na `{{url}}`?",
"error.startup.title": "Povezivanje sa serverom nije uspjelo",
"error.paidModel.title": "Morate se prijaviti da biste koristili ovaj model",
"error.paidModel.description":
"Prijavite se ili kreirajte račun za pristup preko 500 modela, koristite kredite po cijeni koštanja ili donesite vlastiti ključ.",
@@ -409,6 +409,8 @@ export const dict = {
"error.globalSync.connectFailed": "Kunne ikke forbinde til server. Kører der en server på `{{url}}`?",
"error.startup.title": "Serverforbindelse mislykkedes",
"error.paidModel.title": "Du skal logge ind for at bruge denne model",
"error.paidModel.description":
"Log ind eller opret en konto for at få adgang til over 500 modeller, brug kreditter til kostpris, eller medbring din egen nøgle.",
@@ -414,6 +414,8 @@ export const dict = {
"error.globalSync.connectFailed": "Verbindung zum Server fehlgeschlagen. Läuft ein Server unter `{{url}}`?",
"error.startup.title": "Serververbindung fehlgeschlagen",
"error.paidModel.title": "Du musst dich anmelden, um dieses Modell zu nutzen",
"error.paidModel.description":
"Melde dich an oder erstelle ein Konto, um auf über 500 Modelle zuzugreifen, Credits zum Selbstkostenpreis zu nutzen oder deinen eigenen Schlüssel mitzubringen.",
@@ -411,6 +411,8 @@ export const dict = {
"error.globalSync.connectFailed": "Could not connect to server. Is there a server running at `{{url}}`?",
"error.startup.title": "Server connection failed",
"error.paidModel.title": "You need to sign in to use this model",
"error.paidModel.description":
"Sign in or create an account to access over 500 models, use credits at cost, or bring your own key.",
@@ -838,6 +840,7 @@ export const dict = {
"prompt.placeholder.connecting": "Connecting to server...",
"prompt.placeholder.default": "Type a message... (Enter to send, Shift+Enter for new line)",
"prompt.placeholder.error": "",
"context.usage.sessionCost": "Session cost",
@@ -410,6 +410,8 @@ export const dict = {
"error.globalSync.connectFailed": "No se pudo conectar al servidor. ¿Hay un servidor ejecutándose en `{{url}}`?",
"error.startup.title": "Error de conexión al servidor",
"error.paidModel.title": "Necesitas iniciar sesión para usar este modelo",
"error.paidModel.description":
"Inicia sesión o crea una cuenta para acceder a más de 500 modelos, usar créditos a precio de coste o traer tu propia clave.",
@@ -414,6 +414,8 @@ export const dict = {
"error.globalSync.connectFailed":
"Impossible de se connecter au serveur. Y a-t-il un serveur en cours d'exécution à `{{url}}` ?",
"error.startup.title": "Échec de la connexion au serveur",
"error.paidModel.title": "Vous devez vous connecter pour utiliser ce modèle",
"error.paidModel.description":
"Connectez-vous ou créez un compte pour accéder à plus de 500 modèles, utiliser des crédits au prix coûtant ou apporter votre propre clé.",
@@ -407,6 +407,8 @@ export const dict = {
"error.globalSync.connectFailed": "サーバーに接続できませんでした。`{{url}}`でサーバーが実行されていますか?",
"error.startup.title": "サーバー接続に失敗しました",
"error.paidModel.title": "このモデルを使用するにはサインインが必要です",
"error.paidModel.description":
"サインインまたはアカウントを作成して、500以上のモデルにアクセスし、原価でクレジットを使用するか、独自のキーを持ち込みましょう。",
@@ -411,6 +411,8 @@ export const dict = {
"error.globalSync.connectFailed": "서버에 연결할 수 없습니다. `{{url}}`에서 서버가 실행 중인가요?",
"error.startup.title": "서버 연결 실패",
"error.paidModel.title": "이 모델을 사용하려면 로그인이 필요합니다",
"error.paidModel.description":
"로그인하거나 계정을 만들어 500개 이상의 모델에 접근하고, 원가로 크레딧을 사용하거나, 자체 키를 가져오세요.",
@@ -411,6 +411,8 @@ export const dict = {
"error.globalSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?",
"error.startup.title": "Servertilkobling mislyktes",
"error.paidModel.title": "Du må logge inn for å bruke denne modellen",
"error.paidModel.description":
"Logg inn eller opprett en konto for å få tilgang til over 500 modeller, bruk kreditter til kostpris, eller ta med din egen nøkkel.",
@@ -409,6 +409,8 @@ export const dict = {
"error.globalSync.connectFailed": "Nie można połączyć się z serwerem. Czy serwer działa pod adresem `{{url}}`?",
"error.startup.title": "Błąd połączenia z serwerem",
"error.paidModel.title": "Musisz się zalogować, aby użyć tego modelu",
"error.paidModel.description":
"Zaloguj się lub utwórz konto, aby uzyskać dostęp do ponad 500 modeli, korzystać z kredytów po kosztach lub użyć własnego klucza.",
@@ -410,6 +410,8 @@ export const dict = {
"error.globalSync.connectFailed": "Не удалось подключиться к серверу. Запущен ли сервер по адресу `{{url}}`?",
"error.startup.title": "Ошибка подключения к серверу",
"error.paidModel.title": "Для использования этой модели необходимо войти",
"error.paidModel.description":
"Войдите или создайте аккаунт для доступа к более чем 500 моделям, используйте кредиты по себестоимости или подключите свой ключ.",
@@ -408,6 +408,8 @@ export const dict = {
"error.globalSync.connectFailed": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ มีเซิร์ฟเวอร์ทำงานอยู่ที่ `{{url}}` หรือไม่?",
"error.startup.title": "การเชื่อมต่อเซิร์ฟเวอร์ล้มเหลว",
"error.paidModel.title": "คุณต้องเข้าสู่ระบบเพื่อใช้โมเดลนี้",
"error.paidModel.description":
"เข้าสู่ระบบหรือสร้างบัญชีเพื่อเข้าถึงโมเดลกว่า 500 รายการ ใช้เครดิตในราคาทุน หรือใช้คีย์ของคุณเอง",
@@ -406,6 +406,8 @@ export const dict = {
"error.globalSync.connectFailed": "无法连接到服务器。是否有服务器正在 `{{url}}` 运行?",
"error.startup.title": "服务器连接失败",
"error.paidModel.title": "您需要登录才能使用此模型",
"error.paidModel.description": "登录或创建账户以访问500多个模型,按成本使用积分,或使用您自己的密钥。",
"error.paidModel.action": "登录",
@@ -407,6 +407,8 @@ export const dict = {
"error.globalSync.connectFailed": "無法連線到伺服器。是否有伺服器正在 `{{url}}` 執行?",
"error.startup.title": "伺服器連線失敗",
"error.paidModel.title": "您需要登入才能使用此模型",
"error.paidModel.description": "登入或建立帳戶以存取500多個模型,按成本使用點數,或使用您自己的金鑰。",
"error.paidModel.action": "登入",
@@ -101,6 +101,76 @@
border-top: 1px solid var(--vscode-panel-border);
}
/* Startup Error Banner */
.startup-error-banner {
margin: 8px 12px;
border: 1px solid var(--vscode-inputValidation-errorBorder, #f14c4c);
border-radius: 4px;
background: var(--vscode-inputValidation-errorBackground, rgba(241, 76, 76, 0.1));
font-size: 12px;
}
.startup-error-header {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 10px;
cursor: pointer;
user-select: none;
min-width: 0;
}
.startup-error-title {
flex: 1;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.startup-error-firstline {
font-weight: 400;
}
.startup-error-chevron {
display: flex;
align-items: center;
flex-shrink: 0;
transition: transform 150ms;
}
.startup-error-chevron-expanded {
transform: rotate(90deg);
}
.startup-error-retry {
background: none;
border: none;
color: var(--vscode-textLink-foreground);
cursor: pointer;
font-size: 11px;
padding: 0;
flex-shrink: 0;
}
.startup-error-retry:hover {
text-decoration: underline;
}
.startup-error-details {
margin: 0;
padding: 8px 10px;
border-top: 1px solid var(--vscode-inputValidation-errorBorder, #f14c4c);
background: var(--vscode-editor-background);
font-family: var(--vscode-editor-font-family, monospace);
font-size: 11px;
white-space: pre-wrap;
word-break: break-word;
max-height: 200px;
overflow-y: auto;
}
/* New Task Button */
.new-task-button-wrapper {
padding: 8px 12px 0;
@@ -363,6 +363,8 @@ export interface ConnectionStateMessage {
type: "connectionState"
state: ConnectionState
error?: string
userMessage?: string
userDetails?: string
}
export interface ErrorMessage {
@@ -1539,6 +1541,10 @@ export interface OpenChangesRequest {
type: "openChanges"
}
export interface RetryConnectionRequest {
type: "retryConnection"
}
// Open a sub-agent session in a read-only editor panel
export interface OpenSubAgentViewerRequest {
type: "openSubAgentViewer"
@@ -1637,6 +1643,7 @@ export type WebviewMessage =
| ApplyWorktreeDiffMessage
| EnhancePromptRequest
| OpenChangesRequest
| RetryConnectionRequest
| OpenSubAgentViewerRequest
| SetDefaultBaseBranchRequest