From f5f4685cf13ef4063055f2a498db181c3c0509a8 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 24 Mar 2026 17:30:52 +0100 Subject: [PATCH 01/15] feat(vscode): add "Continue in Worktree" to transfer sessions from sidebar/local to isolated worktrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a button (sidebar + Agent Manager local sessions) that captures git state as patches, creates a worktree, applies changes, and forks the session — all without modifying the source working tree. --- packages/kilo-vscode/src/KiloProvider.ts | 26 ++++ .../src/agent-manager/AgentManagerProvider.ts | 37 +++++ .../src/agent-manager/continue-in-worktree.ts | 114 ++++++++++++++ .../src/agent-manager/git-transfer.ts | 147 ++++++++++++++++++ .../kilo-vscode/src/agent-manager/types.ts | 5 + packages/kilo-vscode/src/extension.ts | 5 + .../src/components/chat/ChatView.tsx | 52 ++++++- .../webview-ui/src/types/messages.ts | 25 +++ 8 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts create mode 100644 packages/kilo-vscode/src/agent-manager/git-transfer.ts diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 9439e6fb265..8a05915c61a 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -148,6 +148,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper * Return null to consume the message, or return a (possibly transformed) message. */ private onBeforeMessage: ((msg: Record) => Promise | null>) | null = null + /** Handler for "Continue in Worktree" — set by extension.ts to delegate to AgentManagerProvider. */ + private continueInWorktreeHandler: + | ((sessionId: string, progress: (status: string, detail?: string, error?: string) => void) => Promise) + | null = null + constructor( private readonly extensionUri: vscode.Uri, private readonly connectionService: KiloConnectionService, @@ -374,6 +379,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.postMessage({ type: "openCloudSession", sessionId }) } + /** Register the handler for "Continue in Worktree" messages from the sidebar. */ + public setContinueInWorktreeHandler( + handler: (sessionId: string, progress: (status: string, detail?: string, error?: string) => void) => Promise, + ): void { + this.continueInWorktreeHandler = handler + } + /** * Attach to a webview that already has its own HTML set. * Sets up message handling and connection without overriding HTML content. @@ -540,6 +552,20 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "openChanges": vscode.commands.executeCommand("kilo-code.new.showChanges") break + case "continueInWorktree": + if (message.sessionId && this.continueInWorktreeHandler) { + this.continueInWorktreeHandler(message.sessionId, (status: string, detail?: string, error?: string) => { + this.postMessage({ type: "continueInWorktreeProgress", status, detail, error }) + }).catch((err: unknown) => { + console.error("[Kilo New] continueInWorktree failed:", err) + this.postMessage({ + type: "continueInWorktreeProgress", + status: "error", + error: err instanceof Error ? err.message : String(err), + }) + }) + } + break case "retryConnection": console.log("[Kilo New] KiloProvider: 🔄 Retrying connection...") this.initializeConnection().catch((e) => diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index afc4e63c3ce..6cd83042791 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -18,6 +18,7 @@ import { SessionTerminalManager } from "./SessionTerminalManager" import { createTerminalHost } from "./terminal-host" import { executeVscodeTask } from "./task-runner" import { forkSession } from "./fork-session" +import { continueInWorktree } from "./continue-in-worktree" import { shouldStopDiffPolling } from "./delete-worktree" import { buildKeybindingMap } from "./format-keybinding" import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version" @@ -1811,6 +1812,42 @@ export class AgentManagerProvider implements Disposable { return this.panel?.sessions.getSessionDirectories() ?? new Map() } + /** + * Continue a sidebar session in a new worktree. + * Captures git state, creates worktree, applies state, forks session. + * Called from KiloProvider when the sidebar sends "continueInWorktree". + */ + public async continueFromSidebar( + sessionId: string, + progress: (status: string, detail?: string, error?: string) => void, + ): Promise { + const root = this.getRoot() + if (!root) { + progress("error", undefined, "No workspace folder open") + return + } + + this.openPanel() + await this.waitForStateReady("continueFromSidebar") + + await continueInWorktree( + { + root, + getClient: () => this.connectionService.getClient(), + createWorktreeOnDisk: (opts) => this.createWorktreeOnDisk(opts), + runSetupScript: (p, b, id) => this.runSetupScriptForWorktree(p, b, id), + getStateManager: () => this.getStateManager(), + registerWorktreeSession: (sid, dir) => this.registerWorktreeSession(sid, dir), + registerSession: (session) => this.panel?.sessions.registerSession(session), + notifyReady: (sid, result, wid) => this.notifyWorktreeReady(sid, result, wid), + capture: (event, props) => this.host.capture(event, props), + log: (...args) => this.log(...args), + }, + sessionId, + progress, + ) + } + public postMessage(message: unknown): void { this.panel?.postMessage(message) } diff --git a/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts new file mode 100644 index 00000000000..a42d05c9cc6 --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts @@ -0,0 +1,114 @@ +import type { KiloClient, Session } from "@kilocode/sdk/v2/client" +import type { CreateWorktreeResult } from "./WorktreeManager" +import type { WorktreeStateManager } from "./WorktreeStateManager" +import { capture as captureGitState, apply as applyGitState } from "./git-transfer" +import { getErrorMessage } from "../kilo-provider-utils" +import { PLATFORM } from "./constants" + +export interface ContinueContext { + root: string + getClient: () => KiloClient + createWorktreeOnDisk: (opts: { baseBranch: string }) => Promise<{ + worktree: { id: string } + result: CreateWorktreeResult + } | null> + runSetupScript: (path: string, branch: string, worktreeId: string) => Promise + getStateManager: () => WorktreeStateManager | undefined + registerWorktreeSession: (sessionId: string, directory: string) => void + registerSession: (session: Session) => void + notifyReady: (sessionId: string, result: CreateWorktreeResult, worktreeId: string) => void + capture: (event: string, props: Record) => void + log: (...args: unknown[]) => void +} + +/** + * Continue a sidebar session in a new worktree. + * Captures git state, creates worktree, applies state, forks session. + * + * Pure orchestration — no vscode imports. + */ +export async function continueInWorktree( + ctx: ContinueContext, + sessionId: string, + progress: (status: string, detail?: string, error?: string) => void, +): Promise { + // Abort the session if it's running + try { + const client = ctx.getClient() + await client.session.abort({ sessionID: sessionId }).catch((err) => { + ctx.log("Session abort failed (may already be idle):", getErrorMessage(err)) + }) + } catch (err) { + ctx.log("Client not available for abort, continuing:", getErrorMessage(err)) + } + + // 1. Capture git state (read-only, non-destructive) + progress("capturing", "Capturing git changes...") + let snapshot + try { + snapshot = await captureGitState(ctx.root, (...args) => ctx.log(...args)) + } catch (err) { + progress("error", undefined, `Failed to capture git state: ${getErrorMessage(err)}`) + return + } + + // 2. Create worktree from current branch + progress("creating", "Creating worktree...") + const created = await ctx.createWorktreeOnDisk({ baseBranch: snapshot.branch }) + if (!created) { + progress("error", undefined, "Failed to create worktree") + return + } + + // 3. Run setup script + progress("setup", "Running setup script...") + await ctx.runSetupScript(created.result.path, created.result.branch, created.worktree.id) + + // 4. Apply git state to worktree + progress("transferring", "Transferring changes...") + const applied = await applyGitState(snapshot, created.result.path, (...args) => ctx.log(...args)) + if (!applied.ok) { + ctx.log("Git state transfer failed, continuing with empty worktree:", applied.error) + } + + // 5. Fork session into worktree + progress("forking", "Forking session...") + let client: KiloClient + try { + client = ctx.getClient() + } catch (err) { + ctx.log("Client not available for session fork:", getErrorMessage(err)) + progress("error", undefined, "Not connected to CLI backend") + return + } + + let forked: Session + try { + const { data } = await client.session.fork( + { sessionID: sessionId, directory: created.result.path }, + { throwOnError: true }, + ) + forked = data + } catch (err) { + progress("error", undefined, `Failed to fork session: ${getErrorMessage(err)}`) + return + } + + // 6. Register session in state and notify + const state = ctx.getStateManager() + if (state) { + state.addSession(forked.id, created.worktree.id) + } + ctx.registerWorktreeSession(forked.id, created.result.path) + ctx.registerSession(forked) + ctx.notifyReady(forked.id, created.result, created.worktree.id) + + ctx.capture("Continue in Worktree", { + source: PLATFORM, + sessionId: forked.id, + worktreeId: created.worktree.id, + }) + ctx.log(`Continued sidebar session ${sessionId} → worktree ${created.worktree.id} (session ${forked.id})`) + + progress("done") +} diff --git a/packages/kilo-vscode/src/agent-manager/git-transfer.ts b/packages/kilo-vscode/src/agent-manager/git-transfer.ts new file mode 100644 index 00000000000..5341ab857ad --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/git-transfer.ts @@ -0,0 +1,147 @@ +import * as nodePath from "path" +import * as fs from "fs/promises" +import * as cp from "child_process" + +/** + * Portable git state snapshot — captures uncommitted changes as patches + * that can be applied to any directory on the same commit. + * + * Used by "Continue in Worktree" to copy git state from the user's + * working tree into a fresh worktree without modifying the source. + */ +export interface GitSnapshot { + branch: string + head: string + /** Binary-safe unified diff of unstaged changes, or null if clean. */ + unstaged: string | null + /** Binary-safe unified diff of staged changes, or null if none staged. */ + staged: string | null + /** Untracked files (new files not yet added to git). */ + untracked: UntrackedFile[] +} + +export interface UntrackedFile { + /** Relative path from repo root. */ + path: string + /** Raw file content. */ + content: Buffer +} + +const MAX_FILE = 10 * 1024 * 1024 // 10 MB + +function git(args: string[], cwd: string, stdin?: string): Promise<{ code: number; stdout: string; stderr: string }> { + return new Promise((resolve) => { + const child = cp.execFile( + "git", + args, + { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }, + (error, stdout, stderr) => { + if (!error) { + resolve({ code: 0, stdout, stderr }) + return + } + const exec = error as cp.ExecException + resolve({ code: typeof exec.code === "number" ? exec.code : 1, stdout: stdout ?? "", stderr: stderr ?? "" }) + }, + ) + if (stdin !== undefined && child.stdin) { + child.stdin.end(stdin) + } + }) +} + +async function raw(args: string[], cwd: string): Promise { + const result = await git(args, cwd) + return result.stdout.trim() +} + +/** + * Capture the current git state from `cwd` as a portable snapshot. + * This is a read-only operation — the source directory is never modified. + */ +export async function capture(cwd: string, log: (...args: unknown[]) => void): Promise { + const [branch, head, unstaged, staged, untrackedRaw] = await Promise.all([ + raw(["branch", "--show-current"], cwd), + raw(["rev-parse", "HEAD"], cwd), + raw(["diff", "--binary"], cwd).then((s: string) => (s ? s : null)), + raw(["diff", "--cached", "--binary"], cwd).then((s: string) => (s ? s : null)), + raw(["ls-files", "--others", "--exclude-standard"], cwd).then((s: string) => + s.split("\n").filter((l: string) => l.length > 0), + ), + ]) + + const untracked: UntrackedFile[] = [] + for (const rel of untrackedRaw) { + const full = nodePath.resolve(cwd, rel) + try { + const stat = await fs.stat(full) + if (stat.size > MAX_FILE) { + log(`Skipping untracked file ${rel}: ${(stat.size / 1024 / 1024).toFixed(1)} MB exceeds limit`) + continue + } + const content = await fs.readFile(full) + untracked.push({ path: rel, content }) + } catch (err) { + log(`Failed to read untracked file ${rel}:`, err) + } + } + + return { branch, head, unstaged, staged, untracked } +} + +/** + * Apply a git snapshot to a target directory. + * Applies staged changes (and re-stages them), unstaged changes, and writes untracked files. + */ +export async function apply( + snapshot: GitSnapshot, + target: string, + log: (...args: unknown[]) => void, +): Promise<{ ok: boolean; error?: string }> { + // Apply staged patch first, then re-stage those files + if (snapshot.staged) { + const result = await git(["apply", "--whitespace=nowarn", "-"], target, snapshot.staged) + if (result.code !== 0) { + const msg = result.stderr.trim() || "Patch did not apply" + log("Failed to apply staged patch:", msg) + return { ok: false, error: `Staged patch failed: ${msg}` } + } + const files = parsePatchFiles(snapshot.staged) + if (files.length > 0) { + await git(["add", "--", ...files], target) + } + } + + // Apply unstaged patch (leave as unstaged working-tree changes) + if (snapshot.unstaged) { + const result = await git(["apply", "--whitespace=nowarn", "-"], target, snapshot.unstaged) + if (result.code !== 0) { + const msg = result.stderr.trim() || "Patch did not apply" + log("Failed to apply unstaged patch:", msg) + return { ok: false, error: `Unstaged patch failed: ${msg}` } + } + } + + // Write untracked files + for (const file of snapshot.untracked) { + const full = nodePath.resolve(target, file.path) + try { + await fs.mkdir(nodePath.dirname(full), { recursive: true }) + await fs.writeFile(full, file.content) + } catch (err) { + log(`Failed to write untracked file ${file.path}:`, err) + } + } + + return { ok: true } +} + +/** Extract file paths from a unified diff's `diff --git a/... b/...` headers. */ +function parsePatchFiles(patch: string): string[] { + const files: string[] = [] + for (const line of patch.split("\n")) { + const match = /^diff --git a\/.+ b\/(.+)$/.exec(line) + if (match && match[1]) files.push(match[1]) + } + return files +} diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 19f2e730413..3efeded8650 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -416,6 +416,11 @@ interface AbortIn { sessionID: string } +interface ContinueInWorktreeIn { + type: "continueInWorktree" + sessionId: string +} + /** All messages the Agent Manager expects from the webview (onMessage input). */ export type AgentManagerInMessage = | CreateWorktreeIn diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 89c3d460760..d9baac5b5d4 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -61,6 +61,11 @@ export function activate(context: vscode.ExtensionContext) { const agentManagerProvider = new AgentManagerProvider(agentManagerHost, connectionService) context.subscriptions.push(agentManagerProvider) + // Wire "Continue in Worktree" from sidebar → Agent Manager + provider.setContinueInWorktreeHandler((sessionId, progress) => + agentManagerProvider.continueFromSidebar(sessionId, progress), + ) + // Register serializer so Agent Manager restores when VS Code restarts context.subscriptions.push( vscode.window.registerWebviewPanelSerializer(AgentManagerProvider.viewType, { diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 6e6c106e162..5fc1158ca57 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -3,9 +3,10 @@ * Main chat container that combines all chat components */ -import { Component, Show, createEffect, createMemo, on, onCleanup, onMount } from "solid-js" +import { Component, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { Icon } from "@kilocode/kilo-ui/icon" +import { Spinner } from "@kilocode/kilo-ui/spinner" import { TaskHeader } from "./TaskHeader" import { MessageList } from "./MessageList" import { PromptInput } from "./PromptInput" @@ -31,11 +32,17 @@ export const ChatView: Component = (props) => { const server = useServer() // Show "Show Changes" only in the standalone sidebar, not inside Agent Manager const isSidebar = () => worktreeMode === undefined + // Show "Continue in Worktree" in sidebar or local Agent Manager sessions (not already in a worktree) + const canContinueInWorktree = () => worktreeMode === undefined || worktreeMode.mode() === "local" const id = () => session.currentSessionID() const hasMessages = () => session.messages().length > 0 const idle = () => session.status() !== "busy" + // "Continue in Worktree" state + const [transferring, setTransferring] = createSignal(false) + const [transferDetail, setTransferDetail] = createSignal("") + // Permissions and questions scoped to this session's family (self + subagents). // Each ChatView only sees its own session tree — no cross-session leakage. // Memoized so the BFS walk in sessionFamily() runs once per reactive update, @@ -76,6 +83,28 @@ export const ChatView: Component = (props) => { onCleanup(() => document.removeEventListener("keydown", handler)) }) + // Listen for "Continue in Worktree" progress messages + { + const labels: Record = { + capturing: "Capturing changes...", + creating: "Creating worktree...", + setup: "Running setup...", + transferring: "Transferring changes...", + forking: "Starting session...", + } + const cleanup = vscode.onMessage((msg) => { + if (msg.type !== "continueInWorktreeProgress") return + const status = (msg as { status: string }).status + if (status === "done" || status === "error") { + setTransferring(false) + setTransferDetail("") + return + } + setTransferDetail(labels[status] ?? "Working...") + }) + onCleanup(cleanup) + } + const decide = (response: "once" | "always" | "reject", approvedAlways: string[], deniedAlways: string[]) => { const perm = permissionRequest() if (!perm || session.respondingPermissions().has(perm.id)) return @@ -131,6 +160,27 @@ export const ChatView: Component = (props) => { {language.t("command.session.show.changes")} + + + diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index f22ba82a24f..79b9d94cc1f 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -1289,6 +1289,7 @@ export type ExtensionMessage = | ProviderActionErrorMessage | RecentsLoadedMessage | LanguageChangedMessage + | ContinueInWorktreeProgressMessage // ============================================ // Messages FROM webview TO extension @@ -1900,6 +1901,29 @@ export interface RequestRecentsMessage { type: "requestRecents" } +// Continue in Worktree: transfer sidebar session + git state to an isolated worktree +export interface ContinueInWorktreeRequest { + type: "continueInWorktree" + sessionId: string +} + +export type ContinueInWorktreeStatus = + | "capturing" + | "creating" + | "setup" + | "transferring" + | "forking" + | "done" + | "error" + +// Continue in Worktree: progress updates (extension → webview) +export interface ContinueInWorktreeProgressMessage { + type: "continueInWorktreeProgress" + status: ContinueInWorktreeStatus + detail?: string + error?: string +} + export type WebviewMessage = | SendMessageRequest | AbortRequest @@ -2012,6 +2036,7 @@ export type WebviewMessage = | SaveCustomProviderMessage | PersistRecentsRequest | RequestRecentsMessage + | ContinueInWorktreeRequest // ============================================ // VS Code API type From 829395b1a550dfae9dc6ec0f0904687592c4833e Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 24 Mar 2026 17:45:25 +0100 Subject: [PATCH 02/15] =?UTF-8?q?fix(vscode):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20wire=20AM=20handler,=20fail=20on=20transfer=20error?= =?UTF-8?q?,=20hide=20button=20in=20worktree=20sessions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Handle continueInWorktree in AgentManagerProvider.onMessage so the button works from local Agent Manager sessions - Add ContinueInWorktreeIn to AgentManagerInMessage union - Stop and report error when git state transfer fails instead of silently continuing with an empty worktree - Use explicit continueInWorktree prop on ChatView so the button only shows in sidebar and Agent Manager local sessions, not worktree tabs --- .../kilo-vscode/src/agent-manager/AgentManagerProvider.ts | 6 ++++++ .../kilo-vscode/src/agent-manager/continue-in-worktree.ts | 4 +++- packages/kilo-vscode/src/agent-manager/types.ts | 1 + .../webview-ui/agent-manager/AgentManagerApp.tsx | 1 + .../kilo-vscode/webview-ui/src/components/chat/ChatView.tsx | 6 ++++-- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 6cd83042791..765840c2068 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -198,6 +198,12 @@ export class AgentManagerProvider implements Disposable { this.panel.sessions.clearSessionDirectory(m.sessionId) return null } + if (m.type === "continueInWorktree") { + void this.continueFromSidebar(m.sessionId, (status, detail, error) => { + this.panel?.postMessage({ type: "continueInWorktreeProgress", status, detail, error }) + }) + return null + } if (m.type === "agentManager.addSessionToWorktree") return this.onAddSessionToWorktree(m.worktreeId) if (m.type === "agentManager.forkSession") return this.onForkSession(m.sessionId, m.worktreeId) if (m.type === "agentManager.closeSession") return this.onCloseSession(m.sessionId) diff --git a/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts index a42d05c9cc6..5ce466201ff 100644 --- a/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts +++ b/packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts @@ -68,7 +68,9 @@ export async function continueInWorktree( progress("transferring", "Transferring changes...") const applied = await applyGitState(snapshot, created.result.path, (...args) => ctx.log(...args)) if (!applied.ok) { - ctx.log("Git state transfer failed, continuing with empty worktree:", applied.error) + ctx.log("Git state transfer failed:", applied.error) + progress("error", undefined, applied.error ?? "Failed to apply changes to worktree") + return } // 5. Fork session into worktree diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 3efeded8650..3dab224e322 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -462,3 +462,4 @@ export type AgentManagerInMessage = | LoadMessagesIn | ClearSessionIn | AbortIn + | ContinueInWorktreeIn diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 0e12ae2261d..02a80e40495 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -2758,6 +2758,7 @@ const AgentManagerContent: Component = () => { openLocally(id) }} readonly={readOnly()} + continueInWorktree={selection() === LOCAL} />
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 5fc1158ca57..e7d72315850 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -22,6 +22,8 @@ import { useServer } from "../../context/server" interface ChatViewProps { onSelectSession?: (id: string) => void readonly?: boolean + /** When true, show the "Continue in Worktree" button. Defaults to true in the sidebar. */ + continueInWorktree?: boolean } export const ChatView: Component = (props) => { @@ -32,8 +34,8 @@ export const ChatView: Component = (props) => { const server = useServer() // Show "Show Changes" only in the standalone sidebar, not inside Agent Manager const isSidebar = () => worktreeMode === undefined - // Show "Continue in Worktree" in sidebar or local Agent Manager sessions (not already in a worktree) - const canContinueInWorktree = () => worktreeMode === undefined || worktreeMode.mode() === "local" + // Show "Continue in Worktree": explicit prop, or default to true in sidebar + const canContinueInWorktree = () => props.continueInWorktree ?? isSidebar() const id = () => session.currentSessionID() const hasMessages = () => session.messages().length > 0 From dd5d966046773aa2b11d30ba39c378255d64a057 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 24 Mar 2026 18:05:54 +0100 Subject: [PATCH 03/15] fix(vscode): put Show Changes and Continue in Worktree on same row with flex-wrap --- .../src/components/chat/ChatView.tsx | 66 ++++++++++--------- .../webview-ui/src/styles/chat.css | 11 ++++ 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index e7d72315850..f1cc399c27b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -150,38 +150,42 @@ export const ChatView: Component = (props) => { > {language.t("command.session.new.task")} - - - - - - {transferring() ? transferDetail() : "Continue in Worktree"} - + + + +
diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 73604369a59..9da9e92c6be 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -176,6 +176,17 @@ padding: 8px 12px 0; } +.session-actions-row { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.session-actions-row > * { + flex: 1 1 auto; + min-width: 0; +} + /* ============================================ Message List ============================================ */ From 12e33433a4fd7099446e3decaeaa3fef48d2509a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 25 Mar 2026 16:34:44 +0100 Subject: [PATCH 04/15] fix(vscode): fix session action buttons to share one row Remove data-full-width from buttons inside the flex row (it forced width:100% overriding flex). Use flex:1 with min-width:fit-content so they share the row and only wrap when genuinely too narrow. --- .../kilo-vscode/webview-ui/src/components/chat/ChatView.tsx | 2 -- packages/kilo-vscode/webview-ui/src/styles/chat.css | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index f1cc399c27b..39cf74fbb1b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -156,7 +156,6 @@ export const ChatView: Component = (props) => { - -
- - - - - - -
-
+
+ + + + + + + +
diff --git a/some b/some new file mode 100644 index 00000000000..5e885ccfa4f --- /dev/null +++ b/some @@ -0,0 +1 @@ +fjdsklfjsd From 14ba3f49c53c03ea3fdb0c239ef6eed046750c1b Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 25 Mar 2026 16:50:44 +0100 Subject: [PATCH 06/15] chore: remove stray file --- some | 1 - 1 file changed, 1 deletion(-) delete mode 100644 some diff --git a/some b/some deleted file mode 100644 index 5e885ccfa4f..00000000000 --- a/some +++ /dev/null @@ -1 +0,0 @@ -fjdsklfjsd From 4dc55f6e595b1a5604ce46b76e88d3667fdf89b7 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 25 Mar 2026 17:12:59 +0100 Subject: [PATCH 07/15] fix(vscode): replace Show Changes with diff stats badge, shorten Worktree label Replace the "Show Changes" text button with a compact diff stats badge showing file count, additions, and deletions (3f +42 -8) using data from session.summary(). Shorten "Continue in Worktree" to "Worktree". All three buttons share one row. --- .../src/components/chat/ChatView.tsx | 17 ++++---- .../webview-ui/src/styles/chat.css | 42 ++++++++++++++++++- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 6ca8166718a..c50fd424c7a 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -151,15 +151,18 @@ export const ChatView: Component = (props) => { {language.t("command.session.new.task")} - + + 0f}> + {session.summary()!.files}f + +{session.summary()!.additions} + -{session.summary()!.deletions} + + diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index d33e14fb3f8..60847a9383c 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -180,13 +180,53 @@ display: flex; flex-wrap: wrap; gap: 4px; + align-items: center; } -.session-actions-row > [data-component="button"] { +.session-actions-row > [data-component="button"], +.session-actions-row > .session-diff-badge { flex: 1 1 0; min-width: fit-content; } +/* Diff stats badge — matches Agent Manager diff toggle style */ +.session-diff-badge { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + padding: 4px 8px; + border: none; + border-radius: 4px; + background: transparent; + color: var(--vscode-descriptionForeground); + font-family: var(--vscode-editor-font-family), monospace; + font-size: 11px; + font-variant-numeric: tabular-nums; + cursor: pointer; + white-space: nowrap; +} + +.session-diff-badge:hover { + background: var(--vscode-toolbar-hoverBackground); +} + +.session-diff-badge [data-component="icon"] { + opacity: 0.7; +} + +.session-diff-files { + color: var(--vscode-descriptionForeground); +} + +.session-diff-add { + color: #34d399; +} + +.session-diff-del { + color: #f87171; +} + /* ============================================ Message List ============================================ */ From 6867b49194071c2e0dfd279f5ccf9ca6e5e7558a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 25 Mar 2026 18:43:24 +0100 Subject: [PATCH 08/15] fix(vscode): move diff badge to the right end of the action row --- .../src/components/chat/ChatView.tsx | 28 +++++++++---------- .../webview-ui/src/styles/chat.css | 2 ++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index c50fd424c7a..0f68223384d 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -150,20 +150,6 @@ export const ChatView: Component = (props) => { > {language.t("command.session.new.task")} - - - + + + diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 60847a9383c..eaead9dbeb6 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -195,6 +195,7 @@ align-items: center; justify-content: center; gap: 4px; + margin-left: auto; padding: 4px 8px; border: none; border-radius: 4px; @@ -205,6 +206,7 @@ font-variant-numeric: tabular-nums; cursor: pointer; white-space: nowrap; + flex: 0 0 auto; } .session-diff-badge:hover { From 6c3cd38a6cf17b1205812e9a111136092e13a34a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 25 Mar 2026 18:50:07 +0100 Subject: [PATCH 09/15] fix(vscode): add tooltips to session action buttons --- .../src/components/chat/ChatView.tsx | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 0f68223384d..c9a4a9878fd 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -7,6 +7,7 @@ import { Component, Show, createEffect, createMemo, createSignal, on, onCleanup, import { Button } from "@kilocode/kilo-ui/button" import { Icon } from "@kilocode/kilo-ui/icon" import { Spinner } from "@kilocode/kilo-ui/spinner" +import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { TaskHeader } from "./TaskHeader" import { MessageList } from "./MessageList" import { PromptInput } from "./PromptInput" @@ -142,47 +143,53 @@ export const ChatView: Component = (props) => {
- - + + + + + + - + + +
From 7b088dd01e521fd0dcc8efb700e9ff3c52b8baa7 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 25 Mar 2026 18:58:57 +0100 Subject: [PATCH 10/15] fix(vscode): shorten tooltips, hide diff badge when no changes --- .../webview-ui/src/components/chat/ChatView.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index c9a4a9878fd..4d022d2a86a 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -154,7 +154,7 @@ export const ChatView: Component = (props) => { - + - - + + From 811238b3d1dcf7045d118809c093f71d0a13cd05 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 25 Mar 2026 19:15:12 +0100 Subject: [PATCH 11/15] fix(vscode): make session action buttons fill space dynamically Buttons use flex:1 via > * selector so they grow to fill available space. When the diff badge is hidden (no changes), remaining buttons expand to fill the row. Diff badge wrapper uses flex:0 + margin-left:auto to stay right-aligned at its natural width. --- .../webview-ui/src/components/chat/ChatView.tsx | 2 +- packages/kilo-vscode/webview-ui/src/styles/chat.css | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 4d022d2a86a..580d0352a76 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -176,7 +176,7 @@ export const ChatView: Component = (props) => { - +