From dfeb7bf722c7000a2d7a318ee5f56a36908a0173 Mon Sep 17 00:00:00 2001 From: Thomas Boom Date: Tue, 24 Mar 2026 21:01:28 +0100 Subject: [PATCH 01/35] Update bonus credits and AI model versions in README Updated the bonus credits offer and AI model versions in the README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31d9e5cf348..b1557add3c9 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - ⚡ Inline autocomplete suggestions - 🤖 Latest AI models - 🎁 API keys optional -- 💡 **Get $20 in bonus credits when you top-up for the first time** Credits can be used with 500+ models like Gemini 3.1 Pro, Claude 4.6 Sonnet & Opus, and GPT-5.2 +- 💡 **Get $20 in bonus credits when you top-up for the first time** Credits can be used with 500+ models like Gemini 3.1 Pro, Claude 4.6 Sonnet & Opus, and GPT-5.4 ## Quick Links @@ -38,7 +38,7 @@ ## Get Started in Visual Studio Code 1. Install the Kilo Code extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=kilocode.Kilo-Code). -2. Create your account to access 500+ cutting-edge AI models including Gemini 3 Pro, Claude 4.5 Sonnet & Opus, and GPT-5 – with transparent pricing that matches provider rates exactly. +2. Create your account to access 500+ cutting-edge AI models including Gemini 3.1 Pro, Claude 4.6 Sonnet & Opus, and GPT-5.4 – with transparent pricing that matches provider rates exactly. 3. Start coding with AI that adapts to your workflow. Watch our quick-start guide to see Kilo in action: [![Watch the video](https://img.youtube.com/vi/pqGfYXgrhig/maxresdefault.jpg)](https://youtu.be/pqGfYXgrhig) From 9cafa6f8b9aadb17be63977617b94b811b358210 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 10:48:30 +0300 Subject: [PATCH 02/35] test(cli): add regression tests for config resilience Add 4 tests that reproduce the crash when agent or command markdown files have valid YAML but invalid Zod schema values (e.g. mode: banana). These currently pass against the fix that will follow. --- .../test/kilocode/config-resilience.test.ts | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 packages/opencode/test/kilocode/config-resilience.test.ts diff --git a/packages/opencode/test/kilocode/config-resilience.test.ts b/packages/opencode/test/kilocode/config-resilience.test.ts new file mode 100644 index 00000000000..a5d013c746c --- /dev/null +++ b/packages/opencode/test/kilocode/config-resilience.test.ts @@ -0,0 +1,155 @@ +import { afterEach, describe, expect, test } from "bun:test" +import path from "path" +import { Bus } from "../../src/bus" +import { Config } from "../../src/config/config" +import { Instance } from "../../src/project/instance" +import { Filesystem } from "../../src/util/filesystem" +import { tmpdir } from "../fixture/fixture" + +afterEach(async () => { + await Instance.disposeAll() + Config.global.reset() +}) + +describe("config resilience", () => { + test("skips invalid agent markdown configs", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, ".kilo", "agent", "skip.md"), + `--- +mode: "banana" +--- +Broken agent prompt`, + ) + await Filesystem.write( + path.join(dir, ".kilo", "agent", "keep.md"), + `--- +model: test/model +--- +Valid agent prompt`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const cfg = await Config.get() + + expect(cfg.agent?.["skip"]).toBeUndefined() + expect(cfg.agent?.["keep"]).toMatchObject({ + name: "keep", + model: "test/model", + prompt: "Valid agent prompt", + }) + }, + }) + }) + + test("publishes an error for invalid agent markdown configs", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, ".kilo", "agent", "skip.md"), + `--- +mode: "banana" +--- +Broken agent prompt`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const seen: Array<{ type: string; properties: { error: { name: string; data: { message: string } } } }> = [] + const unsub = Bus.subscribeAll((event) => { + if (event.type === "session.error") seen.push(event) + }) + + await Config.get() + unsub() + + expect( + seen.some( + (item) => + item.properties.error.name === "UnknownError" && + item.properties.error.data.message.includes("skip.md") && + item.properties.error.data.message.includes("mode"), + ), + ).toBe(true) + }, + }) + }) + + test("skips invalid command markdown configs", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, ".kilo", "command", "skip.md"), + `--- +subtask: "banana" +--- +Broken command template`, + ) + await Filesystem.write( + path.join(dir, ".kilo", "command", "keep.md"), + `--- +description: Valid command +--- +Valid command template`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const cfg = await Config.get() + + expect(cfg.command?.["skip"]).toBeUndefined() + expect(cfg.command?.["keep"]).toEqual({ + description: "Valid command", + template: "Valid command template", + }) + }, + }) + }) + + test("publishes an error for invalid command markdown configs", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, ".kilo", "command", "skip.md"), + `--- +subtask: "banana" +--- +Broken command template`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const seen: Array<{ type: string; properties: { error: { name: string; data: { message: string } } } }> = [] + const unsub = Bus.subscribeAll((event) => { + if (event.type === "session.error") seen.push(event) + }) + + await Config.get() + unsub() + + expect( + seen.some( + (item) => + item.properties.error.name === "UnknownError" && + item.properties.error.data.message.includes("skip.md") && + item.properties.error.data.message.includes("subtask"), + ), + ).toBe(true) + }, + }) + }) +}) From e3a5b69054a490a6ac32363859484f3e8bb32c96 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 10:48:37 +0300 Subject: [PATCH 03/35] fix(cli): skip invalid agent/command configs instead of crashing Replace throw with log-and-skip in loadAgent() and loadCommand() when Zod validation fails on markdown config files. Publish a session.error bus event so the UI can surface the problem. Add a try/catch safety net around the per-directory config loading loop in stateInit(). --- packages/opencode/src/config/config.ts | 51 +++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 5b3e3b28de4..60d80eb3f8a 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -30,6 +30,7 @@ import { Installation } from "@/installation" import { ConfigMarkdown } from "./markdown" import { constants, existsSync } from "fs" import { Bus } from "@/bus" +import { BusEvent } from "@/bus/bus-event" // kilocode_change import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" import { Glob } from "../util/glob" @@ -282,10 +283,16 @@ export namespace Config { }), ) - result.command = mergeDeep(result.command ?? {}, await loadCommand(dir)) - result.agent = mergeDeep(result.agent, await loadAgent(dir)) - result.agent = mergeDeep(result.agent, await loadMode(dir)) - result.plugin.push(...(await loadPlugin(dir))) + // kilocode_change start + try { + result.command = mergeDeep(result.command ?? {}, await loadCommand(dir)) + result.agent = mergeDeep(result.agent, await loadAgent(dir)) + result.agent = mergeDeep(result.agent, await loadMode(dir)) + result.plugin.push(...(await loadPlugin(dir))) + } catch (err: unknown) { + log.error("failed to load config directory", { dir, err }) + } + // kilocode_change end } // Inline config content overrides all non-managed config sources. @@ -463,6 +470,34 @@ export namespace Config { return ext.length ? file.slice(0, -ext.length) : file } + // kilocode_change start + // Local event definition that matches Session.Event.Error's type string. + // Avoids importing @/session during config init (which causes a circular dependency). + const SessionError = BusEvent.define("session.error", z.object({ error: z.any() })) + + function detail(issues: z.core.$ZodIssue[]) { + return issues + .map((issue) => { + const loc = issue.path.map(String).join(".") + if (!loc) return issue.message + return `${loc}: ${issue.message}` + }) + .join("\n") + } + + async function invalid(kind: "agent" | "command", item: string, issues: z.core.$ZodIssue[], cause: Error) { + const text = detail(issues) + const message = text ? `Config file at ${item} is invalid: ${text}` : `Config file at ${item} is invalid` + Bus.publish(SessionError, { error: new NamedError.Unknown({ message }).toObject() }) + const err = new InvalidError({ path: item, issues }, { cause }) + if (kind === "command") { + log.error("failed to load command", { command: item, err }) + return + } + log.error("failed to load agent", { agent: item, err }) + } + // kilocode_change end + async function loadCommand(dir: string) { const result: Record = {} for (const item of await Glob.scan("{command,commands}/**/*.md", { @@ -505,7 +540,9 @@ export namespace Config { result[config.name] = parsed.data continue } - throw new InvalidError({ path: item, issues: parsed.error.issues }, { cause: parsed.error }) + // kilocode_change start + await invalid("command", item, parsed.error.issues, parsed.error) + // kilocode_change end } return result } @@ -555,7 +592,9 @@ export namespace Config { result[config.name] = parsed.data continue } - throw new InvalidError({ path: item, issues: parsed.error.issues }, { cause: parsed.error }) + // kilocode_change start + await invalid("agent", item, parsed.error.issues, parsed.error) + // kilocode_change end } return result } From 6c98dd062c3a6fd85161ba3d09523beffa6c0362 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 11:25:00 +0300 Subject: [PATCH 04/35] fix(cli): remove unscoped session.error publish --- packages/opencode/src/config/config.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 60d80eb3f8a..a091703982e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -30,7 +30,6 @@ import { Installation } from "@/installation" import { ConfigMarkdown } from "./markdown" import { constants, existsSync } from "fs" import { Bus } from "@/bus" -import { BusEvent } from "@/bus/bus-event" // kilocode_change import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" import { Glob } from "../util/glob" @@ -471,10 +470,6 @@ export namespace Config { } // kilocode_change start - // Local event definition that matches Session.Event.Error's type string. - // Avoids importing @/session during config init (which causes a circular dependency). - const SessionError = BusEvent.define("session.error", z.object({ error: z.any() })) - function detail(issues: z.core.$ZodIssue[]) { return issues .map((issue) => { @@ -488,13 +483,12 @@ export namespace Config { async function invalid(kind: "agent" | "command", item: string, issues: z.core.$ZodIssue[], cause: Error) { const text = detail(issues) const message = text ? `Config file at ${item} is invalid: ${text}` : `Config file at ${item} is invalid` - Bus.publish(SessionError, { error: new NamedError.Unknown({ message }).toObject() }) const err = new InvalidError({ path: item, issues }, { cause }) if (kind === "command") { - log.error("failed to load command", { command: item, err }) + log.error("failed to load command", { command: item, err, message }) return } - log.error("failed to load agent", { agent: item, err }) + log.error("failed to load agent", { agent: item, err, message }) } // kilocode_change end From a8a2e3b8725c1c7dc258aa941e9e07de839f6d79 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 11:38:13 +0300 Subject: [PATCH 05/35] fix(cli): surface schema validation errors to user --- packages/opencode/src/config/config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index a091703982e..7fabc3481e6 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -484,6 +484,8 @@ export namespace Config { const text = detail(issues) const message = text ? `Config file at ${item} is invalid: ${text}` : `Config file at ${item} is invalid` const err = new InvalidError({ path: item, issues }, { cause }) + const { Session } = await import("@/session") + Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) if (kind === "command") { log.error("failed to load command", { command: item, err, message }) return From f2dea0a67759ff073f3a881082a3404f9bb42f52 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 10:49:46 +0300 Subject: [PATCH 06/35] docs(cli): add legacy paths to kilo-config skill The built-in kilo-config skill only documented modern config paths (.kilo/, ~/.config/kilo/), but the CLI still loads legacy directories (.kilocode/, .opencode/) at every session. Users with configs in legacy locations got no guidance from the agent. Expand the Config File Locations section to document all scanned directories, glob patterns with plural variants, legacy config filenames, and environment variable overrides. --- .../src/kilocode/skills/kilo-config.md | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index 70289d08dcd..e34a77f7518 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -4,7 +4,7 @@ All config lives in `kilo.json` (or `kilo.jsonc`). Precedence low-to-high: remot ## Commands (`.kilo/command/*.md`) -Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`. +Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`. Also loaded from `.kilocode/` and `.opencode/` directories (legacy), and plural `commands/` variants. See Config File Locations for the full search order. ```yaml --- @@ -22,6 +22,8 @@ Template variables: `$1`-`$N` (positional args), `$ARGUMENTS` (full string), `@f ## Agents (`.kilo/agent/*.md`) +Also loaded from `.kilocode/` and `.opencode/` directories (legacy), and plural `agents/` variants. + ```yaml --- description: When to use this agent @@ -127,7 +129,7 @@ Additional skill directories and remote URLs: } ``` -Skills are markdown files at `skills//SKILL.md` with `name` and `description` in frontmatter. +Skills are markdown files at `skills//SKILL.md` (or `skill//SKILL.md`) with `name` and `description` in frontmatter. Discovered inside `.kilo/`, `.kilocode/`, and `.opencode/` directories. ## Other Top-Level Fields @@ -200,12 +202,49 @@ Toggle notifications, Toggle animations, Toggle diff wrapping, Toggle sidebar (` ## Config File Locations -| Scope | Path | -| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Project | `./kilo.json`, `./.kilo/kilo.json` | -| Global | `~/.config/kilo/kilo.json` | -| Managed | Linux: `/etc/kilo/kilo.json`, macOS: `/Library/Application Support/kilo/kilo.json`, Windows: `%ProgramData%\kilo\kilo.json` (enterprise, highest priority) | -| Commands | `.kilo/command/*.md` (project), `~/.config/kilo/command/*.md` (global) | -| Agents | `.kilo/agent/*.md` (project), `~/.config/kilo/agent/*.md` (global) | -| Skills | `.kilo/skill/*/SKILL.md`, `.kilo/skills/*/SKILL.md` | -| Instructions | `AGENTS.md`, `.kilo/instructions.md`, glob patterns from `instructions` | +### Config files (kilo.json) + +| Scope | Path | +| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Project | `./kilo.json`, `./kilo.jsonc`, `./opencode.json` (legacy), `./opencode.jsonc` (legacy) | +| Global | `~/.config/kilo/kilo.json`, `~/.config/kilo/kilo.jsonc`, `~/.config/kilo/opencode.json` (legacy), `~/.config/kilo/config.json` (legacy) | +| Managed | Linux: `/etc/kilo/kilo.json`, macOS: `/Library/Application Support/kilo/kilo.json`, Windows: `%ProgramData%\kilo\kilo.json` (enterprise, highest priority) | + +Each config directory (`.kilo/`, `.kilocode/`, `.opencode/`) can also contain `kilo.json`, `kilo.jsonc`, `opencode.json`, or `opencode.jsonc`. + +### Config directories + +Three directory names are scanned: `.kilo` (modern), `.kilocode` (legacy), `.opencode` (legacy). All three are checked at each level: + +- **Project**: walks up from CWD to the git worktree root, checking for all three at each directory level +- **Home**: `~/.kilo/`, `~/.kilocode/`, `~/.opencode/` +- **XDG global**: `~/.config/kilo/` (always loaded, lowest file-based precedence) + +### Commands, agents, modes, plugins + +Glob patterns run inside every discovered config directory (including legacy): + +| Type | Pattern | +| ------- | ---------------------------- | +| Command | `{command,commands}/**/*.md` | +| Agent | `{agent,agents}/**/*.md` | +| Mode | `{mode,modes}/*.md` | +| Plugin | `{plugin,plugins}/*.{ts,js}` | + +Example: `~/.config/kilo/command/*.md` (modern global), `~/.kilocode/command/*.md` (legacy global), `.opencode/commands/*.md` (legacy project) all load commands. + +### Skills and instructions + +| Scope | Path | +| ------------ | ----------------------------------------------------------------------- | +| Skills | `{skill,skills}//SKILL.md` inside any config directory | +| Instructions | `AGENTS.md`, `.kilo/instructions.md`, glob patterns from `instructions` | + +### Environment variable overrides + +| Variable | Description | +| ----------------------------- | ---------------------------------------------------------------- | +| `KILO_CONFIG` | Path to an additional config file (loaded after global) | +| `KILO_CONFIG_DIR` | Path to an additional config directory (appended to search list) | +| `KILO_CONFIG_CONTENT` | Inline JSON config string (high precedence, after project dirs) | +| `KILO_DISABLE_PROJECT_CONFIG` | Skip project-level `.kilo`/`.kilocode`/`.opencode` directories | From 6a7c296d341291d8a773846df9846cc0ba62d18b Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 10:59:54 +0300 Subject: [PATCH 07/35] fix(cli): widen kilo-config skill to cover path location questions The skill description and opening paragraph now explicitly mention locating config/command/agent/skill paths across global, project, and legacy directories (.kilo/, .kilocode/, .opencode/, ~/.config/kilo/). This ensures the agent loads the skill for 'where is this command?' prompts, not just configure/customize prompts. --- packages/opencode/src/kilocode/skills/builtin.ts | 2 +- packages/opencode/src/kilocode/skills/kilo-config.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/kilocode/skills/builtin.ts b/packages/opencode/src/kilocode/skills/builtin.ts index 9172a39cf95..96accd8f5ad 100644 --- a/packages/opencode/src/kilocode/skills/builtin.ts +++ b/packages/opencode/src/kilocode/skills/builtin.ts @@ -15,7 +15,7 @@ export const BUILTIN_SKILLS: BuiltinSkill[] = [ { name: "kilo-config", description: - "Guide for configuring Kilo CLI: commands, agents, MCP servers, skills, permissions, instructions, plugins, providers, all kilo.json fields, and TUI settings (themes, appearance, keybinds, ctrl+p commands). Use when the user asks about configuring, customizing, or changing settings in Kilo.", + "Guide for configuring Kilo CLI and locating config, command, agent, and skill paths (global, project, legacy), plus MCP servers, permissions, instructions, plugins, providers, kilo.json fields, and TUI settings (themes, appearance, keybinds, ctrl+p commands). Use when the user asks about configuring Kilo, where it loads things from, or how to change settings.", content: KILO_CONFIG, }, ] diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index e34a77f7518..cc2540678f8 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -2,6 +2,8 @@ All config lives in `kilo.json` (or `kilo.jsonc`). Precedence low-to-high: remote well-known, global (`~/.config/kilo/kilo.json`), env `KILO_CONFIG`, project `./kilo.json`, `.kilo/kilo.json`, `KILO_CONFIG_CONTENT`, managed (see Config File Locations). Deep-merged; later wins. +This also covers where Kilo looks for config files, commands, agents, and skills across project, global, and legacy paths such as `.kilo/`, `.kilocode/`, `.opencode/`, and `~/.config/kilo/`. + ## Commands (`.kilo/command/*.md`) Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`. Also loaded from `.kilocode/` and `.opencode/` directories (legacy), and plural `commands/` variants. See Config File Locations for the full search order. From cafccaf68760f59eb6a4a754fd1b7533b6ba311a Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:10:10 +0300 Subject: [PATCH 08/35] fix(cli): add named-command lookup guidance to kilo-config skill The skill loaded but the agent still only searched the repo root when asked 'where is /review-pr?'. Add an explicit procedural subsection that tells the agent to search ~/.config/kilo/, ~/.kilo/, ~/.kilocode/, and ~/.opencode/ with explicit path arguments before falling back to project directories. Add a regression test verifying the guidance is present in the builtin skill output. --- .../src/kilocode/skills/kilo-config.md | 19 ++++++++++- packages/opencode/test/tool/skill.test.ts | 32 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index cc2540678f8..e8e763a2e16 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -6,7 +6,7 @@ This also covers where Kilo looks for config files, commands, agents, and skills ## Commands (`.kilo/command/*.md`) -Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`. Also loaded from `.kilocode/` and `.opencode/` directories (legacy), and plural `commands/` variants. See Config File Locations for the full search order. +Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`. Commands can live in `.kilo/`, `.kilocode/`, `.opencode/`, and global config roots, with both `command/` and `commands/` directory names supported. See Config File Locations for the full search order. ```yaml --- @@ -22,6 +22,23 @@ Reference files with @file and shell output with !`cmd`. Template variables: `$1`-`$N` (positional args), `$ARGUMENTS` (full string), `@file` (file contents), `` !`cmd` `` (shell output). +### Finding a named command + +When asked where `/name` lives, do not search only the repo root. Search these roots explicitly, and use an explicit search `path` for each one: + +1. `~/.config/kilo/` +2. `~/.kilo/` +3. `~/.kilocode/` +4. `~/.opencode/` +5. project `.kilo/`, `.kilocode/`, and `.opencode/` directories from the current working directory up to the worktree root + +Use exact patterns first: + +- `**/command/.md` +- `**/commands/.md` + +If found, return the full path. If not found in those roots, explain that the command is not present in the loaded config paths. + ## Agents (`.kilo/agent/*.md`) Also loaded from `.kilocode/` and `.opencode/` directories (legacy), and plural `agents/` variants. diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index 70649dd9016..0fa1f13d4be 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -109,4 +109,36 @@ Use this skill. process.env.KILO_TEST_HOME = home } }) + + test("built-in kilo-config includes named command lookup guidance", async () => { + await using tmp = await tmpdir({ git: true }) + + const home = process.env.KILO_TEST_HOME + process.env.KILO_TEST_HOME = tmp.path + + try { + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const tool = await SkillTool.init() + const ctx: Tool.Context = { + ...baseCtx, + ask: async () => {}, + } + + const result = await tool.execute({ name: "kilo-config" }, ctx) + + expect(tool.description).toContain("where it loads things from") + expect(result.metadata.dir).toBe("builtin") + expect(result.output).toContain("### Finding a named command") + expect(result.output).toContain("`~/.config/kilo/`") + expect(result.output).toContain("`~/.kilocode/`") + expect(result.output).toContain("`**/command/.md`") + expect(result.output).toContain("explicit search `path`") + }, + }) + } finally { + process.env.KILO_TEST_HOME = home + } + }) }) From 6a91d2a6aab351a2ff38e87512ae02181a9bc141 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:16:06 +0300 Subject: [PATCH 09/35] docs(cli): add missing opencode.jsonc to global paths --- packages/opencode/src/kilocode/skills/kilo-config.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index e8e763a2e16..972542739a5 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -223,11 +223,11 @@ Toggle notifications, Toggle animations, Toggle diff wrapping, Toggle sidebar (` ### Config files (kilo.json) -| Scope | Path | -| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Project | `./kilo.json`, `./kilo.jsonc`, `./opencode.json` (legacy), `./opencode.jsonc` (legacy) | -| Global | `~/.config/kilo/kilo.json`, `~/.config/kilo/kilo.jsonc`, `~/.config/kilo/opencode.json` (legacy), `~/.config/kilo/config.json` (legacy) | -| Managed | Linux: `/etc/kilo/kilo.json`, macOS: `/Library/Application Support/kilo/kilo.json`, Windows: `%ProgramData%\kilo\kilo.json` (enterprise, highest priority) | +| Scope | Path | +| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Project | `./kilo.json`, `./kilo.jsonc`, `./opencode.json` (legacy), `./opencode.jsonc` (legacy) | +| Global | `~/.config/kilo/kilo.json`, `~/.config/kilo/kilo.jsonc`, `~/.config/kilo/opencode.json` (legacy), `~/.config/kilo/opencode.jsonc` (legacy), `~/.config/kilo/config.json` (legacy) | +| Managed | Linux: `/etc/kilo/kilo.json`, macOS: `/Library/Application Support/kilo/kilo.json`, Windows: `%ProgramData%\kilo\kilo.json` (enterprise, highest priority) | Each config directory (`.kilo/`, `.kilocode/`, `.opencode/`) can also contain `kilo.json`, `kilo.jsonc`, `opencode.json`, or `opencode.jsonc`. From 2302f7f67acc46b48207059e76cdf2699a10e2e7 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:16:40 +0300 Subject: [PATCH 10/35] docs(cli): fix instructions auto-discovery list --- packages/opencode/src/kilocode/skills/kilo-config.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index 972542739a5..7c2d52b9c0d 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -254,10 +254,10 @@ Example: `~/.config/kilo/command/*.md` (modern global), `~/.kilocode/command/*.m ### Skills and instructions -| Scope | Path | -| ------------ | ----------------------------------------------------------------------- | -| Skills | `{skill,skills}//SKILL.md` inside any config directory | -| Instructions | `AGENTS.md`, `.kilo/instructions.md`, glob patterns from `instructions` | +| Scope | Path | +| ------------ | ------------------------------------------------------------------------ | +| Skills | `{skill,skills}//SKILL.md` inside any config directory | +| Instructions | `AGENTS.md`, `CLAUDE.md`, glob patterns from `instructions` config field | ### Environment variable overrides From b86265102389ebe6a704744f535d3a0988cec7a1 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:17:15 +0300 Subject: [PATCH 11/35] docs(cli): list all managed config files --- packages/opencode/src/kilocode/skills/kilo-config.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index 7c2d52b9c0d..edb460c2eb5 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -223,11 +223,11 @@ Toggle notifications, Toggle animations, Toggle diff wrapping, Toggle sidebar (` ### Config files (kilo.json) -| Scope | Path | -| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Project | `./kilo.json`, `./kilo.jsonc`, `./opencode.json` (legacy), `./opencode.jsonc` (legacy) | -| Global | `~/.config/kilo/kilo.json`, `~/.config/kilo/kilo.jsonc`, `~/.config/kilo/opencode.json` (legacy), `~/.config/kilo/opencode.jsonc` (legacy), `~/.config/kilo/config.json` (legacy) | -| Managed | Linux: `/etc/kilo/kilo.json`, macOS: `/Library/Application Support/kilo/kilo.json`, Windows: `%ProgramData%\kilo\kilo.json` (enterprise, highest priority) | +| Scope | Path | +| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Project | `./kilo.json`, `./kilo.jsonc`, `./opencode.json` (legacy), `./opencode.jsonc` (legacy) | +| Global | `~/.config/kilo/kilo.json`, `~/.config/kilo/kilo.jsonc`, `~/.config/kilo/opencode.json` (legacy), `~/.config/kilo/opencode.jsonc` (legacy), `~/.config/kilo/config.json` (legacy) | +| Managed | Linux: `/etc/kilo/`, macOS: `/Library/Application Support/kilo/`, Windows: `%ProgramData%\kilo\` — loads `kilo.json`, `kilo.jsonc`, `opencode.json`, `opencode.jsonc` (enterprise, highest priority) | Each config directory (`.kilo/`, `.kilocode/`, `.opencode/`) can also contain `kilo.json`, `kilo.jsonc`, `opencode.json`, or `opencode.jsonc`. From eb1494f122c963fb27671d01e75c46bc65604a95 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:17:52 +0300 Subject: [PATCH 12/35] docs(cli): widen KILO_DISABLE_PROJECT_CONFIG desc --- packages/opencode/src/kilocode/skills/kilo-config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index edb460c2eb5..df435e5815c 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -266,4 +266,4 @@ Example: `~/.config/kilo/command/*.md` (modern global), `~/.kilocode/command/*.m | `KILO_CONFIG` | Path to an additional config file (loaded after global) | | `KILO_CONFIG_DIR` | Path to an additional config directory (appended to search list) | | `KILO_CONFIG_CONTENT` | Inline JSON config string (high precedence, after project dirs) | -| `KILO_DISABLE_PROJECT_CONFIG` | Skip project-level `.kilo`/`.kilocode`/`.opencode` directories | +| `KILO_DISABLE_PROJECT_CONFIG` | Skip all project-level config (files and directories) | From 5a9520d3f0a0e5e757d54a5d53ba2ea3056b0cea Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:26:46 +0300 Subject: [PATCH 13/35] docs(cli): add KILO_CONFIG_DIR to command lookup --- packages/opencode/src/kilocode/skills/kilo-config.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index df435e5815c..682e5ba747e 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -30,7 +30,8 @@ When asked where `/name` lives, do not search only the repo root. Search these r 2. `~/.kilo/` 3. `~/.kilocode/` 4. `~/.opencode/` -5. project `.kilo/`, `.kilocode/`, and `.opencode/` directories from the current working directory up to the worktree root +5. The `KILO_CONFIG_DIR` directory (if the env var is set) +6. project `.kilo/`, `.kilocode/`, and `.opencode/` directories from the current working directory up to the worktree root Use exact patterns first: From 27057648d326e43972d1e76a58537813d071480b Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 14:32:15 +0300 Subject: [PATCH 14/35] docs(cli): add CONTEXT.md to instructions list --- packages/opencode/src/kilocode/skills/kilo-config.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index 682e5ba747e..a7ca25d7b53 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -255,10 +255,10 @@ Example: `~/.config/kilo/command/*.md` (modern global), `~/.kilocode/command/*.m ### Skills and instructions -| Scope | Path | -| ------------ | ------------------------------------------------------------------------ | -| Skills | `{skill,skills}//SKILL.md` inside any config directory | -| Instructions | `AGENTS.md`, `CLAUDE.md`, glob patterns from `instructions` config field | +| Scope | Path | +| ------------ | -------------------------------------------------------------------------------------- | +| Skills | `{skill,skills}//SKILL.md` inside any config directory | +| Instructions | `AGENTS.md`, `CLAUDE.md`, `CONTEXT.md`, glob patterns from `instructions` config field | ### Environment variable overrides From f0118047e539112c6c6c06228a10a3bd38cbd232 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 16:26:46 +0300 Subject: [PATCH 15/35] feat(cli): add config warnings endpoint and surface errors to TUI/extension Collect warnings during config loading instead of crashing when config files have invalid JSON, schema errors, or frontmatter issues. Warnings are exposed via a new GET /config/warnings pull-based endpoint. - Add Config.Warning schema and warnings collection in stateInit() - Wrap all loadFile/load calls with try/catch to collect warnings - Thread warnings through loadCommand/loadAgent/loadMode/invalid - Wrap well-known remote config block to prevent network/parse crashes - Add GET /config/warnings server route with OpenAPI spec - Regenerate SDK with config.warnings() method - TUI: fetch warnings in bootstrap non-blocking phase, show as toast - VS Code: fetch warnings on SSE connect, show as native notifications - Add 5 new tests for warning collection (JSON, schema, markdown) --- packages/kilo-vscode/src/KiloProvider.ts | 24 + .../opencode/src/cli/cmd/tui/context/sync.tsx | 19 + packages/opencode/src/config/config.ts | 173 +- packages/opencode/src/server/routes/config.ts | 23 + .../test/kilocode/config-resilience.test.ts | 104 + packages/sdk/js/src/v2/gen/sdk.gen.ts | 31 + packages/sdk/js/src/v2/gen/types.gen.ts | 22 + packages/sdk/openapi.json | 2157 ++++------------- 8 files changed, 792 insertions(+), 1761 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index de9c78c8176..05194727b75 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -124,6 +124,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private cachedMcpStatusMessage: unknown = null /** Ref-count of in-flight handleUpdateConfig calls; prevents fetchAndSendConfig from sending stale data */ private pending = 0 + private configWarningsShown = false // kilocode_change - prevent repeated config warnings on SSE reconnect /** Cached notificationsLoaded payload */ private cachedNotificationsMessage: unknown = null private pendingReviewComments: { comments: unknown[]; autoSend: boolean }[] = [] @@ -1058,6 +1059,7 @@ 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({ @@ -1947,6 +1949,28 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + // kilocode_change start + /** + * Fetch config warnings from the server and display them as VS Code warning messages. + * Only shown once per provider lifecycle (flag resets on dispose/re-create, not on SSE reconnect). + */ + private async checkConfigWarnings(): Promise { + if (this.configWarningsShown || !this.client) return + try { + const dir = this.getWorkspaceDirectory() + const result = await this.client.config.warnings({ directory: dir }) + const list = result?.data ?? [] + if (list.length === 0) return + this.configWarningsShown = true + for (const warning of list) { + vscode.window.showWarningMessage(`Config: ${warning.message}`) + } + } catch { + // Endpoint may not exist on older CLI versions — silently ignore + } + } + // kilocode_change end + /** * Fetch Kilo news/notifications and send to webview. * Uses the cached message pattern so the webview gets data immediately on refresh. diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index e7049fcbe4e..36119ccd7a2 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -29,6 +29,7 @@ import { batch, onMount } from "solid-js" import { Log } from "@/util/log" import type { Path } from "@kilocode/sdk" import type { Workspace } from "@kilocode/sdk/v2" +import { useToast } from "../ui/toast" // kilocode_change export const { use: useSync, provider: SyncProvider } = createSimpleContext({ name: "Sync", @@ -106,6 +107,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }) const sdk = useSDK() + const toast = useToast() // kilocode_change const fullSyncedSessions = new Set() // kilocode_change @@ -464,6 +466,23 @@ 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 + sdk.client.config + .warnings() + .then((x) => { + const list = x.data ?? [] + if (list.length === 0) return + const first = list[0] + const suffix = list.length > 1 ? ` (and ${list.length - 1} more)` : "" + toast.show({ + title: "Config Warning", + message: first.message + suffix, + variant: "warning", + duration: 8000, + }) + }) + .catch(() => {}), + // kilocode_change end ]).then(() => { setStore("status", "complete") }) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 7fabc3481e6..152fb3fe21b 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -52,6 +52,14 @@ export namespace Config { const log = Log.create({ service: "config" }) + // kilocode_change start + export const Warning = z.object({ + path: z.string(), + message: z.string(), + }) + export type Warning = z.infer + // kilocode_change end + // Managed settings directory for enterprise deployments (highest priority, admin-controlled) // These settings override all user and project settings function systemManagedConfigDir(): string { @@ -85,6 +93,18 @@ export namespace Config { // kilocode_change start — capture init so resetState() can invalidate the cache entry const stateInit = async () => { + // kilocode_change end + // kilocode_change start + const warnings: Warning[] = [] + const caught = (err: unknown, source: string) => { + const w = toWarning(err) + if (w) { + warnings.push(w) + log.warn("skipped config due to error", { source, err }) + return + } + throw err + } // kilocode_change end const auth = await Auth.all() @@ -200,24 +220,32 @@ export namespace Config { for (const [key, value] of Object.entries(auth)) { if (value.type === "wellknown") { const url = key.replace(/\/+$/, "") - process.env[value.key] = value.token - log.debug("fetching remote config", { url: `${url}/.well-known/opencode` }) - const response = await fetch(`${url}/.well-known/opencode`) - if (!response.ok) { - throw new Error(`failed to fetch remote config from ${url}: ${response.status}`) + const source = `${url}/.well-known/opencode` + try { + process.env[value.key] = value.token + log.debug("fetching remote config", { url: source }) + const response = await fetch(source) + if (!response.ok) { + throw new Error(`failed to fetch remote config from ${url}: ${response.status}`) + } + const wellknown = (await response.json()) as any + const remoteConfig = wellknown.config ?? {} + // Add $schema to prevent load() from trying to write back to a non-existent file + if (!remoteConfig.$schema) remoteConfig.$schema = "https://app.kilo.ai/config.json" // kilocode_change + result = mergeConfigConcatArrays( + result, + await load(JSON.stringify(remoteConfig), { + dir: path.dirname(source), + source, + }), + ) + log.debug("loaded remote config from well-known", { url }) + } catch (err) { + const w = toWarning(err) + if (w) warnings.push(w) + else warnings.push({ path: source, message: err instanceof Error ? err.message : String(err) }) + log.warn("skipped remote config due to error", { url, err }) } - const wellknown = (await response.json()) as any - const remoteConfig = wellknown.config ?? {} - // Add $schema to prevent load() from trying to write back to a non-existent file - if (!remoteConfig.$schema) remoteConfig.$schema = "https://app.kilo.ai/config.json" // kilocode_change - result = mergeConfigConcatArrays( - result, - await load(JSON.stringify(remoteConfig), { - dir: path.dirname(`${url}/.well-known/opencode`), - source: `${url}/.well-known/opencode`, - }), - ) - log.debug("loaded remote config from well-known", { url }) } } @@ -226,12 +254,20 @@ export namespace Config { } // Global user config overrides remote config. - result = mergeConfigConcatArrays(result, await global()) + try { + result = mergeConfigConcatArrays(result, await global()) + } catch (err) { + caught(err, "global config") + } // Custom config path overrides global config. if (Flag.KILO_CONFIG) { - result = mergeConfigConcatArrays(result, await loadFile(Flag.KILO_CONFIG)) - log.debug("loaded custom config", { path: Flag.KILO_CONFIG }) + try { + result = mergeConfigConcatArrays(result, await loadFile(Flag.KILO_CONFIG)) + log.debug("loaded custom config", { path: Flag.KILO_CONFIG }) + } catch (err) { + caught(err, Flag.KILO_CONFIG) + } } // Project config overrides global and remote config. @@ -239,7 +275,11 @@ export namespace Config { // kilocode_change start for (const file of ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) { // kilocode_change end - result = mergeConfigConcatArrays(result, await loadFile(file)) + try { + result = mergeConfigConcatArrays(result, await loadFile(file)) + } catch (err) { + caught(err, file) + } } } @@ -267,7 +307,11 @@ export namespace Config { for (const file of ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) { // kilocode_change end log.debug(`loading config from ${path.join(dir, file)}`) - result = mergeConfigConcatArrays(result, await loadFile(path.join(dir, file))) + try { + result = mergeConfigConcatArrays(result, await loadFile(path.join(dir, file))) + } catch (err) { + caught(err, path.join(dir, file)) + } // to satisfy the type checker result.agent ??= {} result.mode ??= {} @@ -284,9 +328,9 @@ export namespace Config { // kilocode_change start try { - result.command = mergeDeep(result.command ?? {}, await loadCommand(dir)) - result.agent = mergeDeep(result.agent, await loadAgent(dir)) - result.agent = mergeDeep(result.agent, await loadMode(dir)) + result.command = mergeDeep(result.command ?? {}, await loadCommand(dir, warnings)) + result.agent = mergeDeep(result.agent, await loadAgent(dir, warnings)) + result.agent = mergeDeep(result.agent, await loadMode(dir, warnings)) result.plugin.push(...(await loadPlugin(dir))) } catch (err: unknown) { log.error("failed to load config directory", { dir, err }) @@ -296,14 +340,18 @@ export namespace Config { // Inline config content overrides all non-managed config sources. if (process.env.KILO_CONFIG_CONTENT) { - result = mergeConfigConcatArrays( - result, - await load(process.env.KILO_CONFIG_CONTENT, { - dir: Instance.directory, - source: "KILO_CONFIG_CONTENT", - }), - ) - log.debug("loaded custom config from KILO_CONFIG_CONTENT") + try { + result = mergeConfigConcatArrays( + result, + await load(process.env.KILO_CONFIG_CONTENT, { + dir: Instance.directory, + source: "KILO_CONFIG_CONTENT", + }), + ) + log.debug("loaded custom config from KILO_CONFIG_CONTENT") + } catch (err) { + caught(err, "KILO_CONFIG_CONTENT") + } } // Load managed config files last (highest priority) - enterprise admin-controlled @@ -314,7 +362,11 @@ export namespace Config { // kilocode_change start for (const file of ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) { // kilocode_change end - result = mergeConfigConcatArrays(result, await loadFile(path.join(managedDir, file))) + try { + result = mergeConfigConcatArrays(result, await loadFile(path.join(managedDir, file))) + } catch (err) { + caught(err, path.join(managedDir, file)) + } } } @@ -367,6 +419,7 @@ export namespace Config { config: result, directories, deps, + warnings, } } // kilocode_change start — create state from named init so resetState() can invalidate it @@ -470,6 +523,25 @@ export namespace Config { } // kilocode_change start + function toWarning(err: unknown): Warning | undefined { + 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}` : ""), + } + if (ConfigPaths.InvalidError.isInstance(err)) { + const text = err.data.issues ? detail(err.data.issues) : err.data.message + return { + path: err.data.path, + message: text + ? `Configuration is invalid at ${err.data.path}: ${text}` + : `Configuration is invalid at ${err.data.path}`, + } + } + return undefined + } + function detail(issues: z.core.$ZodIssue[]) { return issues .map((issue) => { @@ -480,10 +552,17 @@ export namespace Config { .join("\n") } - async function invalid(kind: "agent" | "command", item: string, issues: z.core.$ZodIssue[], cause: Error) { + async function invalid( + kind: "agent" | "command", + item: string, + issues: z.core.$ZodIssue[], + cause: Error, + warnings?: Warning[], + ) { const text = detail(issues) const message = text ? `Config file at ${item} is invalid: ${text}` : `Config file at ${item} is invalid` const err = new InvalidError({ path: item, issues }, { cause }) + if (warnings) warnings.push({ path: item, message }) const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) if (kind === "command") { @@ -494,7 +573,7 @@ export namespace Config { } // kilocode_change end - async function loadCommand(dir: string) { + async function loadCommand(dir: string, warnings?: Warning[]) { const result: Record = {} for (const item of await Glob.scan("{command,commands}/**/*.md", { cwd: dir, @@ -506,6 +585,7 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse command ${item}` + if (warnings) warnings.push({ path: item, message }) const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) log.error("failed to load command", { command: item, err }) @@ -537,13 +617,13 @@ export namespace Config { continue } // kilocode_change start - await invalid("command", item, parsed.error.issues, parsed.error) + await invalid("command", item, parsed.error.issues, parsed.error, warnings) // kilocode_change end } return result } - async function loadAgent(dir: string) { + async function loadAgent(dir: string, warnings?: Warning[]) { const result: Record = {} for (const item of await Glob.scan("{agent,agents}/**/*.md", { @@ -556,6 +636,7 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse agent ${item}` + if (warnings) warnings.push({ path: item, message }) const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) log.error("failed to load agent", { agent: item, err }) @@ -589,13 +670,13 @@ export namespace Config { continue } // kilocode_change start - await invalid("agent", item, parsed.error.issues, parsed.error) + await invalid("agent", item, parsed.error.issues, parsed.error, warnings) // kilocode_change end } return result } - async function loadMode(dir: string) { + async function loadMode(dir: string, warnings?: Warning[]) { const result: Record = {} for (const item of await Glob.scan("{mode,modes}/*.md", { cwd: dir, @@ -607,6 +688,7 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse mode ${item}` + if (warnings) warnings.push({ path: item, message }) const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) log.error("failed to load mode", { mode: item, err }) @@ -627,6 +709,9 @@ export namespace Config { } continue } + // kilocode_change start + await invalid("agent", item, parsed.error.issues, parsed.error, warnings) + // kilocode_change end } return result } @@ -1532,6 +1617,12 @@ export namespace Config { return state().then((x) => x.config) } + // kilocode_change start + export async function warnings() { + return state().then((x) => x.warnings) + } + // kilocode_change end + export async function getGlobal() { return global() } @@ -1741,5 +1832,3 @@ export namespace Config { return state().then((x) => x.directories) } } -Filesystem.write -Filesystem.write diff --git a/packages/opencode/src/server/routes/config.ts b/packages/opencode/src/server/routes/config.ts index f9ac7e42976..faa46c40972 100644 --- a/packages/opencode/src/server/routes/config.ts +++ b/packages/opencode/src/server/routes/config.ts @@ -62,6 +62,29 @@ export const ConfigRoutes = lazy(() => return c.json(config) }, ) + // kilocode_change start + .get( + "/warnings", + describeRoute({ + summary: "Get config warnings", + description: "Get warnings generated during config loading (e.g., invalid JSON, schema errors).", + operationId: "config.warnings", + responses: { + 200: { + description: "Config warnings", + content: { + "application/json": { + schema: resolver(Config.Warning.array()), + }, + }, + }, + }, + }), + async (c) => { + return c.json(await Config.warnings()) + }, + ) + // kilocode_change end .get( "/providers", describeRoute({ diff --git a/packages/opencode/test/kilocode/config-resilience.test.ts b/packages/opencode/test/kilocode/config-resilience.test.ts index a5d013c746c..7e48d68dec9 100644 --- a/packages/opencode/test/kilocode/config-resilience.test.ts +++ b/packages/opencode/test/kilocode/config-resilience.test.ts @@ -152,4 +152,108 @@ Broken command template`, }, }) }) + + test("collects warnings for invalid agent markdown configs", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, ".kilo", "agent", "broken.md"), + `--- +mode: "banana" +--- +Broken agent`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Config.get() + const warns = await Config.warnings() + + expect(warns.some((w) => w.path.includes("broken.md") && w.message.includes("invalid"))).toBe(true) + }, + }) + }) + + test("collects warnings for invalid command markdown configs", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, ".kilo", "command", "broken.md"), + `--- +subtask: "banana" +--- +Broken command`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Config.get() + const warns = await Config.warnings() + + expect(warns.some((w) => w.path.includes("broken.md") && w.message.includes("invalid"))).toBe(true) + }, + }) + }) + + test("collects warnings for invalid JSON in .kilo directory config", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write(path.join(dir, ".kilo", "kilo.json"), "{ not valid json !!!") + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const cfg = await Config.get() + const warns = await Config.warnings() + + // Config loading should not crash + expect(cfg).toBeDefined() + // Warning should reference the bad file + expect(warns.some((w) => w.path.includes("kilo.json") && w.message.includes("not valid JSON"))).toBe(true) + }, + }) + }) + + test("collects warnings for invalid schema in .kilo directory config", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write(path.join(dir, ".kilo", "kilo.json"), JSON.stringify({ unknownField: true })) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const cfg = await Config.get() + const warns = await Config.warnings() + + expect(cfg).toBeDefined() + expect(warns.some((w) => w.path.includes("kilo.json") && w.message.includes("invalid"))).toBe(true) + }, + }) + }) + + test("returns empty warnings when config is valid", async () => { + await using tmp = await tmpdir({ + config: { model: "test/model" }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Config.get() + const warns = await Config.warnings() + + expect(warns).toEqual([]) + }, + }) + }) }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 0217a99a5fd..fa72a0bb006 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -21,6 +21,7 @@ import type { ConfigProvidersResponses, ConfigUpdateErrors, ConfigUpdateResponses, + ConfigWarningsResponses, EnhancePromptEnhanceErrors, EnhancePromptEnhanceResponses, EventSubscribeResponses, @@ -843,6 +844,36 @@ export class Config2 extends HeyApiClient { }) } + /** + * Get config warnings + * + * Get warnings generated during config loading (e.g., invalid JSON, schema errors). + */ + public warnings( + parameters?: { + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/config/warnings", + ...options, + ...params, + }) + } + /** * List config providers * diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 4b719f8b994..e8f9bd24bfc 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2462,6 +2462,28 @@ export type ConfigUpdateResponses = { export type ConfigUpdateResponse = ConfigUpdateResponses[keyof ConfigUpdateResponses] +export type ConfigWarningsData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/config/warnings" +} + +export type ConfigWarningsResponses = { + /** + * Config warnings + */ + 200: Array<{ + path: string + message: string + }> +} + +export type ConfigWarningsResponse = ConfigWarningsResponses[keyof ConfigWarningsResponses] + export type ConfigProvidersData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 905852293d2..b8192c91aa8 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -27,10 +27,7 @@ "type": "string" } }, - "required": [ - "healthy", - "version" - ] + "required": ["healthy", "version"] } } } @@ -745,10 +742,7 @@ "type": "number" } }, - "required": [ - "rows", - "cols" - ] + "required": ["rows", "cols"] } } } @@ -978,6 +972,59 @@ ] } }, + "/config/warnings": { + "get": { + "operationId": "config.warnings", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "Get config warnings", + "description": "Get warnings generated during config loading (e.g., invalid JSON, schema errors).", + "responses": { + "200": { + "description": "Config warnings", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": ["path", "message"] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.config.warnings({\n ...\n})" + } + ] + } + }, "/config/providers": { "get": { "operationId": "config.providers", @@ -1023,10 +1070,7 @@ } } }, - "required": [ - "providers", - "default" - ] + "required": ["providers", "default"] } } } @@ -1233,11 +1277,7 @@ ] } }, - "required": [ - "type", - "branch", - "extra" - ] + "required": ["type", "branch", "extra"] } } } @@ -2148,9 +2188,7 @@ ], "summary": "Get session", "description": "Retrieve detailed information about a specific Kilo session.", - "tags": [ - "Session" - ], + "tags": ["Session"], "responses": { "200": { "description": "Get session", @@ -2377,9 +2415,7 @@ } ], "summary": "Get session children", - "tags": [ - "Session" - ], + "tags": ["Session"], "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { @@ -2576,11 +2612,7 @@ "pattern": "^msg.*" } }, - "required": [ - "modelID", - "providerID", - "messageID" - ] + "required": ["modelID", "providerID", "messageID"] } } } @@ -3004,10 +3036,7 @@ "type": "boolean" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] } } } @@ -3077,10 +3106,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3161,10 +3187,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3210,10 +3233,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "agent": { "type": "string" @@ -3283,9 +3303,7 @@ } } }, - "required": [ - "parts" - ] + "required": ["parts"] } } } @@ -3355,10 +3373,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3725,10 +3740,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "agent": { "type": "string" @@ -3798,9 +3810,7 @@ } } }, - "required": [ - "parts" - ] + "required": ["parts"] } } } @@ -3861,10 +3871,7 @@ } } }, - "required": [ - "info", - "parts" - ] + "required": ["info", "parts"] } } } @@ -3942,20 +3949,13 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "type", - "mime", - "url" - ] + "required": ["type", "mime", "url"] } ] } } }, - "required": [ - "arguments", - "command" - ] + "required": ["arguments", "command"] } } } @@ -4049,19 +4049,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "agent", - "command" - ] + "required": ["agent", "command"] } } } @@ -4150,9 +4144,7 @@ "pattern": "^prt.*" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] } } } @@ -4312,16 +4304,10 @@ "properties": { "response": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] } }, - "required": [ - "response" - ] + "required": ["response"] } } } @@ -4458,19 +4444,13 @@ "properties": { "reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] }, "message": { "type": "string" } }, - "required": [ - "reply" - ] + "required": ["reply"] } } } @@ -4738,9 +4718,7 @@ } } }, - "required": [ - "answers" - ] + "required": ["answers"] } } } @@ -4917,15 +4895,10 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ], + "required": ["field"], "additionalProperties": false } ] @@ -4961,16 +4934,10 @@ "type": "number" } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "limit": { "type": "object", @@ -4985,10 +4952,7 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "modalities": { "type": "object", @@ -4997,70 +4961,39 @@ "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } }, "output": { "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "recommendedIndex": { "type": "number" }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, "ai_sdk_provider": { "type": "string", - "enum": [ - "anthropic", - "openai", - "openai-compatible", - "openrouter" - ] + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated" - ] + "enum": ["alpha", "beta", "deprecated"] }, "options": { "type": "object", @@ -5117,12 +5050,7 @@ } } }, - "required": [ - "name", - "env", - "id", - "models" - ] + "required": ["name", "env", "id", "models"] } }, "default": { @@ -5141,11 +5069,7 @@ } } }, - "required": [ - "all", - "default", - "connected" - ] + "required": ["all", "default", "connected"] } } } @@ -5272,9 +5196,7 @@ "type": "number" } }, - "required": [ - "method" - ] + "required": ["method"] } } } @@ -5354,9 +5276,7 @@ "type": "string" } }, - "required": [ - "method" - ] + "required": ["method"] } } } @@ -5431,9 +5351,7 @@ "additionalProperties": {} } }, - "required": [ - "event" - ] + "required": ["event"] } } } @@ -5482,10 +5400,7 @@ "type": "boolean" } }, - "required": [ - "enabled", - "connected" - ] + "required": ["enabled", "connected"] } } } @@ -5535,10 +5450,7 @@ "type": "boolean" } }, - "required": [ - "enabled", - "connected" - ] + "required": ["enabled", "connected"] } } } @@ -5588,10 +5500,7 @@ "type": "boolean" } }, - "required": [ - "enabled", - "connected" - ] + "required": ["enabled", "connected"] } } } @@ -5638,9 +5547,7 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -5678,9 +5585,7 @@ "type": "string" } }, - "required": [ - "path" - ] + "required": ["path"] } } } @@ -5726,9 +5631,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -5756,9 +5659,7 @@ "minLength": 1 } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -5810,10 +5711,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -5877,13 +5775,7 @@ } } }, - "required": [ - "id", - "worktree", - "timeCreated", - "timeUpdated", - "sandboxes" - ] + "required": ["id", "worktree", "timeCreated", "timeUpdated", "sandboxes"] } } } @@ -5935,10 +5827,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -5966,6 +5855,9 @@ "projectID": { "type": "string" }, + "force": { + "type": "boolean" + }, "workspaceID": { "type": "string" }, @@ -6010,11 +5902,7 @@ } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "revert": { "type": "object", @@ -6032,9 +5920,7 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] }, "permission": { "type": "object", @@ -6056,16 +5942,7 @@ "type": "number" } }, - "required": [ - "id", - "projectID", - "slug", - "directory", - "title", - "version", - "timeCreated", - "timeUpdated" - ] + "required": ["id", "projectID", "slug", "directory", "title", "version", "timeCreated", "timeUpdated"] } } } @@ -6117,10 +5994,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -6167,9 +6041,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "agent": { "type": "string" @@ -6184,10 +6056,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "tools": { "type": "object", @@ -6199,12 +6068,7 @@ } } }, - "required": [ - "role", - "time", - "agent", - "model" - ] + "required": ["role", "time", "agent", "model"] }, { "type": "object", @@ -6223,9 +6087,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "parentID": { "type": "string" @@ -6252,10 +6114,7 @@ "type": "string" } }, - "required": [ - "cwd", - "root" - ] + "required": ["cwd", "root"] }, "summary": { "type": "boolean" @@ -6288,18 +6147,10 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] }, "structured": {}, "variant": { @@ -6325,12 +6176,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "timeCreated", - "data" - ] + "required": ["id", "sessionID", "timeCreated", "data"] } } } @@ -6382,10 +6228,7 @@ "type": "boolean" } }, - "required": [ - "ok", - "id" - ] + "required": ["ok", "id"] } } } @@ -6447,9 +6290,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -6459,10 +6300,7 @@ "additionalProperties": {} } }, - "required": [ - "type", - "text" - ] + "required": ["type", "text"] }, { "type": "object", @@ -6491,16 +6329,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "type", - "text", - "time" - ] + "required": ["type", "text", "time"] }, { "type": "object", @@ -6535,11 +6367,7 @@ "type": "string" } }, - "required": [ - "status", - "input", - "raw" - ] + "required": ["status", "input", "raw"] }, { "type": "object", @@ -6572,16 +6400,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "status", - "input", - "time" - ] + "required": ["status", "input", "time"] }, { "type": "object", @@ -6623,20 +6445,10 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] } }, - "required": [ - "status", - "input", - "output", - "title", - "metadata", - "time" - ] + "required": ["status", "input", "output", "title", "metadata", "time"] }, { "type": "object", @@ -6672,18 +6484,10 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] } }, - "required": [ - "status", - "input", - "error", - "time" - ] + "required": ["status", "input", "error", "time"] } ] }, @@ -6695,22 +6499,12 @@ "additionalProperties": {} } }, - "required": [ - "type", - "callID", - "tool", - "state" - ] + "required": ["type", "callID", "tool", "state"] } ] } }, - "required": [ - "id", - "messageID", - "sessionID", - "data" - ] + "required": ["id", "messageID", "sessionID", "data"] } } } @@ -6776,9 +6570,7 @@ "type": "string" } }, - "required": [ - "location" - ] + "required": ["location"] } } } @@ -6844,9 +6636,7 @@ "type": "string" } }, - "required": [ - "name" - ] + "required": ["name"] } } } @@ -6912,17 +6702,11 @@ "type": "string" } }, - "required": [ - "id", - "name", - "role" - ] + "required": ["id", "name", "role"] } } }, - "required": [ - "email" - ] + "required": ["email"] }, "balance": { "anyOf": [ @@ -6933,9 +6717,7 @@ "type": "number" } }, - "required": [ - "balance" - ] + "required": ["balance"] }, { "type": "null" @@ -6953,11 +6735,7 @@ ] } }, - "required": [ - "profile", - "balance", - "currentOrgId" - ] + "required": ["profile", "balance", "currentOrgId"] } } } @@ -7041,9 +6819,7 @@ ] } }, - "required": [ - "organizationId" - ] + "required": ["organizationId"] } } } @@ -7171,9 +6947,7 @@ } } }, - "required": [ - "modes" - ] + "required": ["modes"] } } } @@ -7284,10 +7058,7 @@ "type": "number" } }, - "required": [ - "prefix", - "suffix" - ] + "required": ["prefix", "suffix"] } } } @@ -7350,10 +7121,7 @@ "type": "string" } }, - "required": [ - "actionText", - "actionURL" - ] + "required": ["actionText", "actionURL"] }, "showIn": { "type": "array", @@ -7365,11 +7133,7 @@ "type": "string" } }, - "required": [ - "id", - "title", - "message" - ] + "required": ["id", "title", "message"] } } } @@ -7512,9 +7276,7 @@ "type": "string" } }, - "required": [ - "sessionId" - ] + "required": ["sessionId"] } } } @@ -7560,14 +7322,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "provisioned", - "starting", - "restarting", - "running", - "stopped", - "destroying" - ] + "enum": ["provisioned", "starting", "restarting", "running", "stopped", "destroying"] }, { "type": "null" @@ -7590,10 +7345,7 @@ "type": "number" } }, - "required": [ - "cpus", - "memory_mb" - ] + "required": ["cpus", "memory_mb"] }, "openclawVersion": { "anyOf": [ @@ -7635,9 +7387,7 @@ "type": "string" } }, - "required": [ - "status" - ] + "required": ["status"] } } } @@ -7695,12 +7445,7 @@ "type": "string" } }, - "required": [ - "apiKey", - "userId", - "userToken", - "channelId" - ] + "required": ["apiKey", "userId", "userToken", "channelId"] }, { "type": "null" @@ -7797,13 +7542,7 @@ "type": "number" } }, - "required": [ - "session_id", - "title", - "created_at", - "updated_at", - "version" - ] + "required": ["session_id", "title", "created_at", "updated_at", "version"] } }, "nextCursor": { @@ -7817,10 +7556,7 @@ ] } }, - "required": [ - "cliSessions", - "nextCursor" - ] + "required": ["cliSessions", "nextCursor"] } } } @@ -7890,9 +7626,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "lines": { "type": "object", @@ -7901,9 +7635,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "line_number": { "type": "number" @@ -7923,9 +7655,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] }, "start": { "type": "number" @@ -7934,21 +7664,11 @@ "type": "number" } }, - "required": [ - "match", - "start", - "end" - ] + "required": ["match", "start", "end"] } } }, - "required": [ - "path", - "lines", - "line_number", - "absolute_offset", - "submatches" - ] + "required": ["path", "lines", "line_number", "absolute_offset", "submatches"] } } } @@ -7994,10 +7714,7 @@ "name": "dirs", "schema": { "type": "string", - "enum": [ - "true", - "false" - ] + "enum": ["true", "false"] } }, { @@ -8005,10 +7722,7 @@ "name": "type", "schema": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] } }, { @@ -8357,10 +8071,7 @@ ] } }, - "required": [ - "name", - "config" - ] + "required": ["name", "config"] } } } @@ -8415,9 +8126,7 @@ "type": "string" } }, - "required": [ - "authorizationUrl" - ] + "required": ["authorizationUrl"] } } } @@ -8491,9 +8200,7 @@ "const": true } }, - "required": [ - "success" - ] + "required": ["success"] } } } @@ -8589,9 +8296,7 @@ "type": "string" } }, - "required": [ - "code" - ] + "required": ["code"] } } } @@ -8822,9 +8527,7 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } } } @@ -9136,9 +8839,7 @@ "type": "string" } }, - "required": [ - "command" - ] + "required": ["command"] } } } @@ -9198,12 +8899,7 @@ }, "variant": { "type": "string", - "enum": [ - "info", - "success", - "warning", - "error" - ] + "enum": ["info", "success", "warning", "error"] }, "duration": { "description": "Duration in milliseconds", @@ -9211,10 +8907,7 @@ "type": "number" } }, - "required": [ - "message", - "variant" - ] + "required": ["message", "variant"] } } } @@ -9365,9 +9058,7 @@ "pattern": "^ses" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } } } @@ -9414,10 +9105,7 @@ }, "body": {} }, - "required": [ - "path", - "body" - ] + "required": ["path", "body"] } } } @@ -9702,12 +9390,7 @@ "level": { "description": "Log level", "type": "string", - "enum": [ - "debug", - "info", - "error", - "warn" - ] + "enum": ["debug", "info", "error", "warn"] }, "message": { "description": "Log message", @@ -9722,11 +9405,7 @@ "additionalProperties": {} } }, - "required": [ - "service", - "level", - "message" - ] + "required": ["service", "level", "message"] } } } @@ -9827,12 +9506,7 @@ "type": "string" } }, - "required": [ - "name", - "description", - "location", - "content" - ] + "required": ["name", "description", "location", "content"] } } } @@ -9993,15 +9667,10 @@ "type": "string" } }, - "required": [ - "version" - ] + "required": ["version"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.installation.update-available": { "type": "object", @@ -10017,15 +9686,10 @@ "type": "string" } }, - "required": [ - "version" - ] + "required": ["version"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Project": { "type": "object", @@ -10079,10 +9743,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "sandboxes": { "type": "array", @@ -10091,12 +9752,7 @@ } } }, - "required": [ - "id", - "worktree", - "time", - "sandboxes" - ] + "required": ["id", "worktree", "time", "sandboxes"] }, "Event.project.updated": { "type": "object", @@ -10109,10 +9765,7 @@ "$ref": "#/components/schemas/Project" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.server.instance.disposed": { "type": "object", @@ -10128,15 +9781,10 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.server.connected": { "type": "object", @@ -10150,10 +9798,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.global.disposed": { "type": "object", @@ -10167,10 +9812,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.global.config.updated": { "type": "object", @@ -10184,10 +9826,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.lsp.client.diagnostics": { "type": "object", @@ -10206,16 +9845,10 @@ "type": "string" } }, - "required": [ - "serverID", - "path" - ] + "required": ["serverID", "path"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.lsp.updated": { "type": "object", @@ -10229,10 +9862,7 @@ "properties": {} } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.file.edited": { "type": "object", @@ -10248,15 +9878,10 @@ "type": "string" } }, - "required": [ - "file" - ] + "required": ["file"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "OutputFormatText": { "type": "object", @@ -10266,9 +9891,7 @@ "const": "text" } }, - "required": [ - "type" - ] + "required": ["type"] }, "JSONSchema": { "type": "object", @@ -10294,10 +9917,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "schema" - ] + "required": ["type", "schema"] }, "OutputFormat": { "anyOf": [ @@ -10329,20 +9949,10 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "before", - "after", - "additions", - "deletions" - ] + "required": ["file", "before", "after", "additions", "deletions"] }, "UserMessage": { "type": "object", @@ -10364,9 +9974,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "format": { "$ref": "#/components/schemas/OutputFormat" @@ -10387,9 +9995,7 @@ } } }, - "required": [ - "diffs" - ] + "required": ["diffs"] }, "agent": { "type": "string" @@ -10404,10 +10010,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "system": { "type": "string" @@ -10448,14 +10051,7 @@ } } }, - "required": [ - "id", - "sessionID", - "role", - "time", - "agent", - "model" - ] + "required": ["id", "sessionID", "role", "time", "agent", "model"] }, "ProviderAuthError": { "type": "object", @@ -10474,16 +10070,10 @@ "type": "string" } }, - "required": [ - "providerID", - "message" - ] + "required": ["providerID", "message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "UnknownError": { "type": "object", @@ -10499,15 +10089,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "MessageOutputLengthError": { "type": "object", @@ -10521,10 +10106,7 @@ "properties": {} } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "MessageAbortedError": { "type": "object", @@ -10540,15 +10122,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "StructuredOutputError": { "type": "object", @@ -10567,16 +10144,10 @@ "type": "number" } }, - "required": [ - "message", - "retries" - ] + "required": ["message", "retries"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "ContextOverflowError": { "type": "object", @@ -10595,15 +10166,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "APIError": { "type": "object", @@ -10646,16 +10212,10 @@ } } }, - "required": [ - "message", - "isRetryable" - ] + "required": ["message", "isRetryable"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "AssistantMessage": { "type": "object", @@ -10680,9 +10240,7 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] }, "error": { "anyOf": [ @@ -10734,10 +10292,7 @@ "type": "string" } }, - "required": [ - "cwd", - "root" - ] + "required": ["cwd", "root"] }, "summary": { "type": "boolean" @@ -10770,18 +10325,10 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] }, "structured": {}, "variant": { @@ -10830,15 +10377,10 @@ "$ref": "#/components/schemas/Message" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.removed": { "type": "object", @@ -10857,16 +10399,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID" - ] + "required": ["sessionID", "messageID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "TextPart": { "type": "object", @@ -10903,9 +10439,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -10915,13 +10449,7 @@ "additionalProperties": {} } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text" - ] + "required": ["id", "sessionID", "messageID", "type", "text"] }, "SubtaskPart": { "type": "object", @@ -10958,24 +10486,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "prompt", - "description", - "agent" - ] + "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] }, "ReasoningPart": { "type": "object", @@ -11013,19 +10530,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text", - "time" - ] + "required": ["id", "sessionID", "messageID", "type", "text", "time"] }, "FilePartSourceText": { "type": "object", @@ -11044,11 +10552,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] }, "FileSource": { "type": "object", @@ -11064,11 +10568,7 @@ "type": "string" } }, - "required": [ - "text", - "type", - "path" - ] + "required": ["text", "type", "path"] }, "Range": { "type": "object", @@ -11083,10 +10583,7 @@ "type": "number" } }, - "required": [ - "line", - "character" - ] + "required": ["line", "character"] }, "end": { "type": "object", @@ -11098,16 +10595,10 @@ "type": "number" } }, - "required": [ - "line", - "character" - ] + "required": ["line", "character"] } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] }, "SymbolSource": { "type": "object", @@ -11134,14 +10625,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "text", - "type", - "path", - "range", - "name", - "kind" - ] + "required": ["text", "type", "path", "range", "name", "kind"] }, "ResourceSource": { "type": "object", @@ -11160,12 +10644,7 @@ "type": "string" } }, - "required": [ - "text", - "type", - "clientName", - "uri" - ] + "required": ["text", "type", "clientName", "uri"] }, "FilePartSource": { "anyOf": [ @@ -11209,14 +10688,7 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "mime", - "url" - ] + "required": ["id", "sessionID", "messageID", "type", "mime", "url"] }, "ToolStatePending": { "type": "object", @@ -11236,11 +10708,7 @@ "type": "string" } }, - "required": [ - "status", - "input", - "raw" - ] + "required": ["status", "input", "raw"] }, "ToolStateRunning": { "type": "object", @@ -11273,16 +10741,10 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] } }, - "required": [ - "status", - "input", - "time" - ] + "required": ["status", "input", "time"] }, "ToolStateCompleted": { "type": "object", @@ -11324,10 +10786,7 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] }, "attachments": { "type": "array", @@ -11336,14 +10795,7 @@ } } }, - "required": [ - "status", - "input", - "output", - "title", - "metadata", - "time" - ] + "required": ["status", "input", "output", "title", "metadata", "time"] }, "ToolStateError": { "type": "object", @@ -11379,18 +10831,10 @@ "type": "number" } }, - "required": [ - "start", - "end" - ] + "required": ["start", "end"] } }, - "required": [ - "status", - "input", - "error", - "time" - ] + "required": ["status", "input", "error", "time"] }, "ToolState": { "anyOf": [ @@ -11441,15 +10885,7 @@ "additionalProperties": {} } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "callID", - "tool", - "state" - ] + "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] }, "StepStartPart": { "type": "object", @@ -11471,12 +10907,7 @@ "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type" - ] + "required": ["id", "sessionID", "messageID", "type"] }, "StepFinishPart": { "type": "object", @@ -11528,29 +10959,13 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ] + "required": ["input", "output", "reasoning", "cache"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "reason", - "cost", - "tokens" - ] + "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] }, "SnapshotPart": { "type": "object", @@ -11572,13 +10987,7 @@ "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "snapshot" - ] + "required": ["id", "sessionID", "messageID", "type", "snapshot"] }, "PatchPart": { "type": "object", @@ -11606,14 +11015,7 @@ } } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "hash", - "files" - ] + "required": ["id", "sessionID", "messageID", "type", "hash", "files"] }, "AgentPart": { "type": "object", @@ -11651,20 +11053,10 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "name" - ] + "required": ["id", "sessionID", "messageID", "type", "name"] }, "RetryPart": { "type": "object", @@ -11695,20 +11087,10 @@ "type": "number" } }, - "required": [ - "created" - ] + "required": ["created"] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "attempt", - "error", - "time" - ] + "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] }, "CompactionPart": { "type": "object", @@ -11733,13 +11115,7 @@ "type": "boolean" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "auto" - ] + "required": ["id", "sessionID", "messageID", "type", "auto"] }, "Part": { "anyOf": [ @@ -11795,15 +11171,10 @@ "$ref": "#/components/schemas/Part" } }, - "required": [ - "part" - ] + "required": ["part"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.part.delta": { "type": "object", @@ -11831,19 +11202,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID", - "partID", - "field", - "delta" - ] + "required": ["sessionID", "messageID", "partID", "field", "delta"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.message.part.removed": { "type": "object", @@ -11865,17 +11227,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "messageID", - "partID" - ] + "required": ["sessionID", "messageID", "partID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "PermissionRequest": { "type": "object", @@ -11920,20 +11275,10 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ] + "required": ["messageID", "callID"] } }, - "required": [ - "id", - "sessionID", - "permission", - "patterns", - "metadata", - "always" - ] + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"] }, "Event.permission.asked": { "type": "object", @@ -11946,10 +11291,7 @@ "$ref": "#/components/schemas/PermissionRequest" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.permission.replied": { "type": "object", @@ -11969,24 +11311,13 @@ }, "reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] } }, - "required": [ - "sessionID", - "requestID", - "reply" - ] + "required": ["sessionID", "requestID", "reply"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "SessionStatus": { "anyOf": [ @@ -11998,9 +11329,7 @@ "const": "idle" } }, - "required": [ - "type" - ] + "required": ["type"] }, { "type": "object", @@ -12019,12 +11348,7 @@ "type": "number" } }, - "required": [ - "type", - "attempt", - "message", - "next" - ] + "required": ["type", "attempt", "message", "next"] }, { "type": "object", @@ -12034,9 +11358,7 @@ "const": "busy" } }, - "required": [ - "type" - ] + "required": ["type"] } ] }, @@ -12057,16 +11379,10 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": [ - "sessionID", - "status" - ] + "required": ["sessionID", "status"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.idle": { "type": "object", @@ -12082,15 +11398,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "QuestionOption": { "type": "object", @@ -12108,10 +11419,7 @@ "type": "string" } }, - "required": [ - "label", - "description" - ] + "required": ["label", "description"] }, "QuestionInfo": { "type": "object", @@ -12140,11 +11448,7 @@ "type": "boolean" } }, - "required": [ - "question", - "header", - "options" - ] + "required": ["question", "header", "options"] }, "QuestionRequest": { "type": "object", @@ -12174,17 +11478,10 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ] + "required": ["messageID", "callID"] } }, - "required": [ - "id", - "sessionID", - "questions" - ] + "required": ["id", "sessionID", "questions"] }, "Event.question.asked": { "type": "object", @@ -12197,10 +11494,7 @@ "$ref": "#/components/schemas/QuestionRequest" } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "QuestionAnswer": { "type": "array", @@ -12231,17 +11525,10 @@ } } }, - "required": [ - "sessionID", - "requestID", - "answers" - ] + "required": ["sessionID", "requestID", "answers"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.question.rejected": { "type": "object", @@ -12260,16 +11547,10 @@ "type": "string" } }, - "required": [ - "sessionID", - "requestID" - ] + "required": ["sessionID", "requestID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.compacted": { "type": "object", @@ -12285,15 +11566,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.file.watcher.updated": { "type": "object", @@ -12325,16 +11601,10 @@ ] } }, - "required": [ - "file", - "event" - ] + "required": ["file", "event"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Todo": { "type": "object", @@ -12352,11 +11622,7 @@ "type": "string" } }, - "required": [ - "content", - "status", - "priority" - ] + "required": ["content", "status", "priority"] }, "Event.todo.updated": { "type": "object", @@ -12378,16 +11644,10 @@ } } }, - "required": [ - "sessionID", - "todos" - ] + "required": ["sessionID", "todos"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.prompt.append": { "type": "object", @@ -12403,15 +11663,10 @@ "type": "string" } }, - "required": [ - "text" - ] + "required": ["text"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.command.execute": { "type": "object", @@ -12452,15 +11707,10 @@ ] } }, - "required": [ - "command" - ] + "required": ["command"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.toast.show": { "type": "object", @@ -12480,12 +11730,7 @@ }, "variant": { "type": "string", - "enum": [ - "info", - "success", - "warning", - "error" - ] + "enum": ["info", "success", "warning", "error"] }, "duration": { "description": "Duration in milliseconds", @@ -12493,16 +11738,10 @@ "type": "number" } }, - "required": [ - "message", - "variant" - ] + "required": ["message", "variant"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.tui.session.select": { "type": "object", @@ -12520,15 +11759,10 @@ "pattern": "^ses" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.mcp.tools.changed": { "type": "object", @@ -12544,15 +11778,10 @@ "type": "string" } }, - "required": [ - "server" - ] + "required": ["server"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.mcp.browser.open.failed": { "type": "object", @@ -12571,16 +11800,10 @@ "type": "string" } }, - "required": [ - "mcpName", - "url" - ] + "required": ["mcpName", "url"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.command.executed": { "type": "object", @@ -12607,26 +11830,14 @@ "pattern": "^msg.*" } }, - "required": [ - "name", - "sessionID", - "arguments", - "messageID" - ] + "required": ["name", "sessionID", "arguments", "messageID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "PermissionAction": { "type": "string", - "enum": [ - "allow", - "deny", - "ask" - ] + "enum": ["allow", "deny", "ask"] }, "PermissionRule": { "type": "object", @@ -12641,11 +11852,7 @@ "$ref": "#/components/schemas/PermissionAction" } }, - "required": [ - "permission", - "pattern", - "action" - ] + "required": ["permission", "pattern", "action"] }, "PermissionRuleset": { "type": "array", @@ -12704,26 +11911,14 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "additions", - "deletions" - ] + "required": ["file", "additions", "deletions"] } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "share": { "type": "object", @@ -12732,9 +11927,7 @@ "type": "string" } }, - "required": [ - "url" - ] + "required": ["url"] }, "title": { "type": "string" @@ -12758,10 +11951,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -12782,20 +11972,10 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] } }, - "required": [ - "id", - "slug", - "projectID", - "directory", - "title", - "version", - "time" - ] + "required": ["id", "slug", "projectID", "directory", "title", "version", "time"] }, "Event.session.created": { "type": "object", @@ -12811,15 +11991,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.updated": { "type": "object", @@ -12835,15 +12010,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.deleted": { "type": "object", @@ -12859,15 +12029,10 @@ "$ref": "#/components/schemas/Session" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.diff": { "type": "object", @@ -12889,16 +12054,10 @@ } } }, - "required": [ - "sessionID", - "diff" - ] + "required": ["sessionID", "diff"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.error": { "type": "object", @@ -12941,10 +12100,7 @@ } } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.turn.open": { "type": "object", @@ -12960,15 +12116,10 @@ "type": "string" } }, - "required": [ - "sessionID" - ] + "required": ["sessionID"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.session.turn.close": { "type": "object", @@ -12985,23 +12136,13 @@ }, "reason": { "type": "string", - "enum": [ - "completed", - "error", - "interrupted" - ] + "enum": ["completed", "error", "interrupted"] } }, - "required": [ - "sessionID", - "reason" - ] + "required": ["sessionID", "reason"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.vcs.branch.updated": { "type": "object", @@ -13019,10 +12160,7 @@ } } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.workspace.ready": { "type": "object", @@ -13038,15 +12176,10 @@ "type": "string" } }, - "required": [ - "name" - ] + "required": ["name"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.workspace.failed": { "type": "object", @@ -13062,15 +12195,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Pty": { "type": "object", @@ -13096,24 +12224,13 @@ }, "status": { "type": "string", - "enum": [ - "running", - "exited" - ] + "enum": ["running", "exited"] }, "pid": { "type": "number" } }, - "required": [ - "id", - "title", - "command", - "args", - "cwd", - "status", - "pid" - ] + "required": ["id", "title", "command", "args", "cwd", "status", "pid"] }, "Event.pty.created": { "type": "object", @@ -13129,15 +12246,10 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.updated": { "type": "object", @@ -13153,15 +12265,10 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ] + "required": ["info"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.exited": { "type": "object", @@ -13181,16 +12288,10 @@ "type": "number" } }, - "required": [ - "id", - "exitCode" - ] + "required": ["id", "exitCode"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.pty.deleted": { "type": "object", @@ -13207,15 +12308,10 @@ "pattern": "^pty.*" } }, - "required": [ - "id" - ] + "required": ["id"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.worktree.ready": { "type": "object", @@ -13234,16 +12330,10 @@ "type": "string" } }, - "required": [ - "name", - "branch" - ] + "required": ["name", "branch"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event.worktree.failed": { "type": "object", @@ -13259,15 +12349,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "type", - "properties" - ] + "required": ["type", "properties"] }, "Event": { "anyOf": [ @@ -13427,20 +12512,12 @@ "$ref": "#/components/schemas/Event" } }, - "required": [ - "directory", - "payload" - ] + "required": ["directory", "payload"] }, "LogLevel": { "description": "Log level", "type": "string", - "enum": [ - "DEBUG", - "INFO", - "WARN", - "ERROR" - ] + "enum": ["DEBUG", "INFO", "WARN", "ERROR"] }, "ServerConfig": { "description": "Server configuration for kilo serve and web commands", @@ -13478,11 +12555,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] + "enum": ["ask", "allow", "deny"] }, { "type": "null" @@ -13625,11 +12698,7 @@ }, "mode": { "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] + "enum": ["subagent", "primary", "all"] }, "hidden": { "description": "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)", @@ -13651,15 +12720,7 @@ }, { "type": "string", - "enum": [ - "primary", - "secondary", - "accent", - "success", - "warning", - "error", - "info" - ] + "enum": ["primary", "secondary", "accent", "success", "warning", "error", "info"] } ] }, @@ -13745,15 +12806,10 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ], + "required": ["field"], "additionalProperties": false } ] @@ -13789,16 +12845,10 @@ "type": "number" } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "limit": { "type": "object", @@ -13813,10 +12863,7 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "modalities": { "type": "object", @@ -13825,70 +12872,39 @@ "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } }, "output": { "type": "array", "items": { "type": "string", - "enum": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "enum": ["text", "audio", "image", "video", "pdf"] } } }, - "required": [ - "input", - "output" - ] + "required": ["input", "output"] }, "recommendedIndex": { "type": "number" }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, "ai_sdk_provider": { "type": "string", - "enum": [ - "anthropic", - "openai", - "openai-compatible", - "openrouter" - ] + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated" - ] + "enum": ["alpha", "beta", "deprecated"] }, "options": { "type": "object", @@ -14024,10 +13040,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "command" - ], + "required": ["type", "command"], "additionalProperties": false }, "McpOAuthConfig": { @@ -14093,19 +13106,13 @@ "maximum": 9007199254740991 } }, - "required": [ - "type", - "url" - ], + "required": ["type", "url"], "additionalProperties": false }, "LayoutConfig": { "description": "@deprecated Always uses stretch layout.", "type": "string", - "enum": [ - "auto", - "stretch" - ] + "enum": ["auto", "stretch"] }, "Config": { "type": "object", @@ -14145,9 +13152,7 @@ "type": "boolean" } }, - "required": [ - "template" - ] + "required": ["template"] } }, "skills": { @@ -14193,11 +13198,7 @@ "share": { "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", "type": "string", - "enum": [ - "manual", - "auto", - "disabled" - ] + "enum": ["manual", "auto", "disabled"] }, "autoshare": { "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", @@ -14352,9 +13353,7 @@ "type": "boolean" } }, - "required": [ - "enabled" - ], + "required": ["enabled"], "additionalProperties": false } ] @@ -14424,9 +13423,7 @@ "const": true } }, - "required": [ - "disabled" - ] + "required": ["disabled"] }, { "type": "object", @@ -14463,9 +13460,7 @@ "additionalProperties": {} } }, - "required": [ - "command" - ] + "required": ["command"] } ] } @@ -14582,11 +13577,7 @@ "const": false } }, - "required": [ - "data", - "errors", - "success" - ] + "required": ["data", "errors", "success"] }, "OAuth": { "type": "object", @@ -14611,12 +13602,7 @@ "type": "string" } }, - "required": [ - "type", - "refresh", - "access", - "expires" - ] + "required": ["type", "refresh", "access", "expires"] }, "ApiAuth": { "type": "object", @@ -14629,10 +13615,7 @@ "type": "string" } }, - "required": [ - "type", - "key" - ] + "required": ["type", "key"] }, "WellKnownAuth": { "type": "object", @@ -14648,11 +13631,7 @@ "type": "string" } }, - "required": [ - "type", - "key", - "token" - ] + "required": ["type", "key", "token"] }, "Auth": { "anyOf": [ @@ -14681,15 +13660,10 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] } }, - "required": [ - "name", - "data" - ] + "required": ["name", "data"] }, "Model": { "type": "object", @@ -14713,11 +13687,7 @@ "type": "string" } }, - "required": [ - "id", - "url", - "npm" - ] + "required": ["id", "url", "npm"] }, "name": { "type": "string" @@ -14759,13 +13729,7 @@ "type": "boolean" } }, - "required": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "required": ["text", "audio", "image", "video", "pdf"] }, "output": { "type": "object", @@ -14786,13 +13750,7 @@ "type": "boolean" } }, - "required": [ - "text", - "audio", - "image", - "video", - "pdf" - ] + "required": ["text", "audio", "image", "video", "pdf"] }, "interleaved": { "anyOf": [ @@ -14804,28 +13762,15 @@ "properties": { "field": { "type": "string", - "enum": [ - "reasoning_content", - "reasoning_details" - ] + "enum": ["reasoning_content", "reasoning_details"] } }, - "required": [ - "field" - ] + "required": ["field"] } ] } }, - "required": [ - "temperature", - "reasoning", - "attachment", - "toolcall", - "input", - "output", - "interleaved" - ] + "required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output", "interleaved"] }, "cost": { "type": "object", @@ -14846,10 +13791,7 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] }, "experimentalOver200K": { "type": "object", @@ -14870,24 +13812,13 @@ "type": "number" } }, - "required": [ - "read", - "write" - ] + "required": ["read", "write"] } }, - "required": [ - "input", - "output", - "cache" - ] + "required": ["input", "output", "cache"] } }, - "required": [ - "input", - "output", - "cache" - ] + "required": ["input", "output", "cache"] }, "limit": { "type": "object", @@ -14902,19 +13833,11 @@ "type": "number" } }, - "required": [ - "context", - "output" - ] + "required": ["context", "output"] }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated", - "active" - ] + "enum": ["alpha", "beta", "deprecated", "active"] }, "options": { "type": "object", @@ -14953,26 +13876,14 @@ }, "prompt": { "type": "string", - "enum": [ - "codex", - "gemini", - "beast", - "anthropic", - "trinity", - "anthropic_without_todo" - ] + "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"] }, "isFree": { "type": "boolean" }, "ai_sdk_provider": { "type": "string", - "enum": [ - "anthropic", - "openai", - "openai-compatible", - "openrouter" - ] + "enum": ["anthropic", "openai", "openai-compatible", "openrouter"] } }, "required": [ @@ -15000,12 +13911,7 @@ }, "source": { "type": "string", - "enum": [ - "env", - "config", - "custom", - "api" - ] + "enum": ["env", "config", "custom", "api"] }, "env": { "type": "array", @@ -15033,14 +13939,7 @@ } } }, - "required": [ - "id", - "name", - "source", - "env", - "options", - "models" - ] + "required": ["id", "name", "source", "env", "options", "models"] }, "ToolIDs": { "type": "array", @@ -15059,11 +13958,7 @@ }, "parameters": {} }, - "required": [ - "id", - "description", - "parameters" - ] + "required": ["id", "description", "parameters"] }, "ToolList": { "type": "array", @@ -15123,15 +14018,7 @@ "type": "string" } }, - "required": [ - "id", - "type", - "branch", - "name", - "directory", - "extra", - "projectID" - ] + "required": ["id", "type", "branch", "name", "directory", "extra", "projectID"] }, "Worktree": { "type": "object", @@ -15146,11 +14033,7 @@ "type": "string" } }, - "required": [ - "name", - "branch", - "directory" - ] + "required": ["name", "branch", "directory"] }, "WorktreeCreateInput": { "type": "object", @@ -15171,9 +14054,7 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] }, "WorktreeResetInput": { "type": "object", @@ -15182,9 +14063,7 @@ "type": "string" } }, - "required": [ - "directory" - ] + "required": ["directory"] }, "WorktreeDiffItem": { "type": "object", @@ -15206,11 +14085,7 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] }, "tracked": { "type": "boolean" @@ -15250,10 +14125,7 @@ "type": "string" } }, - "required": [ - "id", - "worktree" - ] + "required": ["id", "worktree"] }, "GlobalSession": { "type": "object", @@ -15306,26 +14178,14 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "additions", - "deletions" - ] + "required": ["file", "additions", "deletions"] } } }, - "required": [ - "additions", - "deletions", - "files" - ] + "required": ["additions", "deletions", "files"] }, "share": { "type": "object", @@ -15334,9 +14194,7 @@ "type": "string" } }, - "required": [ - "url" - ] + "required": ["url"] }, "title": { "type": "string" @@ -15360,10 +14218,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ] + "required": ["created", "updated"] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -15384,9 +14239,7 @@ "type": "string" } }, - "required": [ - "messageID" - ] + "required": ["messageID"] }, "project": { "anyOf": [ @@ -15399,16 +14252,7 @@ ] } }, - "required": [ - "id", - "slug", - "projectID", - "directory", - "title", - "version", - "time", - "project" - ] + "required": ["id", "slug", "projectID", "directory", "title", "version", "time", "project"] }, "McpResource": { "type": "object", @@ -15429,11 +14273,7 @@ "type": "string" } }, - "required": [ - "name", - "uri", - "client" - ] + "required": ["name", "uri", "client"] }, "TextPartInput": { "type": "object", @@ -15464,9 +14304,7 @@ "type": "number" } }, - "required": [ - "start" - ] + "required": ["start"] }, "metadata": { "type": "object", @@ -15476,10 +14314,7 @@ "additionalProperties": {} } }, - "required": [ - "type", - "text" - ] + "required": ["type", "text"] }, "FilePartInput": { "type": "object", @@ -15504,11 +14339,7 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": [ - "type", - "mime", - "url" - ] + "required": ["type", "mime", "url"] }, "AgentPartInput": { "type": "object", @@ -15540,17 +14371,10 @@ "maximum": 9007199254740991 } }, - "required": [ - "value", - "start", - "end" - ] + "required": ["value", "start", "end"] } }, - "required": [ - "type", - "name" - ] + "required": ["type", "name"] }, "SubtaskPartInput": { "type": "object", @@ -15581,21 +14405,13 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ] + "required": ["providerID", "modelID"] }, "command": { "type": "string" } }, - "required": [ - "type", - "prompt", - "description", - "agent" - ] + "required": ["type", "prompt", "description", "agent"] }, "ProviderAuthMethod": { "type": "object", @@ -15616,10 +14432,7 @@ "type": "string" } }, - "required": [ - "type", - "label" - ] + "required": ["type", "label"] }, "ProviderAuthAuthorization": { "type": "object", @@ -15643,11 +14456,7 @@ "type": "string" } }, - "required": [ - "url", - "method", - "instructions" - ] + "required": ["url", "method", "instructions"] }, "Symbol": { "type": "object", @@ -15668,17 +14477,10 @@ "$ref": "#/components/schemas/Range" } }, - "required": [ - "uri", - "range" - ] + "required": ["uri", "range"] } }, - "required": [ - "name", - "kind", - "location" - ] + "required": ["name", "kind", "location"] }, "FileNode": { "type": "object", @@ -15694,32 +14496,20 @@ }, "type": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] }, "ignored": { "type": "boolean" } }, - "required": [ - "name", - "path", - "absolute", - "type", - "ignored" - ] + "required": ["name", "path", "absolute", "type", "ignored"] }, "FileContent": { "type": "object", "properties": { "type": { "type": "string", - "enum": [ - "text", - "binary" - ] + "enum": ["text", "binary"] }, "content": { "type": "string" @@ -15766,24 +14556,14 @@ } } }, - "required": [ - "oldStart", - "oldLines", - "newStart", - "newLines", - "lines" - ] + "required": ["oldStart", "oldLines", "newStart", "newLines", "lines"] } }, "index": { "type": "string" } }, - "required": [ - "oldFileName", - "newFileName", - "hunks" - ] + "required": ["oldFileName", "newFileName", "hunks"] }, "encoding": { "type": "string", @@ -15793,10 +14573,7 @@ "type": "string" } }, - "required": [ - "type", - "content" - ] + "required": ["type", "content"] }, "File": { "type": "object", @@ -15816,19 +14593,10 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "path", - "added", - "removed", - "status" - ] + "required": ["path", "added", "removed", "status"] }, "MCPStatusConnected": { "type": "object", @@ -15838,9 +14606,7 @@ "const": "connected" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusDisabled": { "type": "object", @@ -15850,9 +14616,7 @@ "const": "disabled" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusFailed": { "type": "object", @@ -15865,10 +14629,7 @@ "type": "string" } }, - "required": [ - "status", - "error" - ] + "required": ["status", "error"] }, "MCPStatusNeedsAuth": { "type": "object", @@ -15878,9 +14639,7 @@ "const": "needs_auth" } }, - "required": [ - "status" - ] + "required": ["status"] }, "MCPStatusNeedsClientRegistration": { "type": "object", @@ -15893,10 +14652,7 @@ "type": "string" } }, - "required": [ - "status", - "error" - ] + "required": ["status", "error"] }, "MCPStatus": { "anyOf": [ @@ -15936,13 +14692,7 @@ "type": "string" } }, - "required": [ - "home", - "state", - "config", - "worktree", - "directory" - ] + "required": ["home", "state", "config", "worktree", "directory"] }, "VcsInfo": { "type": "object", @@ -15951,9 +14701,7 @@ "type": "string" } }, - "required": [ - "branch" - ] + "required": ["branch"] }, "Command": { "type": "object", @@ -15972,11 +14720,7 @@ }, "source": { "type": "string", - "enum": [ - "command", - "mcp", - "skill" - ] + "enum": ["command", "mcp", "skill"] }, "template": { "anyOf": [ @@ -15998,11 +14742,7 @@ } } }, - "required": [ - "name", - "template", - "hints" - ] + "required": ["name", "template", "hints"] }, "Agent": { "type": "object", @@ -16018,11 +14758,7 @@ }, "mode": { "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] + "enum": ["subagent", "primary", "all"] }, "native": { "type": "boolean" @@ -16055,10 +14791,7 @@ "type": "string" } }, - "required": [ - "modelID", - "providerID" - ] + "required": ["modelID", "providerID"] }, "variant": { "type": "string" @@ -16079,12 +14812,7 @@ "maximum": 9007199254740991 } }, - "required": [ - "name", - "mode", - "permission", - "options" - ] + "required": ["name", "mode", "permission", "options"] }, "LSPStatus": { "type": "object", @@ -16111,12 +14839,7 @@ ] } }, - "required": [ - "id", - "name", - "root", - "status" - ] + "required": ["id", "name", "root", "status"] }, "FormatterStatus": { "type": "object", @@ -16134,12 +14857,8 @@ "type": "boolean" } }, - "required": [ - "name", - "extensions", - "enabled" - ] + "required": ["name", "extensions", "enabled"] } } } -} \ No newline at end of file +} From 770887e96ceb5417f46dafdfe1494605145c0437 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 17:05:08 +0300 Subject: [PATCH 16/35] 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"] From 5accaead16bd807aa2af4a8d894b57f70f3678b1 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 17:57:10 +0300 Subject: [PATCH 17/35] fix(cli): include detail in validation error --- packages/opencode/src/config/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 36d1b550559..16800e74f34 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -561,7 +561,7 @@ export namespace Config { warnings?: Warning[], ) { const text = detail(issues) - const message = `Config file at ${item} is invalid` + const message = text ? `Config file at ${item} is invalid: ${text}` : `Config file at ${item} is invalid` const err = new InvalidError({ path: item, issues }, { cause }) if (warnings) warnings.push({ path: item, message, detail: text || undefined }) const { Session } = await import("@/session") From f0a30751b835c5f2dfe95506adcbca62a8e4756c Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 17:57:34 +0300 Subject: [PATCH 18/35] fix(cli): let managed config errors propagate --- packages/opencode/src/config/config.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 16800e74f34..794e034e9d2 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -363,11 +363,7 @@ export namespace Config { // kilocode_change start for (const file of ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) { // kilocode_change end - try { - result = mergeConfigConcatArrays(result, await loadFile(path.join(managedDir, file))) - } catch (err) { - caught(err, path.join(managedDir, file)) - } + result = mergeConfigConcatArrays(result, await loadFile(path.join(managedDir, file))) } } From 0de8282c04377ea562998720ccd8e0aceb29bdb6 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 17:58:24 +0300 Subject: [PATCH 19/35] style(vscode): remove kilocode_change markers --- packages/kilo-vscode/src/KiloProvider.ts | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 55c2408bb0b..cd1019b2a93 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -124,7 +124,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private cachedMcpStatusMessage: unknown = null /** Ref-count of in-flight handleUpdateConfig calls; prevents fetchAndSendConfig from sending stale data */ private pending = 0 - private configWarningsShown = false // kilocode_change - prevent repeated config warnings on SSE reconnect + private configWarningsShown = false /** Cached notificationsLoaded payload */ private cachedNotificationsMessage: unknown = null private pendingReviewComments: { comments: unknown[]; autoSend: boolean }[] = [] @@ -1048,10 +1048,9 @@ 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 + // 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 + void this.checkConfigWarnings("state") try { // Profile fetch is best-effort — returns 401 when user isn't logged into gateway. const sdkClient = this.client @@ -1127,6 +1126,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } this.postMessage({ type: "connectionState", state: this.connectionState }) + + // connect() can resolve after SSE reaches "connected" but before this + // provider subscribes to onStateChange(). In that case the initial + // connected callback is missed, so run the warning check here too. + if (this.connectionState === "connected") { + void this.checkConfigWarnings("init") + } + await this.syncWebviewState("initializeConnection") await this.flushPendingSessionRefresh("initializeConnection") @@ -1952,23 +1959,32 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } - // kilocode_change start /** * 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 { - if (this.configWarningsShown || !this.client) return + private async checkConfigWarnings(from: string): Promise { + if (this.configWarningsShown) { + console.log("[Kilo New] KiloProvider: config warnings already shown", { from }) + return + } + if (!this.client) { + console.log("[Kilo New] KiloProvider: config warnings skipped (no client)", { from }) + return + } try { const dir = this.getWorkspaceDirectory() + console.log("[Kilo New] KiloProvider: checking config warnings", { from, dir }) const result = await this.client.config.warnings({ directory: dir }) const list = result?.data ?? [] + console.log("[Kilo New] KiloProvider: config warnings fetched", { from, count: list.length }) if (list.length === 0) return this.configWarningsShown = true const first = list[0]! const summary = list.length === 1 ? first.message : `${first.message} (and ${list.length - 1} more)` + console.warn("[Kilo New] KiloProvider: showing config warnings", { from, count: list.length, path: first.path }) const action = await vscode.window.showWarningMessage(`Config: ${summary}`, "Show Details") if (action === "Show Details") { @@ -1982,10 +1998,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper channel.show() } } catch (err) { - console.warn("[Kilo New] KiloProvider: checkConfigWarnings failed:", err) + console.warn("[Kilo New] KiloProvider: checkConfigWarnings failed:", { from, err }) } } - // kilocode_change end /** * Fetch Kilo news/notifications and send to webview. From 5d184aba94faec942e36a64aef221044f7340605 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 18:01:37 +0300 Subject: [PATCH 20/35] style(cli): fix kilocode_change markers --- packages/opencode/src/config/config.ts | 30 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 794e034e9d2..eb0e363e05a 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -221,6 +221,7 @@ export namespace Config { for (const [key, value] of Object.entries(auth)) { if (value.type === "wellknown") { const url = key.replace(/\/+$/, "") + // kilocode_change start const source = `${url}/.well-known/opencode` try { process.env[value.key] = value.token @@ -232,7 +233,7 @@ export namespace Config { const wellknown = (await response.json()) as any const remoteConfig = wellknown.config ?? {} // Add $schema to prevent load() from trying to write back to a non-existent file - if (!remoteConfig.$schema) remoteConfig.$schema = "https://app.kilo.ai/config.json" // kilocode_change + if (!remoteConfig.$schema) remoteConfig.$schema = "https://app.kilo.ai/config.json" result = mergeConfigConcatArrays( result, await load(JSON.stringify(remoteConfig), { @@ -246,6 +247,7 @@ export namespace Config { if (w) warnings.push(w) else warnings.push({ path: source, message: err instanceof Error ? err.message : String(err) }) log.warn("skipped remote config due to error", { url, err }) + // kilocode_change end } } } @@ -255,33 +257,37 @@ export namespace Config { } // Global user config overrides remote config. + // kilocode_change start try { result = mergeConfigConcatArrays(result, await global()) } catch (err) { caught(err, "global config") } + // kilocode_change end // Custom config path overrides global config. if (Flag.KILO_CONFIG) { + // kilocode_change start try { result = mergeConfigConcatArrays(result, await loadFile(Flag.KILO_CONFIG)) log.debug("loaded custom config", { path: Flag.KILO_CONFIG }) } catch (err) { caught(err, Flag.KILO_CONFIG) } + // kilocode_change end } // Project config overrides global and remote config. if (!Flag.KILO_DISABLE_PROJECT_CONFIG) { // kilocode_change start for (const file of ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) { - // kilocode_change end try { result = mergeConfigConcatArrays(result, await loadFile(file)) } catch (err) { caught(err, file) } } + // kilocode_change end } result.agent = result.agent || {} @@ -306,7 +312,6 @@ export namespace Config { dir === Flag.KILO_CONFIG_DIR ) { for (const file of ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) { - // kilocode_change end log.debug(`loading config from ${path.join(dir, file)}`) try { result = mergeConfigConcatArrays(result, await loadFile(path.join(dir, file))) @@ -318,6 +323,7 @@ export namespace Config { result.mode ??= {} result.plugin ??= [] } + // kilocode_change end } deps.push( @@ -341,6 +347,7 @@ export namespace Config { // Inline config content overrides all non-managed config sources. if (process.env.KILO_CONFIG_CONTENT) { + // kilocode_change start try { result = mergeConfigConcatArrays( result, @@ -353,6 +360,7 @@ export namespace Config { } catch (err) { caught(err, "KILO_CONFIG_CONTENT") } + // kilocode_change end } // Load managed config files last (highest priority) - enterprise admin-controlled @@ -362,9 +370,9 @@ export namespace Config { if (existsSync(managedDir)) { // kilocode_change start for (const file of ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) { - // kilocode_change end result = mergeConfigConcatArrays(result, await loadFile(path.join(managedDir, file))) } + // kilocode_change end } // Migrate deprecated mode field to agent field @@ -416,7 +424,7 @@ export namespace Config { config: result, directories, deps, - warnings, + warnings, // kilocode_change } } // kilocode_change start — create state from named init so resetState() can invalidate it @@ -570,7 +578,9 @@ export namespace Config { } // kilocode_change end + // kilocode_change start async function loadCommand(dir: string, warnings?: Warning[]) { + // kilocode_change end const result: Record = {} for (const item of await Glob.scan("{command,commands}/**/*.md", { cwd: dir, @@ -582,7 +592,7 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse command ${item}` - if (warnings) warnings.push({ path: item, message }) + if (warnings) warnings.push({ path: item, message }) // kilocode_change const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) log.error("failed to load command", { command: item, err }) @@ -620,7 +630,9 @@ export namespace Config { return result } + // kilocode_change start async function loadAgent(dir: string, warnings?: Warning[]) { + // kilocode_change end const result: Record = {} for (const item of await Glob.scan("{agent,agents}/**/*.md", { @@ -633,7 +645,7 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse agent ${item}` - if (warnings) warnings.push({ path: item, message }) + if (warnings) warnings.push({ path: item, message }) // kilocode_change const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) log.error("failed to load agent", { agent: item, err }) @@ -673,7 +685,9 @@ export namespace Config { return result } + // kilocode_change start async function loadMode(dir: string, warnings?: Warning[]) { + // kilocode_change end const result: Record = {} for (const item of await Glob.scan("{mode,modes}/*.md", { cwd: dir, @@ -685,7 +699,7 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse mode ${item}` - if (warnings) warnings.push({ path: item, message }) + if (warnings) warnings.push({ path: item, message }) // kilocode_change const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) log.error("failed to load mode", { mode: item, err }) From 2ca9542254bdd2aa9b29110a77e65c68c88cbc4b Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 7 Apr 2026 18:06:48 +0300 Subject: [PATCH 21/35] fix(cli): guard session import in config error handlers --- packages/opencode/src/config/config.ts | 32 ++++++++++++---- .../test/kilocode/config-resilience.test.ts | 37 +++---------------- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index eb0e363e05a..f7cb7268915 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -568,8 +568,12 @@ export namespace Config { const message = text ? `Config file at ${item} is invalid: ${text}` : `Config file at ${item} is invalid` const err = new InvalidError({ path: item, issues }, { cause }) 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() }) + try { + const { Session } = await import("@/session") + Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + } catch (e) { + log.warn("could not publish session error", { message, err: e }) + } if (kind === "command") { log.error("failed to load command", { command: item, err, message }) return @@ -593,8 +597,12 @@ export namespace Config { ? err.data.message : `Failed to parse command ${item}` if (warnings) warnings.push({ path: item, message }) // kilocode_change - const { Session } = await import("@/session") - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + try { + const { Session } = await import("@/session") + Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + } catch (e) { + log.warn("could not publish session error", { message, err: e }) + } log.error("failed to load command", { command: item, err }) return undefined }) @@ -646,8 +654,12 @@ export namespace Config { ? err.data.message : `Failed to parse agent ${item}` if (warnings) warnings.push({ path: item, message }) // kilocode_change - const { Session } = await import("@/session") - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + try { + const { Session } = await import("@/session") + Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + } catch (e) { + log.warn("could not publish session error", { message, err: e }) + } log.error("failed to load agent", { agent: item, err }) return undefined }) @@ -700,8 +712,12 @@ export namespace Config { ? err.data.message : `Failed to parse mode ${item}` if (warnings) warnings.push({ path: item, message }) // kilocode_change - const { Session } = await import("@/session") - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + try { + const { Session } = await import("@/session") + Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + } catch (e) { + log.warn("could not publish session error", { message, err: e }) + } log.error("failed to load mode", { mode: item, err }) return undefined }) diff --git a/packages/opencode/test/kilocode/config-resilience.test.ts b/packages/opencode/test/kilocode/config-resilience.test.ts index 7e48d68dec9..5cb72a91385 100644 --- a/packages/opencode/test/kilocode/config-resilience.test.ts +++ b/packages/opencode/test/kilocode/config-resilience.test.ts @@ -1,6 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test" import path from "path" -import { Bus } from "../../src/bus" import { Config } from "../../src/config/config" import { Instance } from "../../src/project/instance" import { Filesystem } from "../../src/util/filesystem" @@ -47,7 +46,7 @@ Valid agent prompt`, }) }) - test("publishes an error for invalid agent markdown configs", async () => { + test("reports a warning for invalid agent markdown configs", async () => { await using tmp = await tmpdir({ init: async (dir) => { await Filesystem.write( @@ -63,22 +62,10 @@ Broken agent prompt`, await Instance.provide({ directory: tmp.path, fn: async () => { - const seen: Array<{ type: string; properties: { error: { name: string; data: { message: string } } } }> = [] - const unsub = Bus.subscribeAll((event) => { - if (event.type === "session.error") seen.push(event) - }) - await Config.get() - unsub() + const warns = await Config.warnings() - expect( - seen.some( - (item) => - item.properties.error.name === "UnknownError" && - item.properties.error.data.message.includes("skip.md") && - item.properties.error.data.message.includes("mode"), - ), - ).toBe(true) + expect(warns.some((w) => w.path.includes("skip.md") && w.message.includes("mode"))).toBe(true) }, }) }) @@ -117,7 +104,7 @@ Valid command template`, }) }) - test("publishes an error for invalid command markdown configs", async () => { + test("reports a warning for invalid command markdown configs", async () => { await using tmp = await tmpdir({ init: async (dir) => { await Filesystem.write( @@ -133,22 +120,10 @@ Broken command template`, await Instance.provide({ directory: tmp.path, fn: async () => { - const seen: Array<{ type: string; properties: { error: { name: string; data: { message: string } } } }> = [] - const unsub = Bus.subscribeAll((event) => { - if (event.type === "session.error") seen.push(event) - }) - await Config.get() - unsub() + const warns = await Config.warnings() - expect( - seen.some( - (item) => - item.properties.error.name === "UnknownError" && - item.properties.error.data.message.includes("skip.md") && - item.properties.error.data.message.includes("subtask"), - ), - ).toBe(true) + expect(warns.some((w) => w.path.includes("skip.md") && w.message.includes("subtask"))).toBe(true) }, }) }) From 6106b1275dfce56e81c1afa99f73b90fc967f365 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:19:39 +0000 Subject: [PATCH 22/35] feat(vscode): add open-in-editor icon to instruction files in Rules tab Add a go-to-file icon button next to each instruction file entry in the Agent Behaviour > Rules subtab, allowing users to open the file directly in the editor. Uses the established go-to-file icon pattern consistent with DiffPanel and PromptInput components. --- .../src/components/settings/AgentBehaviourTab.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx index 1b3eac83992..47c0ba3ccc0 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx @@ -1004,7 +1004,15 @@ const AgentBehaviourTab: Component = () => { > {path} - removeInstruction(index())} /> +
+ vscode.postMessage({ type: "openFile", filePath: path })} + /> + removeInstruction(index())} /> +
)} From 60bfeaf63078377d696de9dc989e5de05e372092 Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 8 Apr 2026 09:22:38 +0200 Subject: [PATCH 23/35] feat(agent-manager): sync worktree branch from git worktree list (#8534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Piggyback on the existing `git worktree list --porcelain` call in probeWorktreePresence to detect branch changes (e.g. after checking out a PR branch). Zero additional git calls — the branch info was already returned but discarded. --- .../src/agent-manager/AgentManagerProvider.ts | 16 ++++++++++++---- .../kilo-vscode/src/agent-manager/GitOps.ts | 8 ++++---- .../src/agent-manager/GitStatsPoller.ts | 5 ++++- .../src/agent-manager/WorktreeStateManager.ts | 13 +++++++++++++ .../tests/unit/git-stats-poller.test.ts | 17 ++++++++++------- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index d70103ab362..3554990404d 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -695,11 +695,11 @@ export class AgentManagerProvider implements Disposable { this.pushState() // Disk removal after state is clean — pollers no longer reference this worktree. try { - await manager.removeWorktree(worktree.path, worktree.branch) + await manager.removeWorktree(worktree.path, worktree.originalBranch ?? worktree.branch) } catch (error) { this.log(`Failed to remove worktree from disk: ${error}`) } - this.log(`Deleted worktree ${worktreeId} (${worktree.branch})`) + this.log(`Deleted worktree ${worktreeId} (${worktree.originalBranch ?? worktree.branch})`) return null } @@ -1444,12 +1444,20 @@ export class AgentManagerProvider implements Disposable { const entries = result.worktrees.filter((item) => ids.has(item.worktreeId)) if (entries.length === 0) return + // Sync branches from git worktree list (no extra git calls) + let branchChanged = false + for (const entry of entries) { + if (entry.branch && state.updateWorktreeBranch(entry.worktreeId, entry.branch)) { + branchChanged = true + } + } + const next = new Set(entries.filter((entry) => entry.missing).map((entry) => entry.worktreeId)) - const changed = + const staleChanged = next.size !== this.staleWorktreeIds.size || [...next].some((worktreeId) => !this.staleWorktreeIds.has(worktreeId)) this.staleWorktreeIds = next - if (changed) { + if (staleChanged || branchChanged) { this.pushState() } } diff --git a/packages/kilo-vscode/src/agent-manager/GitOps.ts b/packages/kilo-vscode/src/agent-manager/GitOps.ts index 9aa393513ce..dcd74e95987 100644 --- a/packages/kilo-vscode/src/agent-manager/GitOps.ts +++ b/packages/kilo-vscode/src/agent-manager/GitOps.ts @@ -130,14 +130,14 @@ export class GitOps { } /** Return the set of worktree paths for the repo, excluding bare entries. */ - async listWorktreePaths(cwd: string): Promise> { + async listWorktreePaths(cwd: string): Promise> { const raw = await this.raw(["worktree", "list", "--porcelain"], cwd) - const paths = new Set() + const result = new Map() for (const entry of parseWorktreeList(raw)) { if (entry.bare) continue - paths.add(normalizePath(entry.path)) + result.set(normalizePath(entry.path), entry.branch) } - return paths + return result } /** diff --git a/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts b/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts index 27260b2f059..301261d137e 100644 --- a/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts @@ -26,6 +26,8 @@ export interface LocalStats { export interface WorktreePresence { worktreeId: string missing: boolean + /** Current branch from `git worktree list`, if available. */ + branch?: string } export interface WorktreePresenceResult { @@ -231,7 +233,8 @@ export class GitStatsPoller { () => false, ) const missing = !exists || !tracked.has(normalized) - return { worktreeId: wt.id, missing } + const branch = tracked.get(normalized) + return { worktreeId: wt.id, missing, branch } }), ) diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts index e7c0ba50214..c41ce547aa8 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts @@ -32,6 +32,9 @@ export interface Worktree { prUrl?: string /** Cached PR state for correct badge color on reload (open/merged/closed/draft). */ prState?: string + /** Original branch created with the worktree, used for cleanup on deletion. + * Set automatically when `branch` is updated via live sync. */ + originalBranch?: string } /** @@ -169,6 +172,16 @@ export class WorktreeStateManager { return wt } + updateWorktreeBranch(id: string, branch: string): boolean { + const wt = this.worktrees.get(id) + if (!wt || wt.branch === branch) return false + if (!wt.originalBranch) wt.originalBranch = wt.branch + this.log(`Updated worktree ${id} branch: ${wt.branch} → ${branch}`) + wt.branch = branch + void this.save() + return true + } + updateWorktreeLabel(id: string, label: string): void { const wt = this.worktrees.get(id) if (!wt) return diff --git a/packages/kilo-vscode/tests/unit/git-stats-poller.test.ts b/packages/kilo-vscode/tests/unit/git-stats-poller.test.ts index ef9ec16b823..53176ffccb9 100644 --- a/packages/kilo-vscode/tests/unit/git-stats-poller.test.ts +++ b/packages/kilo-vscode/tests/unit/git-stats-poller.test.ts @@ -3,7 +3,7 @@ import * as fs from "fs" import * as os from "os" import * as path from "path" import type { KiloClient } from "@kilocode/sdk/v2/client" -import { GitStatsPoller } from "../../src/agent-manager/GitStatsPoller" +import { GitStatsPoller, type WorktreePresenceResult } from "../../src/agent-manager/GitStatsPoller" import { GitOps } from "../../src/agent-manager/GitOps" import type { Worktree } from "../../src/agent-manager/WorktreeStateManager" @@ -128,7 +128,7 @@ describe("GitStatsPoller", () => { const wtPath = path.join(root, "wt-a") fs.mkdirSync(wtPath, { recursive: true }) - const presence: Array<{ worktrees: Array<{ worktreeId: string; missing: boolean }>; degraded: boolean }> = [] + const presence: WorktreePresenceResult[] = [] const poller = new GitStatsPoller({ getWorktrees: () => [{ ...worktree("a"), path: wtPath }], @@ -154,7 +154,10 @@ describe("GitStatsPoller", () => { poller.stop() fs.rmSync(root, { recursive: true, force: true }) - expect(presence[0]).toEqual({ worktrees: [{ worktreeId: "a", missing: false }], degraded: false }) + expect(presence[0]).toEqual({ + worktrees: [{ worktreeId: "a", missing: false, branch: "branch-a" }], + degraded: false, + }) }) it("emits degraded probe when git worktree listing fails", async () => { @@ -162,7 +165,7 @@ describe("GitStatsPoller", () => { const wtPath = path.join(root, "wt-a") fs.mkdirSync(wtPath, { recursive: true }) - const presence: Array<{ worktrees: Array<{ worktreeId: string; missing: boolean }>; degraded: boolean }> = [] + const presence: WorktreePresenceResult[] = [] const poller = new GitStatsPoller({ getWorktrees: () => [{ ...worktree("a"), path: wtPath }], @@ -199,7 +202,7 @@ describe("GitStatsPoller", () => { const calls: string[] = [] const emitted: Array> = [] - const presence: Array<{ worktrees: Array<{ worktreeId: string; missing: boolean }>; degraded: boolean }> = [] + const presence: WorktreePresenceResult[] = [] const client = { worktree: { @@ -240,8 +243,8 @@ describe("GitStatsPoller", () => { expect(calls.some((cwd) => cwd === wtBPath)).toBe(false) expect(presence[0]).toEqual({ worktrees: [ - { worktreeId: "a", missing: false }, - { worktreeId: "b", missing: true }, + { worktreeId: "a", missing: false, branch: "branch-a" }, + { worktreeId: "b", missing: true, branch: undefined }, ], degraded: false, }) From 32c442497ff9d3e3e0a7b546bae06170ef76dfda Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:31:47 +0000 Subject: [PATCH 24/35] fix(vscode): use pencil-line icon for edit action in Rules tab The pencil icon more clearly conveys "edit this file" compared to the go-to-file icon. --- .../webview-ui/src/components/settings/AgentBehaviourTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx index 47c0ba3ccc0..5349789bae3 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx @@ -1008,7 +1008,7 @@ const AgentBehaviourTab: Component = () => { vscode.postMessage({ type: "openFile", filePath: path })} /> removeInstruction(index())} /> From 9e13f71a75dc7f9e18d9a70a9b2a5bae9474dc4b Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:51:50 +0000 Subject: [PATCH 25/35] fix(i18n): clarify built-in mode banner to avoid read-only confusion Re-apply PR #7720 which was reverted by a force push during the publish workflow. Also update uk, tr, and nl translations which were missed by the original translation update in PR #8086. Closes #8558 (partial) --- packages/kilo-vscode/webview-ui/src/i18n/en.ts | 3 ++- packages/kilo-vscode/webview-ui/src/i18n/nl.ts | 3 ++- packages/kilo-vscode/webview-ui/src/i18n/tr.ts | 3 ++- packages/kilo-vscode/webview-ui/src/i18n/uk.ts | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index cdc8808c581..ae418d88169 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1175,7 +1175,8 @@ export const dict = { "settings.agentBehaviour.editMode.prompt": "System Prompt", "settings.agentBehaviour.editMode.save": "Done", "settings.agentBehaviour.editMode.back": "Back to list", - "settings.agentBehaviour.editMode.native": "Built-in mode (read-only definition)", + "settings.agentBehaviour.editMode.native": + "This is a built-in mode. Its base definition cannot be changed, but you can configure overrides below.", "settings.agentBehaviour.editMode.promptOverride": "Custom prompt override for this built-in mode", "settings.autoApprove.description": diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index b3a648f5ab9..32e70ebc7a1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -1377,7 +1377,8 @@ export const dict = { "settings.agentBehaviour.editMode": "Modus bewerken", "settings.agentBehaviour.editMode.back": "Terug naar lijst", "settings.agentBehaviour.editMode.description": "Beschrijving", - "settings.agentBehaviour.editMode.native": "Ingebouwde modus (alleen-lezen definitie)", + "settings.agentBehaviour.editMode.native": + "Dit is een ingebouwde modus. De basisdefinitie kan niet worden gewijzigd, maar u kunt hieronder overrides configureren.", "settings.agentBehaviour.editMode.prompt": "Systeemprompt", "settings.agentBehaviour.editMode.promptOverride": "Aangepaste systeemprompt voor deze ingebouwde modus", "settings.agentBehaviour.editMode.save": "Klaar", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 9728ca2defe..996c68cd5ad 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -1370,7 +1370,8 @@ export const dict = { "settings.agentBehaviour.editMode": "Modu Düzenle", "settings.agentBehaviour.editMode.back": "Listeye dön", "settings.agentBehaviour.editMode.description": "Açıklama", - "settings.agentBehaviour.editMode.native": "Yerleşik mod (salt okunur tanım)", + "settings.agentBehaviour.editMode.native": + "Bu yerleşik bir moddur. Temel tanımı değiştirilemez, ancak aşağıdan geçersiz kılmaları yapılandırabilirsiniz.", "settings.agentBehaviour.editMode.prompt": "Sistem İstemi", "settings.agentBehaviour.editMode.promptOverride": "Bu yerleşik mod için özel sistem istemi geçersiz kılma", "settings.agentBehaviour.editMode.save": "Tamam", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 44d43c7073a..1d7a576b582 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -1371,7 +1371,8 @@ export const dict = { "settings.agentBehaviour.editMode": "Редагувати режим", "settings.agentBehaviour.editMode.back": "Назад до списку", "settings.agentBehaviour.editMode.description": "Опис", - "settings.agentBehaviour.editMode.native": "Вбудований режим (визначення лише для читання)", + "settings.agentBehaviour.editMode.native": + "Це вбудований режим. Його базове визначення неможливо змінити, але ви можете налаштувати перевизначення нижче.", "settings.agentBehaviour.editMode.prompt": "Системний запит", "settings.agentBehaviour.editMode.promptOverride": "Власне перевизначення системного запиту для цього вбудованого режиму", From 17b2355a26b2f6ce22e2781180fcb723e3ac298c Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:56:38 +0000 Subject: [PATCH 26/35] fix(vscode): fall back to generic "Try Model" for long model names Recover PR #7981 (force-pushed by publish workflow on April 1, 2026). When the suggested model name exceeds 30 characters (after stripping sub-provider prefix), fall back to the generic "Try Model" label to prevent an oversized notification button. Adds tryModelGeneric i18n key for the fallback label across all 18 locales. See #8558 --- .../src/components/chat/KiloNotifications.tsx | 10 ++++++++-- packages/kilo-vscode/webview-ui/src/i18n/ar.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/br.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/bs.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/da.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/de.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/en.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/es.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ja.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ko.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/nl.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/no.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/pl.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ru.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/th.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/tr.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zh.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zht.ts | 1 + 19 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx index e0d01ef86e6..06d410d1ed4 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx @@ -62,12 +62,16 @@ export const KiloNotifications: Component = () => { return true }) + const MAX_NAME = 30 + const suggestedName = createMemo(() => { const suggestion = suggestedModel() if (!suggestion) return undefined const model = provider.findModel(suggestion) if (!model?.name) return undefined - return stripSubProviderPrefix(model.name) + const name = stripSubProviderPrefix(model.name) + if (name.length > MAX_NAME) return undefined + return name }) const handleTryModel = () => { @@ -98,7 +102,9 @@ export const KiloNotifications: Component = () => {
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 018648fff11..1911d9160d2 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -1334,4 +1334,5 @@ export const dict = { "notifications.action.next": "التالي", "notifications.action.close": "إغلاق", "notifications.action.tryModel": "جرّب {{model}}", + "notifications.action.tryModelGeneric": "جرّب النموذج", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index f53d7ee3106..723bd0ace71 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -1363,4 +1363,5 @@ export const dict = { "notifications.action.next": "Próximo", "notifications.action.close": "Fechar", "notifications.action.tryModel": "Experimentar {{model}}", + "notifications.action.tryModelGeneric": "Experimentar modelo", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 804deedc915..414701e0653 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -1359,4 +1359,5 @@ export const dict = { "notifications.action.next": "Sljedeći", "notifications.action.close": "Zatvori", "notifications.action.tryModel": "Probaj {{model}}", + "notifications.action.tryModelGeneric": "Probaj model", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 0f85dd66ead..c7a0ee2bb4f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -1350,4 +1350,5 @@ export const dict = { "notifications.action.next": "Næste", "notifications.action.close": "Luk", "notifications.action.tryModel": "Prøv {{model}}", + "notifications.action.tryModelGeneric": "Prøv model", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index c0ce3dcc640..139cc84bfa5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -1377,4 +1377,5 @@ export const dict = { "notifications.action.next": "Weiter", "notifications.action.close": "Schließen", "notifications.action.tryModel": "{{model}} ausprobieren", + "notifications.action.tryModelGeneric": "Modell ausprobieren", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index cdc8808c581..64fb332609d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1360,4 +1360,5 @@ export const dict = { "notifications.action.next": "Next", "notifications.action.close": "Close", "notifications.action.tryModel": "Try {{model}}", + "notifications.action.tryModelGeneric": "Try Model", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 83ef47af004..b05fadfa265 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -1366,4 +1366,5 @@ export const dict = { "notifications.action.next": "Siguiente", "notifications.action.close": "Cerrar", "notifications.action.tryModel": "Probar {{model}}", + "notifications.action.tryModelGeneric": "Probar modelo", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 451e4dc37e5..d9ee62fe576 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -1382,4 +1382,5 @@ export const dict = { "notifications.action.next": "Suivant", "notifications.action.close": "Fermer", "notifications.action.tryModel": "Essayer {{model}}", + "notifications.action.tryModelGeneric": "Essayer le modèle", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index a7457c19d22..2466dd4acab 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -1349,4 +1349,5 @@ export const dict = { "notifications.action.next": "次へ", "notifications.action.close": "閉じる", "notifications.action.tryModel": "{{model}}を試す", + "notifications.action.tryModelGeneric": "モデルを試す", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 1c0de33a563..2ebaca8dedc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -1337,4 +1337,5 @@ export const dict = { "notifications.action.next": "다음", "notifications.action.close": "닫기", "notifications.action.tryModel": "{{model}} 시도", + "notifications.action.tryModelGeneric": "모델 시도", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index b3a648f5ab9..f7e082e2819 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -1355,6 +1355,7 @@ export const dict = { "notifications.action.next": "Volgende", "notifications.action.close": "Sluiten", "notifications.action.tryModel": "Probeer {{model}}", + "notifications.action.tryModelGeneric": "Probeer model", // Missing translations - English fallbacks until translated "settings.agentBehaviour.createMode": "Nieuwe modus aanmaken", "settings.agentBehaviour.createMode.button": "Aanmaken", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index d397d75f5a8..f99649fca0f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -1348,4 +1348,5 @@ export const dict = { "notifications.action.next": "Neste", "notifications.action.close": "Lukk", "notifications.action.tryModel": "Prøv {{model}}", + "notifications.action.tryModelGeneric": "Prøv modell", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index c5283783fb5..896cbff05d0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -1358,4 +1358,5 @@ export const dict = { "notifications.action.next": "Następny", "notifications.action.close": "Zamknij", "notifications.action.tryModel": "Wypróbuj {{model}}", + "notifications.action.tryModelGeneric": "Wypróbuj model", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 6685607d233..2d8a1bb2cc3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -1357,4 +1357,5 @@ export const dict = { "notifications.action.next": "Далее", "notifications.action.close": "Закрыть", "notifications.action.tryModel": "Попробовать {{model}}", + "notifications.action.tryModelGeneric": "Попробовать модель", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index dbd2efc8fa0..cffeb9fd8b4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -1333,4 +1333,5 @@ export const dict = { "notifications.action.next": "ถัดไป", "notifications.action.close": "ปิด", "notifications.action.tryModel": "ลองใช้ {{model}}", + "notifications.action.tryModelGeneric": "ลองใช้โมเดล", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 9728ca2defe..7759972656c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -1347,6 +1347,7 @@ export const dict = { "notifications.action.next": "Sonraki", "notifications.action.close": "Kapat", "notifications.action.tryModel": "Dene {{model}}", + "notifications.action.tryModelGeneric": "Modeli Dene", // Missing translations - English fallbacks until translated "profile.switchingAccount": "Hesap değiştiriliyor…", "settings.agentBehaviour.createMode": "Yeni Mod Oluştur", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index e2527847aef..7cc9ef99319 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -1308,4 +1308,5 @@ export const dict = { "notifications.action.next": "下一个", "notifications.action.close": "关闭", "notifications.action.tryModel": "尝试 {{model}}", + "notifications.action.tryModelGeneric": "尝试模型", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 016313e9b0b..71a73f5ee6a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -1311,4 +1311,5 @@ export const dict = { "notifications.action.next": "下一個", "notifications.action.close": "關閉", "notifications.action.tryModel": "嘗試 {{model}}", + "notifications.action.tryModelGeneric": "嘗試模型", } satisfies Partial> From f3dee356afb098aa16f6c6c7b8a95595756c47e9 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:19:10 +0000 Subject: [PATCH 27/35] fix(vscode): add missing uk translation for tryModelGeneric --- packages/kilo-vscode/webview-ui/src/i18n/uk.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 44d43c7073a..5dae7ec8116 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -1348,6 +1348,7 @@ export const dict = { "notifications.action.next": "Далі", "notifications.action.close": "Закрити", "notifications.action.tryModel": "Спробувати {{model}}", + "notifications.action.tryModelGeneric": "Спробувати модель", // Missing translations - English fallbacks until translated "profile.switchingAccount": "Перемикання акаунту…", "settings.agentBehaviour.createMode": "Створити новий режим", From 634cec88b141caf546f4dbd6843aaf9bbfe7e2e6 Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 8 Apr 2026 10:34:41 +0200 Subject: [PATCH 28/35] fix(vscode): show stop button during rate limit retry state (#8552) The PromptInput isBusy() check only matched 'busy' status, hiding the stop button and disabling Escape-to-abort when the session was in 'retry' state (e.g. rate limited). Changed to match any non-idle status so users can always abort. --- .../kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index feaca6ac761..3326f9872e6 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -244,7 +244,7 @@ export const PromptInput: Component = (props) => { window.addEventListener("compactSession", onCompact) onCleanup(() => window.removeEventListener("compactSession", onCompact)) - const isBusy = () => session.status() === "busy" + const isBusy = () => session.status() !== "idle" const isDisabled = () => !server.isConnected() const hasInput = () => text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0 const canSend = () => hasInput() && !isDisabled() && !props.blocked?.() From b8714986902b8704a98f47fc271eeadfe59232d1 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 11:46:40 +0300 Subject: [PATCH 29/35] style(cli): add kilocode markers in toast --- packages/opencode/src/cli/cmd/tui/ui/toast.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx index 32b8deac14d..cbe2eb8550c 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -60,6 +60,7 @@ function init() { const { duration, ...currentToast } = parsedOptions setStore("currentToast", currentToast) if (timeoutHandle) clearTimeout(timeoutHandle) + // kilocode_change start timeoutHandle = null if (duration && duration > 0) { timeoutHandle = setTimeout(() => { @@ -73,6 +74,7 @@ function init() { timeoutHandle = null setStore("currentToast", null) }, + // kilocode_change end error: (err: any) => { if (err instanceof Error) return toast.show({ From bb8b807dff8363be39b9411f567e618ede94d558 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 11:46:42 +0300 Subject: [PATCH 30/35] docs(cli): add KILO_CONFIG_DIR to command paths --- packages/opencode/src/kilocode/skills/kilo-config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index 70289d08dcd..96861a41e9e 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -205,7 +205,7 @@ Toggle notifications, Toggle animations, Toggle diff wrapping, Toggle sidebar (` | Project | `./kilo.json`, `./.kilo/kilo.json` | | Global | `~/.config/kilo/kilo.json` | | Managed | Linux: `/etc/kilo/kilo.json`, macOS: `/Library/Application Support/kilo/kilo.json`, Windows: `%ProgramData%\kilo\kilo.json` (enterprise, highest priority) | -| Commands | `.kilo/command/*.md` (project), `~/.config/kilo/command/*.md` (global) | +| Commands | `.kilo/command/*.md` (project), `~/.config/kilo/command/*.md` (global), `KILO_CONFIG_DIR/command/*.md` if set | | Agents | `.kilo/agent/*.md` (project), `~/.config/kilo/agent/*.md` (global) | | Skills | `.kilo/skill/*/SKILL.md`, `.kilo/skills/*/SKILL.md` | | Instructions | `AGENTS.md`, `.kilo/instructions.md`, glob patterns from `instructions` | From e0e116e9ccdaae0118931312b12450e756e8f8c6 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 11:47:05 +0300 Subject: [PATCH 31/35] docs(cli): add workflows section --- packages/opencode/src/kilocode/skills/kilo-config.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md index 96861a41e9e..572d1d23018 100644 --- a/packages/opencode/src/kilocode/skills/kilo-config.md +++ b/packages/opencode/src/kilocode/skills/kilo-config.md @@ -41,6 +41,10 @@ System prompt for this agent. `mode` values: `primary` = selectable as main agent, `subagent` = only via Task tool, `all` = both. +## Workflows (legacy) + +Markdown files in `.kilo/workflows/` or `.kilocode/workflows/` (project-level) and `~/.kilo/workflows/` or `~/.kilocode/workflows/` (global). These are automatically converted to commands at startup. The filename (minus `.md`) becomes the command name. Project workflows override global ones with the same name. + ## Permissions Scalar form applies to all patterns. Object form maps glob patterns to actions. Evaluated top-to-bottom; first match wins. From b5c756ef72b1ca621167a103c39097a67cbac21f Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 11:47:24 +0300 Subject: [PATCH 32/35] style(cli): add kilocode markers on command block --- packages/opencode/src/config/config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index f7cb7268915..73defd0732f 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -596,7 +596,8 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse command ${item}` - if (warnings) warnings.push({ path: item, message }) // kilocode_change + // kilocode_change start + if (warnings) warnings.push({ path: item, message }) try { const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) @@ -605,6 +606,7 @@ export namespace Config { } log.error("failed to load command", { command: item, err }) return undefined + // kilocode_change end }) if (!md) continue From cef00b9df3b2408ffb9b481f5dd673c286f0eb6b Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 11:47:50 +0300 Subject: [PATCH 33/35] style(cli): add kilocode markers on agent block --- packages/opencode/src/config/config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 73defd0732f..4d0a6e453a8 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -655,7 +655,8 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse agent ${item}` - if (warnings) warnings.push({ path: item, message }) // kilocode_change + // kilocode_change start + if (warnings) warnings.push({ path: item, message }) try { const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) @@ -664,6 +665,7 @@ export namespace Config { } log.error("failed to load agent", { agent: item, err }) return undefined + // kilocode_change end }) if (!md) continue From 0a2c10956e1e31a0084dc0d18a2ed6a82bcb9d55 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Wed, 8 Apr 2026 11:48:10 +0300 Subject: [PATCH 34/35] style(cli): add kilocode markers on mode block --- packages/opencode/src/config/config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 4d0a6e453a8..d4cba10d1a1 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -715,7 +715,8 @@ export namespace Config { const message = ConfigMarkdown.FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse mode ${item}` - if (warnings) warnings.push({ path: item, message }) // kilocode_change + // kilocode_change start + if (warnings) warnings.push({ path: item, message }) try { const { Session } = await import("@/session") Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) @@ -724,6 +725,7 @@ export namespace Config { } log.error("failed to load mode", { mode: item, err }) return undefined + // kilocode_change end }) if (!md) continue From 49cabd99c395dcdb85e3dd228c608e6c72c48b4d Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 8 Apr 2026 10:49:23 +0200 Subject: [PATCH 35/35] feat(agent-manager): add expand/collapse all button to inline diff panel (#8562) --- .../webview-ui/agent-manager/DiffPanel.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx index 3c00d20d5cd..af67780fe9a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx @@ -307,6 +307,11 @@ export const DiffPanel: Component = (props) => { sendAllToChat() } + const handleExpandAll = () => { + const allOpen = open().length === props.diffs.length + setOpen(allOpen ? [] : props.diffs.map((d) => d.file)) + } + const totals = createMemo(() => ({ files: props.diffs.length, additions: props.diffs.reduce((sum, diff) => sum + diff.additions, 0), @@ -354,6 +359,28 @@ export const DiffPanel: Component = (props) => {
+ 0}> + + + +