-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat: send editor context (visible files, open tabs, shell, timezone) to CLI backend #6151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a217754
2a0ac40
ada6297
226583c
08cb48a
cdc3baf
dd32c6d
a2fcda3
07e7761
70d6368
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import * as path from "path" | ||
| import * as vscode from "vscode" | ||
| import { z } from "zod" | ||
| import { | ||
|
|
@@ -7,6 +8,8 @@ import { | |
| type KiloConnectionService, | ||
| type KilocodeNotification, | ||
| } from "./services/cli-backend" | ||
| import type { EditorContext } from "./services/cli-backend/types" | ||
| import { FileIgnoreController } from "./services/autocomplete/shims/FileIgnoreController" | ||
| import { handleChatCompletionRequest } from "./services/autocomplete/chat-autocomplete/handleChatCompletionRequest" | ||
| import { handleChatCompletionAccepted } from "./services/autocomplete/chat-autocomplete/handleChatCompletionAccepted" | ||
| import { buildWebviewHtml } from "./utils" | ||
|
|
@@ -48,6 +51,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper | |
| private unsubscribeNotificationDismiss: (() => void) | null = null | ||
| private webviewMessageDisposable: vscode.Disposable | null = null | ||
|
|
||
| /** Lazily initialized ignore controller for .kilocodeignore filtering */ | ||
| private ignoreController: FileIgnoreController | null = null | ||
| private ignoreControllerDir: string | null = null | ||
|
|
||
| /** Optional interceptor called before the standard message handler. | ||
| * Return null to consume the message, or return a (possibly transformed) message. */ | ||
| private onBeforeMessage: ((msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>) | null = null | ||
|
|
@@ -1055,16 +1062,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper | |
| // Build parts array with file context and user text | ||
| const parts: Array<{ type: "text"; text: string } | { type: "file"; mime: string; url: string }> = [] | ||
|
|
||
| // Inject active editor file as context | ||
| const editor = vscode.window.activeTextEditor | ||
| if (editor && editor.document.uri.scheme === "file") { | ||
| const url = editor.document.uri.toString() | ||
| const already = files?.some((f) => f.url === url) | ||
| if (!already) { | ||
| parts.push({ type: "file", mime: "text/plain", url }) | ||
| } | ||
| } | ||
|
|
||
| // Add any explicitly attached files from the webview | ||
| if (files) { | ||
| for (const f of files) { | ||
|
|
@@ -1074,11 +1071,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper | |
|
|
||
| parts.push({ type: "text", text }) | ||
|
|
||
| const editorContext = await this.gatherEditorContext() | ||
|
|
||
| await this.httpClient.sendMessage(targetSessionID, parts, workspaceDir, { | ||
| providerID, | ||
| modelID, | ||
| agent, | ||
| variant, | ||
| editorContext, | ||
| }) | ||
| } catch (error) { | ||
| console.error("[Kilo New] KiloProvider: Failed to send message:", error) | ||
|
|
@@ -1506,6 +1506,86 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper | |
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Gather VS Code editor context to send alongside messages to the CLI backend. | ||
| */ | ||
| /** | ||
| * Get or create a FileIgnoreController for the current workspace directory. | ||
| * Reinitializes if the workspace directory has changed. | ||
| */ | ||
| private async getIgnoreController(workspaceDir: string): Promise<FileIgnoreController> { | ||
| if (this.ignoreController && this.ignoreControllerDir === workspaceDir) { | ||
| return this.ignoreController | ||
| } | ||
| const controller = new FileIgnoreController(workspaceDir) | ||
| await controller.initialize() | ||
| this.ignoreController = controller | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: The old Consider disposing the old controller before replacing it: this.ignoreController?.dispose()before assigning the new one. |
||
| this.ignoreControllerDir = workspaceDir | ||
| return controller | ||
| } | ||
|
|
||
| private async gatherEditorContext(): Promise<EditorContext> { | ||
| const workspaceDir = this.getWorkspaceDirectory() | ||
| const controller = await this.getIgnoreController(workspaceDir) | ||
|
|
||
| const toRelative = (fsPath: string): string | undefined => { | ||
| if (!workspaceDir) { | ||
| return undefined | ||
| } | ||
| const relative = path.relative(workspaceDir, fsPath) | ||
| if (relative.startsWith("..")) { | ||
| return undefined | ||
| } | ||
| return relative | ||
| } | ||
|
|
||
| // Visible files (capped to avoid bloating context, filtered through .kilocodeignore) | ||
| const visibleFiles = vscode.window.visibleTextEditors | ||
| .map((e) => e.document.uri) | ||
| .filter((uri) => uri.scheme === "file") | ||
| .map((uri) => toRelative(uri.fsPath)) | ||
| .filter((p): p is string => p !== undefined && controller.validateAccess(path.resolve(workspaceDir, p))) | ||
| .slice(0, 200) | ||
|
|
||
| // Open tabs — use instanceof TabInputText to exclude notebooks, diffs, custom editors | ||
| const openTabSet = new Set<string>() | ||
| for (const group of vscode.window.tabGroups.all) { | ||
| for (const tab of group.tabs) { | ||
| if (tab.input instanceof vscode.TabInputText) { | ||
| const uri = tab.input.uri | ||
| if (uri.scheme === "file") { | ||
| const rel = toRelative(uri.fsPath) | ||
| if (rel && controller.validateAccess(uri.fsPath)) { | ||
| openTabSet.add(rel) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const openTabs = [...openTabSet].slice(0, 20) | ||
|
|
||
| // Active file (also filtered through .kilocodeignore) | ||
| const activeEditor = vscode.window.activeTextEditor | ||
| const activeRel = | ||
| activeEditor?.document.uri.scheme === "file" ? toRelative(activeEditor.document.uri.fsPath) : undefined | ||
| const activeFile = | ||
| activeRel && controller.validateAccess(activeEditor!.document.uri.fsPath) ? activeRel : undefined | ||
|
|
||
| // Shell | ||
| const shell = vscode.env.shell || undefined | ||
|
|
||
| // Timezone | ||
| const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || undefined | ||
|
|
||
| return { | ||
| ...(visibleFiles.length > 0 ? { visibleFiles } : {}), | ||
| ...(openTabs.length > 0 ? { openTabs } : {}), | ||
| ...(activeFile ? { activeFile } : {}), | ||
| ...(shell ? { shell } : {}), | ||
| ...(timezone ? { timezone } : {}), | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Get the workspace directory for a session. | ||
| * Checks session directory overrides first (e.g., worktree paths), then falls back to workspace root. | ||
|
|
@@ -1544,5 +1624,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper | |
| this.webviewMessageDisposable?.dispose() | ||
| this.trackedSessionIds.clear() | ||
| this.sessionDirectories.clear() | ||
| this.ignoreController?.dispose() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| export interface EditorContext { | ||
| visibleFiles?: string[] | ||
| openTabs?: string[] | ||
| activeFile?: string | ||
| shell?: string | ||
| timezone?: string | ||
| } | ||
|
|
||
| function formatTime(timezone?: string): string[] { | ||
| const now = new Date() | ||
| const lines = [` Current time: ${now.toISOString()}`] | ||
| if (timezone) { | ||
| const offset = -now.getTimezoneOffset() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Timezone offset mismatch — When the CLI backend runs in a different timezone than the VS Code client (e.g., remote dev server in UTC, user in To compute the correct offset from the timezone string, you could use function getUtcOffset(timezone: string): string {
const now = new Date()
const formatter = new Intl.DateTimeFormat("en-US", {
timeZone: timezone,
timeZoneName: "shortOffset",
})
const parts = formatter.formatToParts(now)
const tzPart = parts.find((p) => p.type === "timeZoneName")
return tzPart?.value ?? ""
}Or simply omit the computed offset and just display the timezone name, since the name is authoritative. |
||
| const sign = offset >= 0 ? "+" : "-" | ||
| const hours = Math.floor(Math.abs(offset) / 60) | ||
| const mins = Math.abs(offset) % 60 | ||
| lines.push(` User timezone: ${timezone}, UTC${sign}${hours}:${mins.toString().padStart(2, "0")}`) | ||
| } | ||
| return lines | ||
| } | ||
|
|
||
| /** | ||
| * Build additional <env> lines from VS Code editor context. | ||
| * Returns an array of pre-formatted ` key: value` strings. | ||
| */ | ||
| export function editorContextEnvLines(ctx?: EditorContext): string[] { | ||
| const lines = formatTime(ctx?.timezone) | ||
| if (ctx?.shell) { | ||
| lines.push(` Default shell: ${ctx.shell}`) | ||
| } | ||
| if (ctx?.activeFile) { | ||
| lines.push(` Active file: ${ctx.activeFile}`) | ||
| } | ||
| if (ctx?.visibleFiles?.length) { | ||
| lines.push(` Visible files: ${ctx.visibleFiles.join(", ")}`) | ||
| } | ||
| if (ctx?.openTabs?.length) { | ||
| lines.push(` Open tabs: ${ctx.openTabs.join(", ")}`) | ||
| } | ||
| return lines | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SUGGESTION: Orphaned JSDoc comment — this doc block ("Gather VS Code editor context...") was likely intended for
gatherEditorContext()at line 1527, but it's now attached to nothing sincegetIgnoreController()has its own JSDoc immediately below. Consider removing this block or moving it abovegatherEditorContext().