feat: add /reload action to reboot the instance from disk

Reboots the per-directory instance, reloading config, skills, agents,
commands, and MCP prompts changed on disk without restarting the
server. Sessions and history are preserved; only the per-directory
instance caches are torn down and rebuilt.

Server: POST /instance/reload wraps the existing atomic
InstanceStore.reload path (the same one project.git.init uses). The
rebuild completes before the 200 response, so clients can refetch with
no race. Returns 409 ConflictError while a session is actively
running. Emits the existing server.instance.disposed SSE event, which
the TUI and extension already use to auto-refetch.

CLI: /reload palette command calls the endpoint; the TUI already
bootstraps on server.instance.disposed.

Extension: /reload slash command, a reload button in the task header
and settings panel, and a Kilo Code: Reload Config and Skills command
palette entry. The handler clears the command cache and reuses
reloadAfterAuthChange to re-fetch config, providers, agents, skills,
and commands. Reload targets the current session's directory so Agent
Manager worktree sessions reload their own worktree instance rather
than the workspace root.

SDK: regenerate so client.instance.reload is available to external
integrations.
This commit is contained in:
marius-kilocode
2026-07-07 12:38:12 +02:00
parent 75b994330f
commit cef3dc7ae8
19 changed files with 405 additions and 0 deletions
@@ -0,0 +1,43 @@
import { describe, expect, it } from "bun:test"
import { hasBusySession } from "@/kilocode/server/httpapi/handlers/instance-reload"
import type { SessionStatus } from "@/session/status"
import { SessionID } from "@/session/schema"
const entries = (items: [string, SessionStatus.Info][]) =>
items.map(([k, v]) => [k as SessionID, v] as [SessionID, SessionStatus.Info])
describe("instance-reload hasBusySession", () => {
it("returns false for an empty map", () => {
expect(hasBusySession(new Map(entries([])))).toBe(false)
})
it("returns false when all sessions are idle", () => {
expect(
hasBusySession(
new Map(
entries([
["s1", { type: "idle" }],
["s2", { type: "idle" }],
]),
),
),
).toBe(false)
})
it("returns true when any session is busy", () => {
expect(
hasBusySession(
new Map(
entries([
["s1", { type: "idle" }],
["s2", { type: "busy" }],
]),
),
),
).toBe(true)
})
it("returns true when the only session is busy", () => {
expect(hasBusySession(new Map(entries([["s1", { type: "busy" }]])))).toBe(true)
})
})