fix(vscode): fix codebase indexing status update on windows

This commit is contained in:
Josh Holmer
2026-05-12 13:56:06 -04:00
parent 8ab58a66f3
commit c5903fdc09
8 changed files with 161 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Fix stale indexing status updates in the VS Code extension on Windows so Settings and the chat indicator refresh while indexing progresses. Directory matching for indexing SSE events now handles Windows path casing, and indexing status pushes are no longer dropped if they arrive before the initial config payload.
+3 -3
View File
@@ -39,6 +39,7 @@ import {
resolveContextDirectory,
resolveNewSessionDirectory,
resolveWorkspaceDirectory,
sameDirectory,
SessionStreamScheduler,
type SessionRefreshContext,
} from "./kilo-provider-utils"
@@ -3085,7 +3086,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
if (event.type === "server.instance.disposed") {
const props = event.properties as Record<string, unknown> | null
const dir = typeof props?.directory === "string" ? props.directory : undefined
if (dir && path.resolve(dir) !== path.resolve(this.getWorkspaceDirectory())) return
if (dir && !sameDirectory(dir, this.getWorkspaceDirectory())) return
void this.reloadAfterAuthChange()
return
}
@@ -3131,8 +3132,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
handleNetworkEvent(event.type as string, event.properties as any, this.client, (s) => this.getWorkspaceDirectory(s))
if (event.type === "indexing.status" && directory) {
const current = path.resolve(this.getWorkspaceDirectory(this.currentSession?.id))
if (path.resolve(directory) !== current) return
if (!sameDirectory(directory, this.getWorkspaceDirectory(this.currentSession?.id))) return
}
const msg = mapSSEEventToWebviewMessage(event, sessionID)
@@ -3,6 +3,7 @@ import { prettifyError } from "zod/v4"
import type { CloudSessionMessage, IndexingStatus } from "./services/cli-backend/types"
import type { PartBatch, PartUpdate } from "./kilo-provider/session-stream-scheduler"
import type { PartRemove } from "./shared/stream-messages"
import * as path from "path"
export { SessionStreamScheduler } from "./kilo-provider/session-stream-scheduler"
@@ -359,6 +360,17 @@ export function resolveNewSessionDirectory(input: {
})
}
export function sameDirectory(a: string, b: string): boolean {
if (!a || !b) return false
const left = path.resolve(a)
const right = path.resolve(b)
if (path.relative(left, right) === "") return true
if (process.platform !== "win32") return false
return path.relative(left.toLowerCase(), right.toLowerCase()) === ""
}
export type WebviewMessage =
| PartUpdate
| PartBatch
@@ -1,5 +1,9 @@
import { describe, expect, it } from "bun:test"
import { formatIndexingLabel, indexingTone } from "../../webview-ui/src/context/indexing-utils"
import {
applyIndexingStatusMessage,
formatIndexingLabel,
indexingTone,
} from "../../webview-ui/src/context/indexing-utils"
import { mapSSEEventToWebviewMessage } from "../../src/kilo-provider-utils"
import { configFeatures } from "../../src/features"
import type { EventIndexingStatus, IndexingStatus } from "@kilocode/sdk/v2/client"
@@ -120,3 +124,54 @@ describe("indexing feature detection", () => {
expect(configFeatures({}).indexing).toBe(false)
})
})
describe("indexing status message handling", () => {
it("applies indexing status regardless of feature-toggle race", () => {
let status = makeStatus()
let loading = true
const applied = applyIndexingStatusMessage(
{
type: "indexingStatusLoaded",
status: makeStatus({
state: "In Progress",
message: "Indexing",
processedFiles: 5,
totalFiles: 20,
percent: 25,
}),
},
(next) => {
status = next
},
(next) => {
loading = next
},
)
expect(applied).toBe(true)
expect(status.state).toBe("In Progress")
expect(status.percent).toBe(25)
expect(loading).toBe(false)
})
it("ignores unrelated extension messages", () => {
let status = makeStatus()
let loading = true
const applied = applyIndexingStatusMessage(
{
type: "connectionState",
state: "connected",
},
(next) => {
status = next
},
(next) => {
loading = next
},
)
expect(applied).toBe(false)
expect(status.state).toBe("Disabled")
expect(loading).toBe(true)
})
})
@@ -7,6 +7,8 @@ const { KiloProvider } = await import("../../src/KiloProvider")
type Internals = {
connectionState: "connecting" | "connected" | "disconnected" | "error"
currentSession: { id: string } | null
cachedIndexingStatusMessage: unknown
handleEvent: (event: unknown, directory?: string) => void
reloadAfterAuthChange: () => Promise<void>
handleUpdateConfig: (partial: Partial<Config>) => Promise<void>
fetchAndSendConfig: () => Promise<void>
@@ -140,4 +142,42 @@ describe("KiloProvider indexing refresh", () => {
globalThis.fetch = original
}
})
it("forwards indexing.status when directory only differs by Windows drive casing", () => {
const provider = new KiloProvider(
{} as never,
{
resolveEventSessionId: () => undefined,
} as never,
)
const internal = provider as unknown as Internals
provider.setSessionDirectory("ses_worktree", "C:/Repo/Work")
internal.currentSession = { id: "ses_worktree" }
const desc = Object.getOwnPropertyDescriptor(process, "platform")
Object.defineProperty(process, "platform", { value: "win32", configurable: true })
try {
internal.handleEvent(
{
type: "indexing.status",
properties: {
status: {
state: "Complete",
message: "Done",
processedFiles: 10,
totalFiles: 10,
percent: 100,
},
},
},
"c:/repo/work",
)
} finally {
if (desc) Object.defineProperty(process, "platform", desc)
}
const msg = internal.cachedIndexingStatusMessage as { type?: string; status?: { state?: string } } | undefined
expect(msg?.type).toBe("indexingStatusLoaded")
expect(msg?.status?.state).toBe("Complete")
})
})
@@ -0,0 +1,31 @@
import { afterEach, describe, expect, it } from "bun:test"
import { sameDirectory } from "../../src/kilo-provider-utils"
const platform = Object.getOwnPropertyDescriptor(process, "platform")
function setPlatform(value: string) {
Object.defineProperty(process, "platform", { value, configurable: true })
}
afterEach(() => {
if (platform) Object.defineProperty(process, "platform", platform)
})
describe("sameDirectory", () => {
it("matches identical paths", () => {
expect(sameDirectory("/repo/pkg", "/repo/pkg")).toBe(true)
})
it("matches trailing slash differences", () => {
expect(sameDirectory("/repo/pkg", "/repo/pkg/")).toBe(true)
})
it("matches Windows drive-letter case differences", () => {
setPlatform("win32")
expect(sameDirectory("C:/Repo/Work", "c:/repo/work")).toBe(true)
})
it("returns false for different directories", () => {
expect(sameDirectory("/repo/pkg-a", "/repo/pkg-b")).toBe(false)
})
})
@@ -1,4 +1,4 @@
import type { IndexingStatus } from "../types/messages"
import type { ExtensionMessage, IndexingStatus } from "../types/messages"
export function formatIndexingLabel(status: IndexingStatus): string {
if (status.state === "In Progress") {
@@ -24,3 +24,14 @@ export function indexingTone(status: IndexingStatus): "muted" | "warning" | "suc
if (status.state === "Standby") return "muted"
return "muted"
}
export function applyIndexingStatusMessage(
message: ExtensionMessage,
setStatus: (status: IndexingStatus) => void,
setLoading: (value: boolean) => void,
): boolean {
if (message.type !== "indexingStatusLoaded") return false
setStatus(message.status)
setLoading(false)
return true
}
@@ -2,7 +2,7 @@ import { createContext, useContext, createMemo, createSignal, onCleanup, createE
import type { ParentComponent, Accessor } from "solid-js"
import { useVSCode } from "./vscode"
import type { ExtensionMessage, IndexingStatus } from "../types/messages"
import { formatIndexingLabel, indexingTone } from "./indexing-utils"
import { applyIndexingStatusMessage, formatIndexingLabel, indexingTone } from "./indexing-utils"
import { useConfig } from "./config"
interface IndexingContextValue {
@@ -31,10 +31,7 @@ export const IndexingProvider: ParentComponent = (props) => {
const [loading, setLoading] = createSignal(true)
const unsubscribe = vscode.onMessage((message: ExtensionMessage) => {
if (!features().indexing) return
if (message.type !== "indexingStatusLoaded") return
setStatus(message.status)
setLoading(false)
applyIndexingStatusMessage(message, setStatus, setLoading)
})
onCleanup(unsubscribe)