diff --git a/.changeset/sidebar-message-forks.md b/.changeset/sidebar-message-forks.md new file mode 100644 index 00000000000..3c07a889db7 --- /dev/null +++ b/.changeset/sidebar-message-forks.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Support forking sidebar conversations from any user message. diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 7ad1817f744..c32f096fff1 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -55,6 +55,7 @@ import { fetchMessagePage, MESSAGE_PAGE_LIMIT } from "./kilo-provider/message-pa import { childID } from "./kilo-provider/task-session" import { handleNetworkEvent, clearNetworkWaits } from "./kilo-provider/network" import { abortSession, parseQueued } from "./kilo-provider/abort" +import { handleForkSession } from "./kilo-provider/fork-session" import { retryable, backoff, MAX_RETRIES } from "./util/retry" import { hasGit } from "./kilo-provider/git-status" // legacy-migration start @@ -303,11 +304,16 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return slimParts(parts) } - /** - * Synchronize current extension-side state to the webview. - * This is primarily used after a webview refresh where early postMessage calls - * may have been dropped before the webview registered its message listeners. - */ + private get forkCtx() { + return { + connection: this.connectionService, + post: (msg: { type: "error"; message: string }) => this.postMessage(msg), + register: (session: Session) => this.registerSession(session), + forked: (session: Session) => this.postMessage({ type: "sessionForked", sessionID: session.id }), + status: (sessionID: string) => this.sessionStatusMap.get(sessionID), + } + } + private async syncWebviewState(reason: string): Promise { const serverInfo = this.connectionService.getServerInfo() console.log("[Kilo New] KiloProvider: 🔄 syncWebviewState()", { @@ -529,21 +535,12 @@ 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. - * - * @param options.onBeforeMessage - Optional interceptor called before the standard handler. - * Return null to consume the message (stop propagation), or return the message - * (possibly transformed) to continue with standard handling. - */ public attachToWebview( webview: vscode.Webview, options?: { onBeforeMessage?: (msg: Record) => Promise | null> }, @@ -555,10 +552,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.initializeConnection() } - /** - * Set up the shared message handler for both sidebar and tab webviews. - * Handles ALL message types so tabs have full functionality. - */ private setupWebviewMessageHandler(webview: vscode.Webview): void { this.webviewMessageDisposable?.dispose() this.webviewMessageDisposable = webview.onDidReceiveMessage(async (message) => { @@ -709,6 +702,12 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper post: (msg) => this.postMessage(msg), }) break + case "forkSession": + handleForkSession(this.forkCtx, message.sessionId, message.messageId).catch((e) => + console.error("[Kilo New] handleForkSession failed:", e), + ) + break + case "retryConnection": console.log("[Kilo New] KiloProvider: 🔄 Retrying connection...") this.initializeConnection().catch((e) => @@ -1255,9 +1254,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return sessionToWebview(session) } - /** - * Handle creating a new session. - */ private async handleCreateSession(): Promise { if (!this.client) { this.postMessage({ diff --git a/packages/kilo-vscode/src/kilo-provider/fork-session.ts b/packages/kilo-vscode/src/kilo-provider/fork-session.ts new file mode 100644 index 00000000000..09b66f73439 --- /dev/null +++ b/packages/kilo-vscode/src/kilo-provider/fork-session.ts @@ -0,0 +1,38 @@ +import type { Session, SessionStatus } from "@kilocode/sdk/v2/client" +import type { KiloConnectionService } from "../services/cli-backend" +import { forkSession } from "../agent-manager/fork-session" + +export interface ForkContext { + connection: KiloConnectionService + post: (message: { type: "error"; message: string }) => void + register: (session: Session) => void + forked: (session: Session) => void + status: (sessionID: string) => SessionStatus["type"] | undefined +} + +export async function handleForkSession(ctx: ForkContext, sessionId: string, messageId?: string): Promise { + const status = ctx.status(sessionId) + if (status && status !== "idle") { + ctx.post({ type: "error", message: "Wait for the session to finish before forking it." }) + return + } + + await forkSession( + { + getClient: () => ctx.connection.getClient(), + state: undefined, + postError: (message) => ctx.post({ type: "error", message }), + registerWorktreeSession: () => {}, + pushState: () => {}, + notifyForked: (session) => { + ctx.register(session) + ctx.forked(session) + }, + registerSession: () => {}, + log: (...args) => console.log("[Kilo New] KiloProvider:", ...args), + }, + sessionId, + undefined, + messageId, + ) +} diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index ba72dfc8d8c..433d382ceb7 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -182,6 +182,12 @@ const AppContent: Component = () => { if (agent) session.selectAgent(agent.name) } + const handleForked = (message: { type?: string; sessionID?: string }) => { + if (message.type !== "sessionForked" || !message.sessionID) return + session.selectSession(message.sessionID) + setCurrentView("newTask") + } + onMount(() => { const handler = (event: MessageEvent) => { const message = event.data @@ -199,6 +205,7 @@ const AppContent: Component = () => { session.selectCloudSession(message.sessionId) setCurrentView("newTask") } + handleForked(message) if (message?.type === "viewSubAgentSession" && message.sessionID) { console.log("[Kilo New] App: 🔍 viewSubAgentSession:", message.sessionID) session.setCurrentSessionID(message.sessionID) @@ -219,17 +226,30 @@ const AppContent: Component = () => { setCurrentView("newTask") } + const handleForkMessage = (sessionId: string, messageId: string) => { + vscode.postMessage({ type: "forkSession", sessionId, messageId }) + } + return (
{/* legacy-migration start — state-driven overlay, independent of currentView */} }> + + } + > setCurrentView("history")} + onForkMessage={session.status() === "idle" ? handleForkMessage : undefined} continueInWorktree promptBoxId="sidebar:new-task" /> diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 69fda551c73..b7c1e8a1f4c 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -573,6 +573,11 @@ export interface SessionCreatedMessage { draftID?: string } +export interface SessionForkedMessage { + type: "sessionForked" + sessionID: string +} + export interface SessionUpdatedMessage { type: "sessionUpdated" session: SessionInfo @@ -1527,6 +1532,7 @@ export type ExtensionMessage = | PermissionErrorMessage | TodoUpdatedMessage | SessionCreatedMessage + | SessionForkedMessage | SessionUpdatedMessage | SessionDeletedMessage | MessageRemovedMessage @@ -2057,6 +2063,12 @@ export interface ForkSessionRequest { messageId?: string } +export interface SidebarForkSessionRequest { + type: "forkSession" + sessionId: string + messageId?: string +} + // Close (remove) a session from its worktree export interface CloseSessionRequest { type: "agentManager.closeSession" @@ -2556,6 +2568,7 @@ export type WebviewMessage = | OpenLocallyRequest | AddSessionToWorktreeRequest | ForkSessionRequest + | SidebarForkSessionRequest | CloseSessionRequest | PersistSessionRequest | ForgetSessionRequest