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/faster-agent-manager-worktree-start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Speed up Agent Manager worktree creation and show ready sessions immediately.
12 changes: 9 additions & 3 deletions packages/kilo-vscode/src/agent-manager/WorktreeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,8 @@ export class WorktreeManager {
}

const existing = await this.git
.branch()
.then((result) => result.all)
.raw(["for-each-ref", "--format=%(refname:lstrip=2)", "refs/heads"])
.then((refs) => refs.trim().split(/\r?\n/).filter(Boolean))
.catch(() => [] as string[])
const sanitized = params.branchName ? sanitizeBranchName(params.branchName) : undefined
const branch = sanitized || generateBranchName(params.prompt || "agent-task", existing)
Expand Down Expand Up @@ -392,7 +392,13 @@ export class WorktreeManager {
*/
private async runWorktreeAdd(args: string[], wtPath: string): Promise<void> {
try {
await this.git.raw(args)
const workers = await this.git.getConfig("checkout.workers").catch((error: unknown) => {
this.log(
`Failed to inspect checkout worker configuration: ${error instanceof Error ? error.message : String(error)}`,
)
return undefined
})
await this.git.raw(workers?.value === null ? ["-c", "checkout.workers=2", ...args] : args)
} catch (error) {
const msg = error instanceof Error ? error.message : String(error)
if (this.isHookError(msg) && (await this.worktreeRegistered(wtPath))) {
Expand Down
11 changes: 11 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 @@ -1152,3 +1152,14 @@ describe("Shared webview provider shell", () => {
expect(fs.readFileSync(PROVIDER_SHELL_FILE, "utf-8")).not.toContain("WorktreeModeProvider")
})
})

describe("Agent Manager worktree setup", () => {
it("dismisses successful setup overlays immediately and retains the error delay", () => {
const source = fs.readFileSync(AGENT_MANAGER_APP_FILE, "utf-8")
expect(source).toContain('globalThis.setTimeout(() => setSetup({ active: false, message: "" }), error ? 3000 : 0)')
expect(source).not.toContain(
'globalThis.setTimeout(() => setSetup({ active: false, message: "" }), error ? 3000 : 500)',
)
expect(source).toContain("globalThis.setTimeout")
})
})
50 changes: 50 additions & 0 deletions packages/kilo-vscode/tests/unit/worktree-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,45 @@ describe("WorktreeManager.createWorktree", () => {

expect(result.parentBranch).toBe(branch)
})

it("uses two checkout workers without changing Git configuration", async () => {
const root = await createTempRepo()
const hook = path.join(root, ".git", "hooks", "post-checkout")
const file = path.join(root, "workers")
await fs.writeFile(hook, `#!/bin/sh\ngit config --get checkout.workers > "${file}"\n`)
await fs.chmod(hook, 0o755)

await createManager(root).createWorktree({ branchName: "parallel-checkout" })

expect((await fs.readFile(file, "utf8")).trim()).toBe("2")
expect((await simpleGit(root).getConfig("checkout.workers")).value).toBeNull()
})

it("preserves an explicitly configured checkout worker count", async () => {
const root = await createTempRepo()
const hook = path.join(root, ".git", "hooks", "post-checkout")
const file = path.join(root, "workers")
gitExec(["git", "-C", root, "config", "checkout.workers", "1"])
await fs.writeFile(hook, `#!/bin/sh\ngit config --get checkout.workers > "${file}"\n`)
await fs.chmod(hook, 0o755)

await createManager(root).createWorktree({ branchName: "configured-checkout" })

expect((await fs.readFile(file, "utf8")).trim()).toBe("1")
expect((await simpleGit(root).getConfig("checkout.workers")).value).toBe("1")
})

it("retains post-checkout hook failure tolerance with parallel checkout", async () => {
const root = await fs.realpath(await createTempRepo())
const hook = path.join(root, ".git", "hooks", "post-checkout")
await fs.writeFile(hook, "#!/bin/sh\nprintf 'post-checkout hook failed' >&2\nexit 1\n")
await fs.chmod(hook, 0o755)

const result = await createManager(root).createWorktree({ branchName: "hook-failure" })

expect(existsSync(result.path)).toBe(true)
expect((await simpleGit(root).raw(["worktree", "list", "--porcelain"])).includes(result.path)).toBe(true)
})
})

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -813,6 +852,17 @@ describe("WorktreeManager.createWorktree branch collision", () => {
expect(second.branch).toBe("collide-2")
expect((await fs.stat(path.join(second.path, ".git"))).isFile()).toBe(true)
})

it("does not treat remote-tracking refs as local branch collisions", async () => {
const root = await createTempRepo()
const git = simpleGit(root)
const hash = (await git.revparse(["HEAD"])).trim()
await git.raw(["update-ref", "refs/remotes/origin/remote-name", hash])

const result = await createManager(root).createWorktree({ branchName: "remote-name" })

expect(result.branch).toBe("remote-name")
})
})

// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1416,7 +1416,7 @@ const AgentManagerContent: Component = () => {
worktreeId: ev.worktreeId,
errorCode: ev.errorCode,
})
globalThis.setTimeout(() => setSetup({ active: false, message: "" }), error ? 3000 : 500)
globalThis.setTimeout(() => setSetup({ active: false, message: "" }), error ? 3000 : 0)
if (!error && ev.sessionId) {
session.selectSession(ev.sessionId)
const ms = managedSessions().find((s) => s.id === ev.sessionId)
Expand Down
Loading