mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-29 18:52:45 +08:00
feat(vscode): support sidebar message forks (#9244)
* feat(vscode): support sidebar message forks * fix(vscode): type sidebar fork message
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Support forking sidebar conversations from any user message.
|
||||
@@ -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<void> {
|
||||
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>,
|
||||
): 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<string, unknown>) => Promise<Record<string, unknown> | 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<void> {
|
||||
if (!this.client) {
|
||||
this.postMessage({
|
||||
|
||||
@@ -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<void> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div class="container">
|
||||
{/* legacy-migration start — state-driven overlay, independent of currentView */}
|
||||
<Show
|
||||
when={migrationNeeded()}
|
||||
fallback={
|
||||
<Switch fallback={<ChatView continueInWorktree promptBoxId="sidebar:fallback" />}>
|
||||
<Switch
|
||||
fallback={
|
||||
<ChatView
|
||||
continueInWorktree
|
||||
onForkMessage={session.status() === "idle" ? handleForkMessage : undefined}
|
||||
promptBoxId="sidebar:fallback"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Match when={currentView() === "newTask"}>
|
||||
<ChatView
|
||||
onSelectSession={handleSelectSession}
|
||||
onShowHistory={() => setCurrentView("history")}
|
||||
onForkMessage={session.status() === "idle" ? handleForkMessage : undefined}
|
||||
continueInWorktree
|
||||
promptBoxId="sidebar:new-task"
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user