mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(cli): improve config warning UX across CLI, TUI, and VS Code
Separate verbose parse details into a new Warning.detail field so toast messages stay short while 'kilo config check' and VS Code's Show Details action expose the full diagnostic. Make the TUI config-warning toast persistent (dismissed on first prompt submit) and fire VS Code's checkConfigWarnings() independently so prior failures in the SSE handler cannot suppress it. Add 'kilo config check' subcommand that prints all warnings with color and exits non-zero.
This commit is contained in:
@@ -1048,6 +1048,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.postMessage({ type: "connectionState", state })
|
||||
|
||||
if (state === "connected") {
|
||||
// kilocode_change start - fire config warnings independently so a failure in the
|
||||
// sequential await chain doesn't prevent warnings from being shown
|
||||
void this.checkConfigWarnings()
|
||||
// kilocode_change end
|
||||
try {
|
||||
// Profile fetch is best-effort — returns 401 when user isn't logged into gateway.
|
||||
const sdkClient = this.client
|
||||
@@ -1059,7 +1063,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
await this.flushPendingSessionRefresh("sse-connected")
|
||||
await fetchAndSendPendingPermissions(this.permissionCtx)
|
||||
await fetchAndSendPendingQuestions(this.questionCtx)
|
||||
await this.checkConfigWarnings() // kilocode_change
|
||||
} catch (error) {
|
||||
console.error("[Kilo New] KiloProvider: ❌ Failed during connected state handling:", error)
|
||||
this.postMessage({
|
||||
@@ -1951,7 +1954,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
// kilocode_change start
|
||||
/**
|
||||
* Fetch config warnings from the server and display them as VS Code warning messages.
|
||||
* Fetch config warnings from the server and display a single consolidated
|
||||
* VS Code warning with a "Show Details" action button.
|
||||
* Only shown once per provider lifecycle (flag resets on dispose/re-create, not on SSE reconnect).
|
||||
*/
|
||||
private async checkConfigWarnings(): Promise<void> {
|
||||
@@ -1962,11 +1966,23 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
const list = result?.data ?? []
|
||||
if (list.length === 0) return
|
||||
this.configWarningsShown = true
|
||||
for (const warning of list) {
|
||||
vscode.window.showWarningMessage(`Config: ${warning.message}`)
|
||||
|
||||
const first = list[0]!
|
||||
const summary = list.length === 1 ? first.message : `${first.message} (and ${list.length - 1} more)`
|
||||
|
||||
const action = await vscode.window.showWarningMessage(`Config: ${summary}`, "Show Details")
|
||||
if (action === "Show Details") {
|
||||
const lines = list.map((w) => {
|
||||
const base = `${w.path}\n ${w.message}`
|
||||
return w.detail ? `${base}\n ${w.detail}` : base
|
||||
})
|
||||
const channel = vscode.window.createOutputChannel("Kilo Config Warnings")
|
||||
channel.clear()
|
||||
channel.appendLine(lines.join("\n\n"))
|
||||
channel.show()
|
||||
}
|
||||
} catch {
|
||||
// Endpoint may not exist on older CLI versions — silently ignore
|
||||
} catch (err) {
|
||||
console.warn("[Kilo New] KiloProvider: checkConfigWarnings failed:", err)
|
||||
}
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// kilocode_change - new file
|
||||
import { EOL } from "os"
|
||||
import { Config } from "../../config/config"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { cmd } from "./cmd"
|
||||
import { UI } from "../ui"
|
||||
|
||||
export const ConfigCommand = cmd({
|
||||
command: "config",
|
||||
describe: "configuration tools",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.command({
|
||||
command: "check",
|
||||
describe: "check configuration for warnings and errors",
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const list = await Config.warnings()
|
||||
if (list.length === 0) {
|
||||
process.stdout.write("No config warnings." + EOL)
|
||||
return
|
||||
}
|
||||
const S = UI.Style
|
||||
for (const warning of list) {
|
||||
process.stderr.write(S.TEXT_WARNING_BOLD + warning.path + S.TEXT_NORMAL + EOL)
|
||||
process.stderr.write(" " + warning.message + EOL)
|
||||
if (warning.detail) {
|
||||
for (const line of warning.detail.split("\n")) {
|
||||
process.stderr.write(" " + S.TEXT_DIM + line + S.TEXT_NORMAL + EOL)
|
||||
}
|
||||
}
|
||||
process.stderr.write(EOL)
|
||||
}
|
||||
process.exitCode = 1
|
||||
})
|
||||
},
|
||||
})
|
||||
.demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
@@ -636,6 +636,7 @@ export function Prompt(props: PromptProps) {
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
toast.dismiss() // kilocode_change - dismiss persistent config warning on first submit
|
||||
history.append({
|
||||
...store.prompt,
|
||||
mode: currentMode,
|
||||
|
||||
@@ -466,7 +466,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
sdk.client.vcs.get().then((x) => setStore("vcs", reconcile(x.data))),
|
||||
sdk.client.path.get().then((x) => setStore("path", reconcile(x.data!))),
|
||||
syncWorkspaces(),
|
||||
// kilocode_change start - show config warnings as toasts
|
||||
// kilocode_change start - show config warnings as persistent toast
|
||||
sdk.client.config
|
||||
.warnings()
|
||||
.then((x) => {
|
||||
@@ -478,7 +478,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
title: "Config Warning",
|
||||
message: first.message + suffix,
|
||||
variant: "warning",
|
||||
duration: 8000,
|
||||
duration: 0,
|
||||
})
|
||||
})
|
||||
.catch(() => {}),
|
||||
|
||||
@@ -60,9 +60,18 @@ function init() {
|
||||
const { duration, ...currentToast } = parsedOptions
|
||||
setStore("currentToast", currentToast)
|
||||
if (timeoutHandle) clearTimeout(timeoutHandle)
|
||||
timeoutHandle = setTimeout(() => {
|
||||
setStore("currentToast", null)
|
||||
}, duration).unref()
|
||||
timeoutHandle = null
|
||||
if (duration && duration > 0) {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
setStore("currentToast", null)
|
||||
timeoutHandle = null
|
||||
}, duration).unref()
|
||||
}
|
||||
},
|
||||
dismiss() {
|
||||
if (timeoutHandle) clearTimeout(timeoutHandle)
|
||||
timeoutHandle = null
|
||||
setStore("currentToast", null)
|
||||
},
|
||||
error: (err: any) => {
|
||||
if (err instanceof Error)
|
||||
|
||||
@@ -56,6 +56,7 @@ export namespace Config {
|
||||
export const Warning = z.object({
|
||||
path: z.string(),
|
||||
message: z.string(),
|
||||
detail: z.string().optional(),
|
||||
})
|
||||
export type Warning = z.infer<typeof Warning>
|
||||
// kilocode_change end
|
||||
@@ -527,8 +528,8 @@ export namespace Config {
|
||||
if (ConfigPaths.JsonError.isInstance(err))
|
||||
return {
|
||||
path: err.data.path,
|
||||
message:
|
||||
`Config file at ${err.data.path} is not valid JSON(C)` + (err.data.message ? `: ${err.data.message}` : ""),
|
||||
message: `Config file at ${err.data.path} is not valid JSON(C)`,
|
||||
detail: err.data.message || undefined,
|
||||
}
|
||||
if (ConfigPaths.InvalidError.isInstance(err)) {
|
||||
const text = err.data.issues ? detail(err.data.issues) : err.data.message
|
||||
@@ -560,9 +561,9 @@ export namespace Config {
|
||||
warnings?: Warning[],
|
||||
) {
|
||||
const text = detail(issues)
|
||||
const message = text ? `Config file at ${item} is invalid: ${text}` : `Config file at ${item} is invalid`
|
||||
const message = `Config file at ${item} is invalid`
|
||||
const err = new InvalidError({ path: item, issues }, { cause })
|
||||
if (warnings) warnings.push({ path: item, message })
|
||||
if (warnings) warnings.push({ path: item, message, detail: text || undefined })
|
||||
const { Session } = await import("@/session")
|
||||
Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
|
||||
if (kind === "command") {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { FormatError } from "./cli/error"
|
||||
import { ServeCommand } from "./cli/cmd/serve"
|
||||
import { WorkspaceServeCommand } from "./cli/cmd/workspace-serve"
|
||||
import { Filesystem } from "./util/filesystem"
|
||||
import { ConfigCommand as ConfigCLICommand } from "./cli/cmd/config" // kilocode_change
|
||||
import { DebugCommand } from "./cli/cmd/debug"
|
||||
import { StatsCommand } from "./cli/cmd/stats"
|
||||
import { McpCommand } from "./cli/cmd/mcp"
|
||||
@@ -195,6 +196,7 @@ let cli = yargs(hideBin(process.argv))
|
||||
.command(SessionCommand)
|
||||
.command(RemoteCommand) // kilocode_change
|
||||
.command(DbCommand)
|
||||
.command(ConfigCLICommand) // kilocode_change
|
||||
|
||||
if (Installation.isLocal()) {
|
||||
cli = cli.command(WorkspaceServeCommand)
|
||||
|
||||
@@ -2479,6 +2479,7 @@ export type ConfigWarningsResponses = {
|
||||
200: Array<{
|
||||
path: string
|
||||
message: string
|
||||
detail?: string
|
||||
}>
|
||||
}
|
||||
|
||||
|
||||
@@ -1008,6 +1008,9 @@
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"detail": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["path", "message"]
|
||||
|
||||
Reference in New Issue
Block a user