Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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/green-lizards-mention.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Support attaching Git changes from prompt mentions in the VS Code extension.
23 changes: 11 additions & 12 deletions packages/kilo-vscode/src/KiloProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import { handleContinueInWorktree } from "./kilo-provider/continue-worktree"
import { parseMessageFiles, type MessageFile } from "./kilo-provider/message-files"
import { handleFileSearch } from "./kilo-provider/file-search"
import { getTerminalContents } from "./services/terminal/context"
import { disposeGitChangesTarget } from "./kilo-provider/git-changes-target"
import { interceptMessage } from "./kilo-provider/git-changes-request"
import { matchFollowup, recordFollowup, type Followup } from "./kilo-provider/followup-session"
import { clearCommandsCache, loadCommands } from "./kilo-provider/commands"
import { fetchMessagePage, MESSAGE_PAGE_LIMIT } from "./kilo-provider/message-page"
Expand Down Expand Up @@ -565,17 +567,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.autocompleteConfigDisposable?.dispose()
this.autocompleteConfigDisposable = watchAutocompleteConfig((msg) => this.postMessage(msg))
this.webviewMessageDisposable = webview.onDidReceiveMessage(async (message) => {
// Run interceptor if attached (e.g., AgentManagerProvider worktree logic)
if (this.onBeforeMessage) {
try {
const result = await this.onBeforeMessage(message)
if (result === null) return // consumed by interceptor
message = result
} catch (error) {
console.error("[Kilo New] KiloProvider: interceptor error:", error)
return
}
}
const intercepted = await interceptMessage(message, {
workspaceDir: (sid) => this.getWorkspaceDirectory(sid ?? this.currentSession?.id),
post: (m) => this.postMessage(m),
error: getErrorMessage,
before: this.onBeforeMessage,
})
if (intercepted === null) return
message = intercepted

await routeSuggestionWebviewMessage(this.questionCtx, message)
if (await ModelState.handleMessage(message.type, message, this.client, (msg) => this.postMessage(msg))) return
Expand Down Expand Up @@ -3322,6 +3321,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.sessionStatusMap.clear()
this.ignoreController?.dispose()
this.chatAutocomplete?.dispose()
this.marketplace?.dispose()
;(this.marketplace?.dispose(), disposeGitChangesTarget())
}
}
34 changes: 33 additions & 1 deletion packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import * as path from "path"
import type { KiloClient, Session } from "@kilocode/sdk/v2/client"
import type { KiloConnectionService } from "../services/cli-backend"
import { getErrorMessage } from "../kilo-provider-utils"
import { resolveLocalDiffTarget } from "../review-utils"
import { isAbsolutePath } from "../path-utils"
import { WorktreeManager, type CreateWorktreeResult } from "./WorktreeManager"
import { WorktreeStateManager } from "./WorktreeStateManager"
import { remoteRef, WorktreeStateManager } from "./WorktreeStateManager"
import { handleSection } from "./section-handler"
import { chooseBaseBranch, normalizeBaseBranch } from "./base-branch"
import { GitStatsPoller, type LocalStats, type WorktreePresenceResult, type WorktreeStats } from "./GitStatsPoller"
Expand Down Expand Up @@ -281,6 +282,7 @@ export class AgentManagerProvider implements Disposable {
if (msg.type === "requestFileSearch" && typeof msg.sessionID !== "string" && this.activeSessionId) {
return { ...msg, sessionID: this.activeSessionId }
}
msg = await this.contextMessage(msg)
const m = msg as unknown as AgentManagerInMessage

const worktree = await this.onWorktreeMessage(m)
Expand All @@ -302,6 +304,36 @@ export class AgentManagerProvider implements Disposable {
return msg
}

private async contextMessage(msg: Record<string, unknown>): Promise<Record<string, unknown>> {
if (msg.type !== "requestGitChangesContext") return msg
const ctx = typeof msg.agentManagerContext === "string" ? msg.agentManagerContext : undefined
const target = ctx ? await this.contextTarget(ctx) : undefined
const sid = typeof msg.sessionID === "string" ? msg.sessionID : this.activeSessionId
const next = sid && typeof msg.sessionID !== "string" ? { ...msg, sessionID: sid } : msg
if (target) return { ...next, ...target }
if (!sid) return next

const state = this.getStateManager()
const session = state?.getSession(sid)
const worktree = session?.worktreeId ? state?.getWorktree(session.worktreeId) : undefined
if (!worktree) return next
return { ...next, contextDirectory: worktree.path, gitChangesBase: remoteRef(worktree) }
}

private async contextTarget(ctx: string): Promise<Record<string, unknown> | undefined> {
if (ctx === "local") {
const root = this.getRoot()
if (!root) return undefined
const target = await resolveLocalDiffTarget(this.gitOps, (...args) => this.log(...args), root)
if (!target) return { contextDirectory: root }
return { contextDirectory: target.directory, gitChangesBase: target.baseBranch }
}

const worktree = this.getStateManager()?.getWorktree(ctx)
if (!worktree) return undefined
return { contextDirectory: worktree.path, gitChangesBase: remoteRef(worktree) }
}

private async onWorktreeMessage(m: AgentManagerInMessage): Promise<Record<string, unknown> | null | undefined> {
if (m.type === "agentManager.createWorktree") return this.onCreateWorktree(m.baseBranch, m.branchName)
if (m.type === "agentManager.deleteWorktree") return this.onDeleteWorktree(m.worktreeId)
Expand Down
28 changes: 28 additions & 0 deletions packages/kilo-vscode/src/kilo-provider/git-changes-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { getGitChangesContext } from "../services/git/context"

type Input = {
requestId: string
dir: string
base?: string
post: (message: unknown) => void
error: (error: unknown) => string
}

export async function captureGitChangesContext(input: Input): Promise<void> {
try {
const output = await getGitChangesContext(input.dir, input.base)
input.post({
type: "gitChangesContextResult",
requestId: input.requestId,
content: output.content,
truncated: output.truncated,
})
} catch (error) {
console.error("[Kilo New] Failed to capture git changes context:", error)
input.post({
type: "gitChangesContextError",
requestId: input.requestId,
error: input.error(error) || "Failed to capture git changes",
})
}
}
32 changes: 32 additions & 0 deletions packages/kilo-vscode/src/kilo-provider/git-changes-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { captureGitChangesContext } from "./git-changes-context"
import { resolveGitChangesTarget } from "./git-changes-target"

type Interceptor = (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>

type Context = {
workspaceDir: (sessionID: string | undefined) => string
post: (message: unknown) => void
error: (error: unknown) => string
before?: Interceptor | null
}

export async function interceptMessage(
msg: Record<string, unknown>,
ctx: Context,
): Promise<Record<string, unknown> | null> {
const next = ctx.before
? await ctx.before(msg).catch((e) => (console.error("[Kilo New] interceptor error:", e), null))
: msg
if (next === null || next.type !== "requestGitChangesContext") return next
const sid = typeof next.sessionID === "string" ? next.sessionID : undefined
const dir = ctx.workspaceDir(sid)
const resolved = await resolveGitChangesTarget(next, dir)
await captureGitChangesContext({
requestId: typeof resolved.requestId === "string" ? resolved.requestId : "",
dir: typeof resolved.contextDirectory === "string" ? resolved.contextDirectory : dir,
base: typeof resolved.gitChangesBase === "string" ? resolved.gitChangesBase : undefined,
post: ctx.post,
error: ctx.error,
}).catch((e) => console.error("[Kilo New] git changes error:", e))
return null
}
24 changes: 24 additions & 0 deletions packages/kilo-vscode/src/kilo-provider/git-changes-target.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { GitOps } from "../agent-manager/GitOps"
import { resolveLocalDiffTarget } from "../review-utils"

let shared: GitOps | undefined

function ops(): GitOps {
if (shared && !shared.disposed) return shared
shared = new GitOps({ log: () => undefined })
return shared
}

export function disposeGitChangesTarget(): void {
shared?.dispose()
shared = undefined
}

export async function resolveGitChangesTarget(message: Record<string, unknown>, dir: string) {
if (message.type !== "requestGitChangesContext") return message
if (typeof message.contextDirectory === "string" || typeof message.gitChangesBase === "string") return message

const target = await resolveLocalDiffTarget(ops(), () => undefined, dir)
if (!target) return { ...message, contextDirectory: dir }
return { ...message, contextDirectory: target.directory, gitChangesBase: target.baseBranch }
}
Loading
Loading