From 770887e96ceb5417f46dafdfe1494605145c0437 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 17:05:08 +0300 Subject: [PATCH] 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. --- packages/kilo-vscode/src/KiloProvider.ts | 28 ++++++++++--- packages/opencode/src/cli/cmd/config.ts | 40 +++++++++++++++++++ .../cli/cmd/tui/component/prompt/index.tsx | 1 + .../opencode/src/cli/cmd/tui/context/sync.tsx | 4 +- .../opencode/src/cli/cmd/tui/ui/toast.tsx | 15 +++++-- packages/opencode/src/config/config.ts | 9 +++-- packages/opencode/src/index.ts | 2 + packages/sdk/js/src/v2/gen/types.gen.ts | 1 + packages/sdk/openapi.json | 3 ++ 9 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/config.ts diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 05194727b75..55c2408bb0b 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -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 { @@ -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 diff --git a/packages/opencode/src/cli/cmd/config.ts b/packages/opencode/src/cli/cmd/config.ts new file mode 100644 index 00000000000..746946bff81 --- /dev/null +++ b/packages/opencode/src/cli/cmd/config.ts @@ -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() {}, +}) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 68660bb9ed1..f8935c321bb 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -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, diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 36119ccd7a2..aa4766bf959 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -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(() => {}), diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx index 36095580fb0..32b8deac14d 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -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) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 152fb3fe21b..36d1b550559 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -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 // 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") { diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 688bc25b742..0b5bc16bc36 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -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) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index e8f9bd24bfc..44dfca17bb5 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2479,6 +2479,7 @@ export type ConfigWarningsResponses = { 200: Array<{ path: string message: string + detail?: string }> } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index b8192c91aa8..65994bb0b9e 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1008,6 +1008,9 @@ }, "message": { "type": "string" + }, + "detail": { + "type": "string" } }, "required": ["path", "message"]