Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/warm-agent-manager-mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Start MCP servers while Agent Manager worktree sessions initialize to reduce the delay before the first response.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { diffSummary as localDiffSummary, diffFile as localDiffFile } from "./lo
import { parseToolRequest, startFromTool, type ToolRequest } from "./tool-start"
import { stopSessionProcesses } from "../kilo-provider/background-process"

import { startSession } from "./mcp-warmup"
import { buildKeybindingMap } from "./format-keybinding"
import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version"
import { Semaphore } from "./semaphore"
Expand Down Expand Up @@ -810,9 +811,11 @@ export class AgentManagerProvider implements Disposable {
})

try {
const { data: session } = await client.session.create(
{ directory: worktreePath, platform: PLATFORM },
{ throwOnError: true },
const { data: session } = await startSession(
client,
worktreePath,
() => client.session.create({ directory: worktreePath, platform: PLATFORM }, { throwOnError: true }),
(...args) => this.log(...args),
)
return session
} catch (error) {
Expand Down
15 changes: 15 additions & 0 deletions packages/kilo-vscode/src/agent-manager/mcp-warmup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { KiloClient } from "@kilocode/sdk/v2/client"

type Client = Pick<KiloClient, "mcp">
type Log = (...args: unknown[]) => void

async function warm(client: Client, dir: string, log: Log): Promise<void> {
log(`[MCPWarmup] Starting for ${dir}`)
await client.mcp.status({ directory: dir }, { throwOnError: true })
log(`[MCPWarmup] Completed for ${dir}`)
}

export function startSession<T>(client: Client, dir: string, create: () => Promise<T>, log: Log): Promise<T> {
void warm(client, dir, log).catch((err) => log(`[MCPWarmup] Failed for ${dir}:`, err))
Comment thread
marius-kilocode marked this conversation as resolved.
return create()
}
10 changes: 10 additions & 0 deletions packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ describe("Agent Manager Provider Messages", () => {
expect(body).toContain("agentManager.sessionAdded")
})

it("warms MCP before creating every new worktree session", () => {
const body = getMethodBody("createSessionInWorktree")
const warmup = body.indexOf("startSession(")
const create = body.indexOf("client.session.create(")

expect(warmup).toBeGreaterThanOrEqual(0)
expect(create).toBeGreaterThanOrEqual(0)
expect(warmup).toBeLessThan(create)
})

it("state-mutating messages wait for state initialization", () => {
const body = getMethodBody("shouldWaitForState")
const messages = [
Expand Down
84 changes: 84 additions & 0 deletions packages/kilo-vscode/tests/unit/agent-manager-mcp-warmup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { describe, expect, it } from "bun:test"
import { startSession } from "../../src/agent-manager/mcp-warmup"

function tick(): Promise<void> {
return new Promise((resolve) => queueMicrotask(resolve))
}

describe("Agent Manager MCP warmup", () => {
it("starts MCP status for the worktree directory before session creation", async () => {
const calls: unknown[][] = []
const client = {
mcp: {
status: (input: unknown, opts: unknown) => {
calls.push(["warm", input, opts])
return Promise.resolve({ data: {} })
},
},
}

const result = await startSession(
client as never,
"/repo/.kilo/worktrees/feature",
async () => {
calls.push(["session"])
return "created"
},
() => {},
)

expect(result).toBe("created")
expect(calls[0]).toEqual(["warm", { directory: "/repo/.kilo/worktrees/feature" }, { throwOnError: true }])
expect(calls[1]).toEqual(["session"])
})

it("does not wait for MCP warmup before creating the session", async () => {
const calls: string[] = []
const warmup = new Promise<unknown>(() => {})
const client = {
mcp: {
status: () => {
calls.push("warm")
return warmup
},
},
}

const result = await startSession(
client as never,
"/repo/.kilo/worktrees/feature",
async () => {
calls.push("session")
return "created"
},
() => {},
)

expect(result).toBe("created")
expect(calls).toEqual(["warm", "session"])
})

it("logs and contains MCP warmup failures", async () => {
const logs: unknown[][] = []
const client = {
mcp: {
status: () => {
throw new Error("connection failed")
},
},
}

const result = await startSession(
client as never,
"/repo/.kilo/worktrees/feature",
async () => "created",
(...args) => logs.push(args),
)
await tick()

expect(result).toBe("created")
expect(logs[0]).toEqual(["[MCPWarmup] Starting for /repo/.kilo/worktrees/feature"])
expect(logs[1]?.[0]).toBe("[MCPWarmup] Failed for /repo/.kilo/worktrees/feature:")
expect(logs[1]?.[1]).toBeInstanceOf(Error)
})
})
Loading