From 0e0b709966d4a33f97cd3c87912d92fbab54f9e3 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 14 Sep 2026 10:44:42 +0200 Subject: [PATCH] refactor(vscode): share worktree reference and background child helpers --- .../agent-manager-worktree-reference.test.ts | 26 ++++++++++++++++++- .../tests/unit/background-agents.test.ts | 19 ++++++++++++++ .../agent-manager/worktree-references.ts | 13 ++++------ .../src/components/chat/background-agents.ts | 19 +++++++------- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-worktree-reference.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-worktree-reference.test.ts index fd9240e8ba3..27aa709ca4f 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-worktree-reference.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-worktree-reference.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "bun:test" import { createRoot } from "solid-js" -import { worktreeReferences } from "../../webview-ui/agent-manager/worktree-references" +import { worktreeDropReference, worktreeReferences } from "../../webview-ui/agent-manager/worktree-references" import { createProjectStore } from "../../webview-ui/agent-manager/project/store" import { buildMentionResults, @@ -68,6 +68,30 @@ function reply(scope: ReturnType, paths: string[] = []) { } describe("Agent Manager worktree references", () => { + it("drops a worktree card as the same reference shape the picker builds", () => { + const ref = worktreeDropReference( + tree("drop", { + label: "Feature", + branch: "feature/drop", + path: "/repo/.kilo/worktrees/drop", + parentBranch: "develop", + }), + "Feature", + [{ id: "ses_drop", title: "Fix login" }], + true, + ) + + expect(ref).toEqual({ + id: "drop", + name: "Feature", + branch: "feature/drop", + path: "/repo/.kilo/worktrees/drop", + base: "develop", + sessions: [{ id: "ses_drop", title: "Fix login" }], + disabled: true, + }) + }) + it("uses sidebar names and includes all sessions without selecting a transcript", () => { const state = createProjectStore("project") state.setWorktrees([tree("named", { label: "Custom name" }), tree("ordered"), tree("empty")]) diff --git a/packages/kilo-vscode/tests/unit/background-agents.test.ts b/packages/kilo-vscode/tests/unit/background-agents.test.ts index 4ad436eef29..e1793c65766 100644 --- a/packages/kilo-vscode/tests/unit/background-agents.test.ts +++ b/packages/kilo-vscode/tests/unit/background-agents.test.ts @@ -3,6 +3,7 @@ import { backgroundAgents, backgroundChildren, backgroundJobAgents, + children, fitBackgroundAgents, showBackgroundAgent, } from "../../webview-ui/src/components/chat/background-agents" @@ -101,6 +102,24 @@ describe("backgroundChildren", () => { }) }) +describe("children", () => { + it("keeps background children while listing each task child once in spawn order", () => { + const tools = [ + taskPart({ id: "part_1", child: "ses_a" }), + taskPart({ id: "part_2", child: "ses_b", background: true }), + taskPart({ id: "part_3", child: "ses_a" }), + ] + + expect(children(tools)).toEqual(["ses_a", "ses_b"]) + }) + + it("ignores non-task tools and parts without a child session", () => { + const bash = { id: "part_3", type: "tool", tool: "bash", state: { status: "running", input: {} } } as ToolPart + + expect(children([bash, taskPart({ id: "part_4", background: true })])).toEqual([]) + }) +}) + describe("backgroundAgents", () => { it("lists a running background agent from tool state metadata", () => { const tools = [taskPart({ child: "ses_child", background: true, description: "Audit deps", agent: "explore" })] diff --git a/packages/kilo-vscode/webview-ui/agent-manager/worktree-references.ts b/packages/kilo-vscode/webview-ui/agent-manager/worktree-references.ts index dc4bd23ccef..c2917cd8bd8 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/worktree-references.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/worktree-references.ts @@ -34,15 +34,12 @@ export function worktreeReferences( worktree.path, Math.max(Date.parse(worktree.createdAt) || 0, ...sessions.map((session) => updated.get(session.id) ?? 0)), ) - return { - id: worktree.id, - name: worktree.label || firstOrderedTitle(sessions, state.tabOrder()[worktree.id], basename || worktree.branch), - branch: worktree.branch, - path: worktree.path, - base: worktree.parentBranch, + return worktreeDropReference( + worktree, + worktree.label || firstOrderedTitle(sessions, state.tabOrder()[worktree.id], basename || worktree.branch), sessions, - disabled: worktree.id === current || state.staleWorktreeIds().has(worktree.id) || state.busy().has(worktree.id), - } + worktree.id === current || state.staleWorktreeIds().has(worktree.id) || state.busy().has(worktree.id), + ) }) .sort( (a, b) => diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/background-agents.ts b/packages/kilo-vscode/webview-ui/src/components/chat/background-agents.ts index 48e29c8072b..71554ed54ee 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/background-agents.ts +++ b/packages/kilo-vscode/webview-ui/src/components/chat/background-agents.ts @@ -66,11 +66,12 @@ function meta(part: ToolPart, key: string): unknown { return (part.state as { metadata?: Record }).metadata?.[key] } -/** Child session IDs of every Task tool part, in spawn order, without duplicates. */ -export function children(tools: ToolPart[]): string[] { +/** Child session IDs of Task tool parts, optionally restricted to background jobs. */ +function taskChildren(tools: ToolPart[], background?: boolean): string[] { const ids: string[] = [] for (const part of tools) { if (part.tool !== "task") continue + if (background !== undefined && meta(part, "background") !== background) continue const id = text(meta(part, "sessionId")) if (!id || ids.includes(id)) continue ids.push(id) @@ -78,16 +79,14 @@ export function children(tools: ToolPart[]): string[] { return ids } +/** Child session IDs of every Task tool part, in spawn order, without duplicates. */ +export function children(tools: ToolPart[]): string[] { + return taskChildren(tools) +} + /** Child session IDs spawned as background jobs, which show a capped reasoning preview. */ export function backgroundChildren(tools: ToolPart[]): Set { - const ids = new Set() - for (const part of tools) { - if (part.tool !== "task") continue - if (meta(part, "background") !== true) continue - const id = text(meta(part, "sessionId")) - if (id) ids.add(id) - } - return ids + return new Set(taskChildren(tools, true)) } function working(status: SessionStatusInfo | undefined): boolean {