Quellcode durchsuchen

Add Autocomplete to chat text box and track visible code (#3999)

* Add chat autocomplete functionality and related types

- Implemented chat text area autocomplete feature with new message handling for chat completion requests.
- Introduced new types for chat completion requests and results in `types.ts`.
- Updated `ExtensionMessage` and `WebviewMessage` to support chat completion messages.
- Styled ghost text for autocomplete suggestions in CSS.
- Refactored `ChatTextArea` component to utilize a custom hook for handling ghost text input and display.
- Enhanced HTML escaping utility to be exported for broader use.

* Add chat autocomplete settings and integration

- Added `enableChatAutocomplete` option to ghost service settings schema.
- Updated `ChatTextArea` to utilize the new autocomplete feature based on the settings.
- Enhanced `useChatGhostText` hook to conditionally request completions based on the new setting.
- Implemented UI components for toggling chat autocomplete in the settings view.
- Updated localization files to include translations for the new chat autocomplete feature.

* feat(chat): add word-by-word ghost text acceptance in chat

- Enhance Tab key to accept full ghost text completion
- Add ArrowRight key to accept next word only, mimicking VS Code behavior
- Refactor key handling logic for better cursor and selection checks
- Remove unused inputValue/setInputValue dependencies from hook
- Add helper functions for word extraction and text insertion

Add test directory for useChatGhostText hook validation

* feat(ghost): add filtering for sensitive files in visible code tracking

Integrate RooIgnoreController into VisibleCodeTracker to respect .kilocodeignore patterns and security concerns, preventing sensitive files from being included in chat autocompletion context. Add corresponding tests for filtering behavior.

* changeset

* refactor(test): consolidate chat autocomplete tests and update visible code tracker test description

- Merge multiple test cases into fewer blocks for better readability
- Update test description to reflect broader security filtering scope
- Remove redundant helper functions in favor of direct assertions

* fix changeset

* fix(chat): improve FIM availability check to validate credentials

- Update isFimAvailable() to check hasValidCredentials() instead of loaded
- Add test case for isFimAvailable when model is not loaded

* Enable chat autocomplete by default

* Address code review suggestions

- Rename test files to use .spec.ts convention
- Remove debug console.log statements from ChatTextAreaAutocomplete
Chris Hasson vor 2 Monaten
Ursprung
Commit
7f349d0474
41 geänderte Dateien mit 1574 neuen und 27 gelöschten Zeilen
  1. 5 0
      .changeset/tidy-donuts-judge.md
  2. 1 0
      packages/types/src/kilocode/kilocode.ts
  3. 12 0
      src/core/webview/webviewMessageHandler.ts
  4. 154 0
      src/services/ghost/chat-autocomplete/ChatTextAreaAutocomplete.ts
  5. 55 0
      src/services/ghost/chat-autocomplete/__tests__/ChatTextAreaAutocomplete.spec.ts
  6. 37 0
      src/services/ghost/chat-autocomplete/handleChatCompletionRequest.ts
  7. 154 0
      src/services/ghost/context/VisibleCodeTracker.ts
  8. 235 0
      src/services/ghost/context/__tests__/VisibleCodeTracker.spec.ts
  9. 86 0
      src/services/ghost/types.ts
  10. 1 0
      src/shared/ExtensionMessage.ts
  11. 1 0
      src/shared/WebviewMessage.ts
  12. 7 0
      src/shared/id.ts
  13. 36 3
      webview-ui/src/components/chat/ChatTextArea.tsx
  14. 456 0
      webview-ui/src/components/chat/hooks/__tests__/useChatGhostText.spec.tsx
  15. 174 0
      webview-ui/src/components/chat/hooks/useChatGhostText.ts
  16. 28 2
      webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx
  17. 13 0
      webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx
  18. 5 1
      webview-ui/src/i18n/locales/ar/kilocode.json
  19. 5 1
      webview-ui/src/i18n/locales/ca/kilocode.json
  20. 5 1
      webview-ui/src/i18n/locales/cs/kilocode.json
  21. 5 1
      webview-ui/src/i18n/locales/de/kilocode.json
  22. 4 0
      webview-ui/src/i18n/locales/en/kilocode.json
  23. 5 1
      webview-ui/src/i18n/locales/es/kilocode.json
  24. 5 1
      webview-ui/src/i18n/locales/fr/kilocode.json
  25. 5 1
      webview-ui/src/i18n/locales/hi/kilocode.json
  26. 5 1
      webview-ui/src/i18n/locales/id/kilocode.json
  27. 5 1
      webview-ui/src/i18n/locales/it/kilocode.json
  28. 5 1
      webview-ui/src/i18n/locales/ja/kilocode.json
  29. 5 1
      webview-ui/src/i18n/locales/ko/kilocode.json
  30. 5 1
      webview-ui/src/i18n/locales/nl/kilocode.json
  31. 5 1
      webview-ui/src/i18n/locales/pl/kilocode.json
  32. 5 1
      webview-ui/src/i18n/locales/pt-BR/kilocode.json
  33. 5 1
      webview-ui/src/i18n/locales/ru/kilocode.json
  34. 5 1
      webview-ui/src/i18n/locales/th/kilocode.json
  35. 5 1
      webview-ui/src/i18n/locales/tr/kilocode.json
  36. 5 1
      webview-ui/src/i18n/locales/uk/kilocode.json
  37. 5 1
      webview-ui/src/i18n/locales/vi/kilocode.json
  38. 5 1
      webview-ui/src/i18n/locales/zh-CN/kilocode.json
  39. 5 1
      webview-ui/src/i18n/locales/zh-TW/kilocode.json
  40. 9 0
      webview-ui/src/index.css
  41. 1 1
      webview-ui/src/utils/highlight.ts

+ 5 - 0
.changeset/tidy-donuts-judge.md

@@ -0,0 +1,5 @@
+---
+"kilo-code": minor
+---
+
+Add Autocomplete support to the chat text box. It can be enabled/disabled using a new toggle in the autocomplete settings menu

+ 1 - 0
packages/types/src/kilocode/kilocode.ts

@@ -11,6 +11,7 @@ export const ghostServiceSettingsSchema = z
 		enableAutoTrigger: z.boolean().optional(),
 		enableQuickInlineTaskKeybinding: z.boolean().optional(),
 		enableSmartInlineTaskKeybinding: z.boolean().optional(),
+		enableChatAutocomplete: z.boolean().optional(),
 		provider: z.string().optional(),
 		model: z.string().optional(),
 	})

+ 12 - 0
src/core/webview/webviewMessageHandler.ts

@@ -3698,7 +3698,19 @@ export const webviewMessageHandler = async (
 				await provider.postMessageToWebview({ type: "keybindingsResponse", keybindings: {} })
 			}
 			break
+		} // kilocode_change start: Chat text area FIM autocomplete
+		case "requestChatCompletion": {
+			const { handleChatCompletionRequest } = await import(
+				"../../services/ghost/chat-autocomplete/handleChatCompletionRequest"
+			)
+			await handleChatCompletionRequest(
+				message as WebviewMessage & { type: "requestChatCompletion" },
+				provider,
+				getCurrentCwd,
+			)
+			break
 		}
+		// kilocode_change end: Chat text area FIM autocomplete
 		case "openCommandFile": {
 			try {
 				if (message.text) {

+ 154 - 0
src/services/ghost/chat-autocomplete/ChatTextAreaAutocomplete.ts

@@ -0,0 +1,154 @@
+import * as vscode from "vscode"
+import { GhostModel } from "../GhostModel"
+import { ProviderSettingsManager } from "../../../core/config/ProviderSettingsManager"
+import { VisibleCodeContext } from "../types"
+
+/**
+ * Service for providing FIM-based autocomplete suggestions in ChatTextArea
+ */
+export class ChatTextAreaAutocomplete {
+	private model: GhostModel
+	private providerSettingsManager: ProviderSettingsManager
+
+	constructor(providerSettingsManager: ProviderSettingsManager) {
+		this.model = new GhostModel()
+		this.providerSettingsManager = providerSettingsManager
+	}
+
+	async initialize(): Promise<boolean> {
+		return this.model.reload(this.providerSettingsManager)
+	}
+
+	/**
+	 * Check if we can successfully make a FIM request.
+	 * Validates that model is loaded, has valid API handler, and supports FIM.
+	 */
+	isFimAvailable(): boolean {
+		return this.model.hasValidCredentials() && this.model.supportsFim()
+	}
+
+	async getCompletion(userText: string, visibleCodeContext?: VisibleCodeContext): Promise<{ suggestion: string }> {
+		if (!this.model.loaded) {
+			const loaded = await this.initialize()
+			if (!loaded) {
+				return { suggestion: "" }
+			}
+		}
+
+		// Now check if we can make FIM requests
+		if (!this.isFimAvailable()) {
+			return { suggestion: "" }
+		}
+
+		const prefix = await this.buildPrefix(userText, visibleCodeContext)
+		const suffix = ""
+
+		let response = ""
+		await this.model.generateFimResponse(prefix, suffix, (chunk) => {
+			response += chunk
+		})
+
+		const cleanedSuggestion = this.cleanSuggestion(response, userText)
+
+		return { suggestion: cleanedSuggestion }
+	}
+
+	/**
+	 * Build the prefix for FIM completion with visible code context and additional sources
+	 */
+	private async buildPrefix(userText: string, visibleCodeContext?: VisibleCodeContext): Promise<string> {
+		const contextParts: string[] = []
+
+		// Add visible code context (replaces cursor-based prefix/suffix)
+		if (visibleCodeContext && visibleCodeContext.editors.length > 0) {
+			contextParts.push("// Code visible in editor:")
+
+			for (const editor of visibleCodeContext.editors) {
+				const fileName = editor.filePath.split("/").pop() || editor.filePath
+				contextParts.push(`\n// File: ${fileName} (${editor.languageId})`)
+
+				for (const range of editor.visibleRanges) {
+					contextParts.push(range.content)
+				}
+			}
+		}
+
+		const clipboardContent = await this.getClipboardContext()
+		if (clipboardContent) {
+			contextParts.push("\n// Clipboard content:")
+			contextParts.push(clipboardContent)
+		}
+
+		contextParts.push("\n// User's message:")
+		contextParts.push(userText)
+
+		return contextParts.join("\n")
+	}
+
+	/**
+	 * Get clipboard content for context
+	 */
+	private async getClipboardContext(): Promise<string | null> {
+		try {
+			const text = await vscode.env.clipboard.readText()
+			// Only include if it's reasonable size and looks like code
+			if (text && text.length > 5 && text.length < 500) {
+				return text
+			}
+		} catch {
+			// Silently ignore clipboard errors
+		}
+		return null
+	}
+
+	/**
+	 * Clean the suggestion by removing any leading repetition of user text
+	 * and filtering out unwanted patterns like comments
+	 */
+	private cleanSuggestion(suggestion: string, userText: string): string {
+		let cleaned = suggestion.trim()
+
+		if (cleaned.startsWith(userText)) {
+			cleaned = cleaned.substring(userText.length)
+		}
+
+		const firstNewline = cleaned.indexOf("\n")
+		if (firstNewline !== -1) {
+			cleaned = cleaned.substring(0, firstNewline)
+		}
+
+		cleaned = cleaned.trimStart()
+
+		// Filter out suggestions that start with comment patterns
+		// This happens because the context uses // prefixes for labels
+		if (this.isUnwantedSuggestion(cleaned)) {
+			return ""
+		}
+
+		return cleaned
+	}
+
+	/**
+	 * Check if suggestion should be filtered out
+	 */
+	public isUnwantedSuggestion(suggestion: string): boolean {
+		// Filter comment-starting suggestions
+		if (suggestion.startsWith("//") || suggestion.startsWith("/*") || suggestion.startsWith("*")) {
+			return true
+		}
+
+		// Filter suggestions that look like code rather than natural language
+		// This includes preprocessor directives (#include) and markdown headers
+		// Chat is for natural language, not formatted documents
+		if (suggestion.startsWith("#")) {
+			return true
+		}
+
+		// Filter suggestions that are just punctuation or whitespace
+		if (suggestion.length < 2 || /^[\s\p{P}]+$/u.test(suggestion)) {
+			return true
+		}
+
+		return false
+	}
+}

+ 55 - 0
src/services/ghost/chat-autocomplete/__tests__/ChatTextAreaAutocomplete.spec.ts

@@ -0,0 +1,55 @@
+import { ChatTextAreaAutocomplete } from "../ChatTextAreaAutocomplete"
+import { ProviderSettingsManager } from "../../../../core/config/ProviderSettingsManager"
+
+describe("ChatTextAreaAutocomplete", () => {
+	let autocomplete: ChatTextAreaAutocomplete
+	let mockProviderSettingsManager: ProviderSettingsManager
+
+	beforeEach(() => {
+		mockProviderSettingsManager = {} as ProviderSettingsManager
+		autocomplete = new ChatTextAreaAutocomplete(mockProviderSettingsManager)
+	})
+
+	describe("isFimAvailable", () => {
+		it("should return false when model is not loaded", () => {
+			const result = autocomplete.isFimAvailable()
+			expect(result).toBe(false)
+		})
+	})
+
+	describe("isUnwantedSuggestion", () => {
+		it("should filter code patterns (comments, preprocessor, short/empty)", () => {
+			const filter = autocomplete.isUnwantedSuggestion.bind(autocomplete)
+
+			// Comments
+			expect(filter("// comment")).toBe(true)
+			expect(filter("/* comment")).toBe(true)
+			expect(filter("*")).toBe(true)
+
+			// Code patterns
+			expect(filter("#include")).toBe(true)
+			expect(filter("# Header")).toBe(true)
+
+			// Meaningless content
+			expect(filter("")).toBe(true)
+			expect(filter("a")).toBe(true)
+			expect(filter("...")).toBe(true)
+		})
+
+		it("should accept natural language suggestions", () => {
+			const filter = autocomplete.isUnwantedSuggestion.bind(autocomplete)
+
+			expect(filter("Hello world")).toBe(false)
+			expect(filter("Can you help me")).toBe(false)
+			expect(filter("test123")).toBe(false)
+			expect(filter("What's up?")).toBe(false)
+		})
+
+		it("should accept symbols in middle of text", () => {
+			const filter = autocomplete.isUnwantedSuggestion.bind(autocomplete)
+
+			expect(filter("Text with # in middle")).toBe(false)
+			expect(filter("Hello // but not a comment")).toBe(false)
+		})
+	})
+})

+ 37 - 0
src/services/ghost/chat-autocomplete/handleChatCompletionRequest.ts

@@ -0,0 +1,37 @@
+import { ClineProvider } from "../../../core/webview/ClineProvider"
+import { WebviewMessage } from "../../../shared/WebviewMessage"
+import { VisibleCodeTracker } from "../context/VisibleCodeTracker"
+import { ChatTextAreaAutocomplete } from "./ChatTextAreaAutocomplete"
+
+/**
+ * Handles a chat completion request from the webview.
+ * Captures visible code context and generates a FIM-based autocomplete suggestion.
+ */
+export async function handleChatCompletionRequest(
+	message: WebviewMessage & { type: "requestChatCompletion" },
+	provider: ClineProvider,
+	getCurrentCwd: () => string,
+): Promise<void> {
+	try {
+		const userText = message.text || ""
+		const requestId = message.requestId || ""
+
+		// Pass RooIgnoreController to respect .kilocodeignore patterns
+		const currentTask = provider.getCurrentTask()
+		const tracker = new VisibleCodeTracker(getCurrentCwd(), currentTask?.rooIgnoreController ?? null)
+
+		const visibleContext = await tracker.captureVisibleCode()
+
+		const autocomplete = new ChatTextAreaAutocomplete(provider.providerSettingsManager)
+		const { suggestion } = await autocomplete.getCompletion(userText, visibleContext)
+
+		await provider.postMessageToWebview({ type: "chatCompletionResult", text: suggestion, requestId })
+	} catch (error) {
+		provider.log(`Error getting chat completion: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`)
+		await provider.postMessageToWebview({
+			type: "chatCompletionResult",
+			text: "",
+			requestId: message.requestId || "",
+		})
+	}
+}

+ 154 - 0
src/services/ghost/context/VisibleCodeTracker.ts

@@ -0,0 +1,154 @@
+/**
+ * VisibleCodeTracker - Captures the actual visible code in VS Code editors
+ *
+ * This service captures what code is currently visible on the user's screen,
+ * not just what files are open. It uses the VS Code API to get:
+ * - All visible text editors (not just tabs)
+ * - The actual visible line ranges in each editor's viewport
+ * - Cursor positions and selections
+ */
+
+import * as vscode from "vscode"
+
+import { toRelativePath } from "../../../utils/path"
+import { isSecurityConcern } from "../../continuedev/core/indexing/ignore"
+import type { RooIgnoreController } from "../../../core/ignore/RooIgnoreController"
+
+import { VisibleCodeContext, VisibleEditorInfo, VisibleRange, DiffInfo } from "../types"
+
+// Git-related URI schemes that should be captured for diff support
+const GIT_SCHEMES = ["git", "gitfs", "file", "vscode-remote"]
+
+export class VisibleCodeTracker {
+	private lastContext: VisibleCodeContext | null = null
+
+	constructor(
+		private workspacePath: string,
+		private rooIgnoreController: RooIgnoreController | null = null,
+	) {}
+
+	/**
+	 * Captures the currently visible code across all visible editors.
+	 * Excludes files matching security patterns or .kilocodeignore rules.
+	 *
+	 * @returns VisibleCodeContext containing information about all visible editors
+	 * and their visible code ranges
+	 */
+	public async captureVisibleCode(): Promise<VisibleCodeContext> {
+		const editors = vscode.window.visibleTextEditors
+		const activeUri = vscode.window.activeTextEditor?.document.uri.toString()
+
+		const editorInfos: VisibleEditorInfo[] = []
+
+		for (const editor of editors) {
+			const document = editor.document
+			const scheme = document.uri.scheme
+
+			// Skip non-code documents (output panels, extension host output, etc.)
+			if (!GIT_SCHEMES.includes(scheme)) {
+				continue
+			}
+
+			const filePath = document.uri.fsPath
+			const relativePath = toRelativePath(filePath, this.workspacePath)
+
+			if (isSecurityConcern(filePath)) {
+				console.log(`[VisibleCodeTracker] Filtered (security): ${relativePath}`)
+				continue
+			}
+			if (this.rooIgnoreController && !this.rooIgnoreController.validateAccess(relativePath)) {
+				console.log(`[VisibleCodeTracker] Filtered (.kilocodeignore): ${relativePath}`)
+				continue
+			}
+
+			const visibleRanges: VisibleRange[] = []
+
+			for (const range of editor.visibleRanges) {
+				const content = document.getText(range)
+				visibleRanges.push({
+					startLine: range.start.line,
+					endLine: range.end.line,
+					content,
+				})
+			}
+
+			const isActive = document.uri.toString() === activeUri
+
+			// Extract diff information for git-backed documents
+			const diffInfo = this.extractDiffInfo(document.uri)
+
+			editorInfos.push({
+				filePath,
+				relativePath,
+				languageId: document.languageId,
+				isActive,
+				visibleRanges,
+				cursorPosition: editor.selection
+					? {
+							line: editor.selection.active.line,
+							character: editor.selection.active.character,
+						}
+					: null,
+				selections: editor.selections.map((sel) => ({
+					start: { line: sel.start.line, character: sel.start.character },
+					end: { line: sel.end.line, character: sel.end.character },
+				})),
+				diffInfo,
+			})
+		}
+
+		this.lastContext = {
+			timestamp: Date.now(),
+			editors: editorInfos,
+		}
+
+		return this.lastContext
+	}
+
+	/**
+	 * Returns the last captured context, or null if never captured.
+	 */
+	public getLastContext(): VisibleCodeContext | null {
+		return this.lastContext
+	}
+
+	/**
+	 * Extract diff information from a URI.
+	 * Git URIs typically look like: git:/path/to/file.ts?ref=HEAD~1
+	 */
+	private extractDiffInfo(uri: vscode.Uri): DiffInfo | undefined {
+		const scheme = uri.scheme
+
+		// Only extract diff info for git-related schemes
+		if (scheme === "git" || scheme === "gitfs") {
+			// Parse query parameters for git reference
+			const query = uri.query
+			let gitRef: string | undefined
+
+			if (query) {
+				// Common patterns: ref=HEAD, ref=abc123
+				const refMatch = query.match(/ref=([^&]+)/)
+				if (refMatch) {
+					gitRef = refMatch[1]
+				}
+			}
+
+			return {
+				scheme,
+				side: "old", // Git scheme documents are typically the "old" side
+				gitRef,
+				originalPath: uri.fsPath,
+			}
+		}
+
+		// File scheme in a diff view is the "new" side
+		// We can't always tell if it's in a diff, so we mark it as new when there's a paired git doc
+		if (scheme === "file") {
+			// This will be marked as diffInfo only if we detect it's paired with a git document
+			// For now, we don't set diffInfo for regular file scheme documents
+			return undefined
+		}
+
+		return undefined
+	}
+}

+ 235 - 0
src/services/ghost/context/__tests__/VisibleCodeTracker.spec.ts

@@ -0,0 +1,235 @@
+import { describe, it, expect, vi, beforeEach } from "vitest"
+import * as vscode from "vscode"
+import { VisibleCodeTracker } from "../VisibleCodeTracker"
+
+// Mock vscode module
+vi.mock("vscode", () => ({
+	window: {
+		visibleTextEditors: [],
+		activeTextEditor: null,
+	},
+}))
+
+vi.mock("../../../../services/continuedev/core/indexing/ignore", () => ({
+	isSecurityConcern: vi.fn((filePath: string) => {
+		return filePath.includes(".env") || filePath.includes("credentials")
+	}),
+}))
+
+describe("VisibleCodeTracker", () => {
+	const mockWorkspacePath = "/workspace"
+
+	beforeEach(() => {
+		// Reset mocks before each test
+		vi.clearAllMocks()
+	})
+
+	describe("captureVisibleCode", () => {
+		it("should return empty context when no editors are visible", async () => {
+			// Mock empty editor list
+			;(vscode.window.visibleTextEditors as any) = []
+			;(vscode.window.activeTextEditor as any) = null
+
+			const tracker = new VisibleCodeTracker(mockWorkspacePath)
+			const context = await tracker.captureVisibleCode()
+
+			expect(context).toEqual({
+				timestamp: expect.any(Number),
+				editors: [],
+			})
+			expect(context.timestamp).toBeGreaterThan(0)
+		})
+
+		it("should capture visible editors with file scheme", async () => {
+			const mockDocument = {
+				uri: {
+					fsPath: "/workspace/test.ts",
+					scheme: "file",
+					toString: () => "file:///workspace/test.ts",
+				},
+				languageId: "typescript",
+				getText: vi.fn((range: any) => {
+					if (range.start.line === 0 && range.end.line === 2) {
+						return "line 0\nline 1\nline 2"
+					}
+					return ""
+				}),
+			}
+
+			const mockEditor = {
+				document: mockDocument,
+				visibleRanges: [
+					{
+						start: { line: 0, character: 0 },
+						end: { line: 2, character: 0 },
+					},
+				],
+				selection: {
+					active: { line: 1, character: 5 },
+				},
+				selections: [
+					{
+						start: { line: 1, character: 0 },
+						end: { line: 1, character: 10 },
+					},
+				],
+			}
+
+			;(vscode.window.visibleTextEditors as any) = [mockEditor]
+			;(vscode.window.activeTextEditor as any) = mockEditor
+
+			const tracker = new VisibleCodeTracker(mockWorkspacePath)
+			const context = await tracker.captureVisibleCode()
+
+			expect(context.editors).toHaveLength(1)
+			expect(context.editors[0]).toMatchObject({
+				filePath: "/workspace/test.ts",
+				relativePath: "test.ts",
+				languageId: "typescript",
+				isActive: true,
+				visibleRanges: [
+					{
+						startLine: 0,
+						endLine: 2,
+						content: "line 0\nline 1\nline 2",
+					},
+				],
+				cursorPosition: {
+					line: 1,
+					character: 5,
+				},
+			})
+		})
+
+		it("should extract diff info for git scheme URIs", async () => {
+			const mockDocument = {
+				uri: {
+					fsPath: "/workspace/test.ts",
+					scheme: "git",
+					query: "ref=HEAD~1",
+					toString: () => "git:///workspace/test.ts?ref=HEAD~1",
+				},
+				languageId: "typescript",
+				getText: vi.fn(() => "old content"),
+			}
+
+			const mockEditor = {
+				document: mockDocument,
+				visibleRanges: [
+					{
+						start: { line: 0, character: 0 },
+						end: { line: 0, character: 0 },
+					},
+				],
+				selection: null,
+				selections: [],
+			}
+
+			;(vscode.window.visibleTextEditors as any) = [mockEditor]
+			;(vscode.window.activeTextEditor as any) = null
+
+			const tracker = new VisibleCodeTracker(mockWorkspacePath)
+			const context = await tracker.captureVisibleCode()
+
+			expect(context.editors[0].diffInfo).toEqual({
+				scheme: "git",
+				side: "old",
+				gitRef: "HEAD~1",
+				originalPath: "/workspace/test.ts",
+			})
+		})
+
+		it("should skip non-code documents", async () => {
+			const mockOutputDocument = {
+				uri: {
+					fsPath: "/workspace/output",
+					scheme: "output",
+					toString: () => "output:///workspace/output",
+				},
+				languageId: "plaintext",
+				getText: vi.fn(() => ""),
+			}
+
+			const mockEditor = {
+				document: mockOutputDocument,
+				visibleRanges: [],
+				selection: null,
+				selections: [],
+			}
+
+			;(vscode.window.visibleTextEditors as any) = [mockEditor]
+			;(vscode.window.activeTextEditor as any) = null
+
+			const tracker = new VisibleCodeTracker(mockWorkspacePath)
+			const context = await tracker.captureVisibleCode()
+
+			// Output scheme should be filtered out
+			expect(context.editors).toHaveLength(0)
+		})
+	})
+
+	describe("security filtering", () => {
+		it("should filter security-sensitive files", async () => {
+			const mockEnvDocument = {
+				uri: {
+					fsPath: "/workspace/.env",
+					scheme: "file",
+					toString: () => "file:///workspace/.env",
+				},
+				languageId: "plaintext",
+				getText: vi.fn(() => "SECRET_KEY=12345"),
+			}
+
+			const mockEditor = {
+				document: mockEnvDocument,
+				visibleRanges: [{ start: { line: 0, character: 0 }, end: { line: 0, character: 20 } }],
+				selection: null,
+				selections: [],
+			}
+
+			;(vscode.window.visibleTextEditors as any) = [mockEditor]
+			;(vscode.window.activeTextEditor as any) = null
+
+			const tracker = new VisibleCodeTracker(mockWorkspacePath)
+			const context = await tracker.captureVisibleCode()
+
+			expect(context.editors).toHaveLength(0)
+		})
+	})
+
+	describe(".kilocodeignore integration", () => {
+		it("should filter files matching .kilocodeignore patterns", async () => {
+			const mockIgnoredDocument = {
+				uri: {
+					fsPath: "/workspace/sensitive/data.json",
+					scheme: "file",
+					toString: () => "file:///workspace/sensitive/data.json",
+				},
+				languageId: "json",
+				getText: vi.fn(() => '{"data": "sensitive"}'),
+			}
+
+			const mockEditor = {
+				document: mockIgnoredDocument,
+				visibleRanges: [{ start: { line: 0, character: 0 }, end: { line: 0, character: 25 } }],
+				selection: null,
+				selections: [],
+			}
+
+			;(vscode.window.visibleTextEditors as any) = [mockEditor]
+			;(vscode.window.activeTextEditor as any) = null
+
+			const mockController = {
+				validateAccess: vi.fn((path: string) => {
+					return !path.includes("sensitive/")
+				}),
+			} as any
+
+			const tracker = new VisibleCodeTracker(mockWorkspacePath, mockController)
+			const context = await tracker.captureVisibleCode()
+
+			expect(context.editors).toHaveLength(0)
+			expect(mockController.validateAccess).toHaveBeenCalledWith("sensitive/data.json")
+		})
+	})
+})

+ 86 - 0
src/services/ghost/types.ts

@@ -97,6 +97,10 @@ export interface PromptResult {
 	completionId: string
 }
 
+// ============================================================================
+// FIM/Hole Filler Completion Types
+// ============================================================================
+
 export interface FillInAtCursorSuggestion {
 	text: string
 	prefix: string
@@ -164,6 +168,88 @@ export interface PendingRequest {
 	promise: Promise<void>
 }
 
+// ============================================================================
+// Visible Code Context Types
+// ============================================================================
+
+/**
+ * Visible range in an editor viewport
+ */
+export interface VisibleRange {
+	startLine: number
+	endLine: number
+	content: string
+}
+
+/**
+ * Diff metadata for git-backed editors
+ */
+export interface DiffInfo {
+	/** The URI scheme (e.g., "git", "gitfs") */
+	scheme: string
+	/** Whether this is the "old" (left) or "new" (right) side of a diff */
+	side: "old" | "new"
+	/** Git reference if available (e.g., "HEAD", "HEAD~1", commit hash) */
+	gitRef?: string
+	/** The actual file path being compared */
+	originalPath: string
+}
+
+/**
+ * Information about a visible editor
+ */
+export interface VisibleEditorInfo {
+	/** Absolute file path */
+	filePath: string
+	/** Path relative to workspace */
+	relativePath: string
+	/** Language identifier (e.g., "typescript", "python") */
+	languageId: string
+	/** Whether this is the active editor */
+	isActive: boolean
+	/** The visible line ranges in the editor viewport */
+	visibleRanges: VisibleRange[]
+	/** Current cursor position, or null if no cursor */
+	cursorPosition: Position | null
+	/** All selections in the editor */
+	selections: Range[]
+	/** Diff information if this editor is part of a diff view */
+	diffInfo?: DiffInfo
+}
+
+/**
+ * Context of all visible code in editors
+ */
+export interface VisibleCodeContext {
+	/** Timestamp when the context was captured */
+	timestamp: number
+	/** Information about all visible editors */
+	editors: VisibleEditorInfo[]
+}
+
+// ============================================================================
+// Chat Text Area Autocomplete Types
+// ============================================================================
+
+/**
+ * Request for chat text area completion
+ */
+export interface ChatCompletionRequest {
+	text: string
+}
+
+/**
+ * Result of chat text area completion (distinct from code editor ChatCompletionResult)
+ */
+export interface ChatTextCompletionResult {
+	suggestion: string
+	requestId: string
+}
+
+// ============================================================================
+// Conversion Utilities
+// ============================================================================
+
 export function extractPrefixSuffix(
 	document: vscode.TextDocument,
 	position: vscode.Position,

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -189,6 +189,7 @@ export interface ExtensionMessage {
 		| "deviceAuthComplete" // kilocode_change: Device auth successful
 		| "deviceAuthFailed" // kilocode_change: Device auth failed
 		| "deviceAuthCancelled" // kilocode_change: Device auth cancelled
+		| "chatCompletionResult" // kilocode_change: FIM completion result for chat text area
 	text?: string
 	// kilocode_change start
 	completionRequestId?: string // Correlation ID from request

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -273,6 +273,7 @@ export interface WebviewMessage {
 		| "startDeviceAuth" // kilocode_change: Start device auth flow
 		| "cancelDeviceAuth" // kilocode_change: Cancel device auth flow
 		| "deviceAuthCompleteWithProfile" // kilocode_change: Device auth complete with specific profile
+		| "requestChatCompletion" // kilocode_change: Request FIM completion for chat text area
 	text?: string
 	completionRequestId?: string // kilocode_change
 	shareId?: string // kilocode_change - for sessionFork

+ 7 - 0
src/shared/id.ts

@@ -0,0 +1,7 @@
+/**
+ * Generate a unique request ID for messages.
+ * Uses a short random string suitable for request correlation.
+ */
+export function generateRequestId(): string {
+	return Math.random().toString(36).substring(2, 9)
+}

+ 36 - 3
webview-ui/src/components/chat/ChatTextArea.tsx

@@ -20,6 +20,8 @@ import {
 	SearchResult,
 } from "@src/utils/context-mentions"
 import { convertToMentionPath } from "@/utils/path-mentions"
+import { escapeHtml } from "@/utils/highlight" // kilocode_change - FIM autocomplete
+import { useChatGhostText } from "./hooks/useChatGhostText" // kilocode_change: FIM autocomplete
 import { DropdownOptionType, Button, StandardTooltip } from "@/components/ui" // kilocode_change
 
 import Thumbnails from "../common/Thumbnails"
@@ -151,6 +153,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 			globalWorkflows, // kilocode_change
 			taskHistoryVersion, // kilocode_change
 			clineMessages,
+			ghostServiceSettings, // kilocode_change
 		} = useExtensionState()
 
 		// kilocode_change start - autocomplete profile type system
@@ -303,6 +306,16 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 		const [isEnhancingPrompt, setIsEnhancingPrompt] = useState(false)
 		const [isFocused, setIsFocused] = useState(false)
 		const [imageWarning, setImageWarning] = useState<string | null>(null) // kilocode_change
+		// kilocode_change start: FIM autocomplete ghost text
+		const {
+			ghostText,
+			handleKeyDown: handleGhostTextKeyDown,
+			handleInputChange: handleGhostTextInputChange,
+		} = useChatGhostText({
+			textAreaRef,
+			enableChatAutocomplete: ghostServiceSettings?.enableChatAutocomplete ?? false,
+		})
+		// kilocode_change end: FIM autocomplete ghost text
 
 		// Use custom hook for prompt history navigation
 		const { handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange } = usePromptHistory({
@@ -530,6 +543,12 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 
 		const handleKeyDown = useCallback(
 			(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
+				// kilocode_change start: FIM autocomplete - Tab to accept ghost text
+				if (handleGhostTextKeyDown(event)) {
+					return // Event was handled by ghost text hook, stop here
+				}
+				// kilocode_change end: FIM autocomplete
+
 				// kilocode_change start: pull slash commands from Cline
 				if (showSlashCommandsMenu) {
 					if (event.key === "Escape") {
@@ -720,6 +739,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 				handleSlashCommandsSelect,
 				selectedSlashCommandsIndex,
 				slashCommandsQuery,
+				handleGhostTextKeyDown, // kilocode_change: FIM autocomplete
 				// kilocode_change end
 				onSend,
 				showContextMenu,
@@ -765,7 +785,9 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 				// Reset history navigation when user types
 				resetOnInputChange()
 
-				const newCursorPosition = target.selectionStart // kilocode_change
+				handleGhostTextInputChange(e) // kilocode_change - FIM autocomplete
+
+				const newCursorPosition = target.selectionStart // Use target for consistency
 				setCursorPosition(newCursorPosition)
 
 				// kilocode_change start: pull slash commands from Cline
@@ -846,7 +868,14 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 					setFileSearchResults([]) // Clear file search results.
 				}
 			},
-			[setInputValue, setSearchRequestId, setFileSearchResults, setSearchLoading, resetOnInputChange],
+			[
+				setInputValue,
+				setSearchRequestId,
+				setFileSearchResults,
+				setSearchLoading,
+				resetOnInputChange,
+				handleGhostTextInputChange, // kilocode_change: FIM autocomplete
+			],
 		)
 
 		useEffect(() => {
@@ -999,11 +1028,15 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 				}
 			}
 			// kilocode_change end
+			if (ghostText) {
+				processedText += `<span class="chat-ghost-text">${escapeHtml(ghostText)}</span>`
+			}
+			// kilocode_change end: FIM autocomplete ghost text display
 
 			highlightLayerRef.current.innerHTML = processedText
 			highlightLayerRef.current.scrollTop = textAreaRef.current.scrollTop
 			highlightLayerRef.current.scrollLeft = textAreaRef.current.scrollLeft
-		}, [customModes])
+		}, [customModes, ghostText])
 
 		useLayoutEffect(() => {
 			updateHighlights()

+ 456 - 0
webview-ui/src/components/chat/hooks/__tests__/useChatGhostText.spec.tsx

@@ -0,0 +1,456 @@
+import { renderHook, act, waitFor } from "@testing-library/react"
+import { vi } from "vitest"
+import { useChatGhostText } from "../useChatGhostText"
+
+// Mock vscode
+vi.mock("@/utils/vscode", () => ({
+	vscode: {
+		postMessage: vi.fn(),
+	},
+}))
+
+describe("useChatGhostText", () => {
+	let mockTextArea: HTMLTextAreaElement
+	let textAreaRef: React.RefObject<HTMLTextAreaElement>
+
+	beforeEach(() => {
+		mockTextArea = document.createElement("textarea")
+		mockTextArea.value = "Hello world"
+		document.body.appendChild(mockTextArea)
+		textAreaRef = { current: mockTextArea }
+		document.execCommand = vi.fn(() => true)
+	})
+
+	afterEach(() => {
+		document.body.removeChild(mockTextArea)
+		vi.clearAllMocks()
+	})
+
+	describe("Tab key acceptance", () => {
+		it("should accept full ghost text on Tab key", () => {
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Simulate receiving ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " completion text",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			// Wait for ghost text to be set
+			waitFor(() => {
+				expect(result.current.ghostText).toBe(" completion text")
+			})
+
+			// Simulate Tab key press
+			const tabEvent = {
+				key: "Tab",
+				shiftKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			act(() => {
+				result.current.handleKeyDown(tabEvent)
+			})
+
+			expect(tabEvent.preventDefault).toHaveBeenCalled()
+			expect(document.execCommand).toHaveBeenCalledWith("insertText", false, " completion text")
+			expect(result.current.ghostText).toBe("")
+		})
+	})
+
+	describe("Right Arrow key - word-by-word acceptance", () => {
+		it("should accept next word when cursor is at end", () => {
+			mockTextArea.value = "Hello world"
+			mockTextArea.selectionStart = 11 // At end
+			mockTextArea.selectionEnd = 11
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text manually for test
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " this is more text",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			// Simulate Right Arrow key press
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: false,
+				ctrlKey: false,
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			act(() => {
+				result.current.handleKeyDown(arrowEvent)
+			})
+
+			expect(arrowEvent.preventDefault).toHaveBeenCalled()
+			expect(document.execCommand).toHaveBeenCalledWith("insertText", false, " this ")
+			expect(result.current.ghostText).toBe("is more text")
+		})
+
+		it("should handle multiple word acceptances", () => {
+			mockTextArea.value = "Start"
+			mockTextArea.selectionStart = 5
+			mockTextArea.selectionEnd = 5
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " word1 word2 word3",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: false,
+				ctrlKey: false,
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			// First word
+			act(() => {
+				result.current.handleKeyDown(arrowEvent)
+			})
+			expect(result.current.ghostText).toBe("word2 word3")
+
+			// Second word
+			act(() => {
+				result.current.handleKeyDown(arrowEvent)
+			})
+			expect(result.current.ghostText).toBe("word3")
+
+			// Third word
+			act(() => {
+				result.current.handleKeyDown(arrowEvent)
+			})
+			expect(result.current.ghostText).toBe("")
+		})
+
+		it("should NOT accept word when cursor is not at end", () => {
+			mockTextArea.value = "Hello world"
+			mockTextArea.selectionStart = 5 // In middle
+			mockTextArea.selectionEnd = 5
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " more text",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: false,
+				ctrlKey: false,
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			const handled = result.current.handleKeyDown(arrowEvent)
+
+			expect(handled).toBe(false)
+			expect(arrowEvent.preventDefault).not.toHaveBeenCalled()
+			expect(result.current.ghostText).toBe(" more text") // Ghost text unchanged
+		})
+
+		it("should NOT accept word with Shift modifier", () => {
+			mockTextArea.value = "Test"
+			mockTextArea.selectionStart = 4
+			mockTextArea.selectionEnd = 4
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " text",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: true, // Shift is pressed
+				ctrlKey: false,
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			const handled = result.current.handleKeyDown(arrowEvent)
+
+			expect(handled).toBe(false)
+			expect(arrowEvent.preventDefault).not.toHaveBeenCalled()
+		})
+
+		it("should NOT accept word with Ctrl modifier", () => {
+			mockTextArea.value = "Test"
+			mockTextArea.selectionStart = 4
+			mockTextArea.selectionEnd = 4
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " text",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: false,
+				ctrlKey: true, // Ctrl is pressed
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			const handled = result.current.handleKeyDown(arrowEvent)
+
+			expect(handled).toBe(false)
+			expect(arrowEvent.preventDefault).not.toHaveBeenCalled()
+		})
+	})
+
+	describe("Escape key", () => {
+		it("should clear ghost text on Escape", () => {
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " world",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			expect(result.current.ghostText).toBe(" world")
+
+			// Simulate Escape key
+			const escapeEvent = {
+				key: "Escape",
+			} as React.KeyboardEvent<HTMLTextAreaElement>
+
+			act(() => {
+				result.current.handleKeyDown(escapeEvent)
+			})
+
+			expect(result.current.ghostText).toBe("")
+		})
+	})
+
+	describe("Edge cases", () => {
+		it("should handle ghost text with only whitespace", () => {
+			mockTextArea.value = "Test"
+			mockTextArea.selectionStart = 4
+			mockTextArea.selectionEnd = 4
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text with only whitespace
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: "   ",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: false,
+				ctrlKey: false,
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			act(() => {
+				result.current.handleKeyDown(arrowEvent)
+			})
+
+			expect(document.execCommand).toHaveBeenCalledWith("insertText", false, "   ")
+			expect(result.current.ghostText).toBe("")
+		})
+
+		it("should handle single word ghost text", () => {
+			mockTextArea.value = "Test"
+			mockTextArea.selectionStart = 4
+			mockTextArea.selectionEnd = 4
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set single word ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: "word",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: false,
+				ctrlKey: false,
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			act(() => {
+				result.current.handleKeyDown(arrowEvent)
+			})
+
+			expect(document.execCommand).toHaveBeenCalledWith("insertText", false, "word")
+			expect(result.current.ghostText).toBe("")
+		})
+
+		it("should handle empty ghost text gracefully", () => {
+			mockTextArea.value = "Test"
+			mockTextArea.selectionStart = 4
+			mockTextArea.selectionEnd = 4
+
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			const arrowEvent = {
+				key: "ArrowRight",
+				shiftKey: false,
+				ctrlKey: false,
+				metaKey: false,
+				preventDefault: vi.fn(),
+			} as unknown as React.KeyboardEvent<HTMLTextAreaElement>
+
+			const handled = result.current.handleKeyDown(arrowEvent)
+
+			expect(handled).toBe(false)
+			expect(arrowEvent.preventDefault).not.toHaveBeenCalled()
+		})
+	})
+
+	describe("clearGhostText", () => {
+		it("should clear ghost text when called", () => {
+			const { result } = renderHook(() =>
+				useChatGhostText({
+					textAreaRef,
+					enableChatAutocomplete: true,
+				}),
+			)
+
+			// Set ghost text
+			act(() => {
+				const messageEvent = new MessageEvent("message", {
+					data: {
+						type: "chatCompletionResult",
+						text: " completion",
+						requestId: "",
+					},
+				})
+				window.dispatchEvent(messageEvent)
+			})
+
+			expect(result.current.ghostText).toBe(" completion")
+
+			act(() => {
+				result.current.clearGhostText()
+			})
+
+			expect(result.current.ghostText).toBe("")
+		})
+	})
+})

+ 174 - 0
webview-ui/src/components/chat/hooks/useChatGhostText.ts

@@ -0,0 +1,174 @@
+// kilocode_change - new file
+import { useCallback, useEffect, useRef, useState } from "react"
+import { ExtensionMessage } from "@roo/ExtensionMessage"
+import { vscode } from "@/utils/vscode"
+import { generateRequestId } from "@roo/id"
+
+interface UseChatGhostTextOptions {
+	textAreaRef: React.RefObject<HTMLTextAreaElement>
+	enableChatAutocomplete?: boolean
+}
+
+interface UseChatGhostTextReturn {
+	ghostText: string
+	handleKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => boolean // Returns true if event was handled
+	handleInputChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
+	clearGhostText: () => void
+}
+
+/**
+ * Hook for managing FIM autocomplete ghost text in the chat text area.
+ * Handles completion requests, ghost text display, and Tab/Escape/ArrowRight interactions.
+ */
+export function useChatGhostText({
+	textAreaRef,
+	enableChatAutocomplete = true,
+}: UseChatGhostTextOptions): UseChatGhostTextReturn {
+	const [ghostText, setGhostText] = useState<string>("")
+	const completionDebounceRef = useRef<NodeJS.Timeout | null>(null)
+	const completionRequestIdRef = useRef<string>("")
+	const skipNextCompletionRef = useRef<boolean>(false) // Skip completion after accepting suggestion
+
+	// Handle chat completion result messages
+	useEffect(() => {
+		const messageHandler = (event: MessageEvent<ExtensionMessage>) => {
+			const message = event.data
+			if (message.type === "chatCompletionResult") {
+				// Only update if this is the response to our latest request
+				if (message.requestId === completionRequestIdRef.current) {
+					setGhostText(message.text || "")
+				}
+			}
+		}
+
+		window.addEventListener("message", messageHandler)
+		return () => window.removeEventListener("message", messageHandler)
+	}, [])
+
+	const clearGhostText = useCallback(() => {
+		setGhostText("")
+	}, [])
+
+	const handleKeyDown = useCallback(
+		(event: React.KeyboardEvent<HTMLTextAreaElement>): boolean => {
+			const textArea = textAreaRef.current
+			if (!textArea) {
+				return false
+			}
+
+			const hasSelection = textArea.selectionStart !== textArea.selectionEnd
+			const isCursorAtEnd = textArea.selectionStart === textArea.value.length
+			const canAcceptCompletion = ghostText && !hasSelection && isCursorAtEnd
+
+			// Tab: Accept full ghost text
+			if (event.key === "Tab" && !event.shiftKey && canAcceptCompletion) {
+				event.preventDefault()
+				skipNextCompletionRef.current = true
+				insertTextAtCursor(textArea, ghostText)
+				setGhostText("")
+				return true
+			}
+
+			// ArrowRight: Accept next word only
+			if (
+				event.key === "ArrowRight" &&
+				!event.shiftKey &&
+				!event.ctrlKey &&
+				!event.metaKey &&
+				canAcceptCompletion
+			) {
+				event.preventDefault()
+				skipNextCompletionRef.current = true
+				const { word, remainder } = extractNextWord(ghostText)
+				insertTextAtCursor(textArea, word)
+				setGhostText(remainder)
+				return true
+			}
+
+			// Escape: Clear ghost text
+			if (event.key === "Escape" && ghostText) {
+				setGhostText("")
+			}
+			return false
+		},
+		[ghostText, textAreaRef],
+	)
+
+	const handleInputChange = useCallback(
+		(e: React.ChangeEvent<HTMLTextAreaElement>) => {
+			const newValue = e.target.value
+
+			// Clear any existing ghost text when typing
+			setGhostText("")
+
+			// Clear any pending completion request
+			if (completionDebounceRef.current) {
+				clearTimeout(completionDebounceRef.current)
+			}
+
+			// Skip completion request if we just accepted a suggestion (Tab) or undid
+			if (skipNextCompletionRef.current) {
+				skipNextCompletionRef.current = false
+				// Don't request a new completion - wait for user to type more
+			} else if (
+				enableChatAutocomplete &&
+				newValue.length >= 5 &&
+				!newValue.startsWith("/") &&
+				!newValue.includes("@")
+			) {
+				// Request new completion after debounce (only if feature is enabled)
+				completionDebounceRef.current = setTimeout(() => {
+					const requestId = generateRequestId()
+					completionRequestIdRef.current = requestId
+					vscode.postMessage({
+						type: "requestChatCompletion",
+						text: newValue,
+						requestId,
+					})
+				}, 300) // 300ms debounce
+			}
+		},
+		[enableChatAutocomplete],
+	)
+
+	useEffect(() => {
+		return () => {
+			if (completionDebounceRef.current) {
+				clearTimeout(completionDebounceRef.current)
+			}
+		}
+	}, [])
+
+	return {
+		ghostText,
+		handleKeyDown,
+		handleInputChange,
+		clearGhostText,
+	}
+}
+
+/**
+ * Extracts the first word from ghost text, including surrounding whitespace.
+ * Mimics VS Code's word acceptance behavior: accepts leading space + word + trailing space as a unit.
+ * Returns the word and the remaining text.
+ */
+function extractNextWord(text: string): { word: string; remainder: string } {
+	if (!text) {
+		return { word: "", remainder: "" }
+	}
+
+	// Match: optional leading whitespace + non-whitespace characters + optional trailing whitespace
+	// This captures " word " or "word " or " word" as complete units
+	const match = text.match(/^(\s*\S+\s*)/)
+	if (match) {
+		return { word: match[1], remainder: text.slice(match[1].length) }
+	}
+
+	// If text is only whitespace, return all of it
+	return { word: text, remainder: "" }
+}
+
+function insertTextAtCursor(textArea: HTMLTextAreaElement, text: string): void {
+	textArea.setSelectionRange(textArea.value.length, textArea.value.length)
+	document?.execCommand("insertText", false, text)
+}

+ 28 - 2
webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx

@@ -28,8 +28,14 @@ export const GhostServiceSettingsView = ({
 }: GhostServiceSettingsViewProps) => {
 	const { t } = useAppTranslation()
 	const { kiloCodeWrapperProperties } = useExtensionState()
-	const { enableAutoTrigger, enableQuickInlineTaskKeybinding, enableSmartInlineTaskKeybinding, provider, model } =
-		ghostServiceSettings || {}
+	const {
+		enableAutoTrigger,
+		enableQuickInlineTaskKeybinding,
+		enableSmartInlineTaskKeybinding,
+		enableChatAutocomplete,
+		provider,
+		model,
+	} = ghostServiceSettings || {}
 	const keybindings = useKeybindings(["kilo-code.addToContextAndFocus", "kilo-code.ghost.generateSuggestions"])
 
 	const onEnableAutoTriggerChange = useCallback(
@@ -53,6 +59,13 @@ export const GhostServiceSettingsView = ({
 		[onGhostServiceSettingsChange],
 	)
 
+	const onEnableChatAutocompleteChange = useCallback(
+		(e: any) => {
+			onGhostServiceSettingsChange("enableChatAutocomplete", e.target.checked)
+		},
+		[onGhostServiceSettingsChange],
+	)
+
 	const openGlobalKeybindings = (filter?: string) => {
 		vscode.postMessage({ type: "openGlobalKeybindings", text: filter })
 	}
@@ -142,6 +155,19 @@ export const GhostServiceSettingsView = ({
 						</>
 					)}
 
+					<div className="flex flex-col gap-1">
+						<VSCodeCheckbox
+							checked={enableChatAutocomplete || false}
+							onChange={onEnableChatAutocompleteChange}>
+							<span className="font-medium">
+								{t("kilocode:ghost.settings.enableChatAutocomplete.label")}
+							</span>
+						</VSCodeCheckbox>
+						<div className="text-vscode-descriptionForeground text-sm mt-1">
+							<Trans i18nKey="kilocode:ghost.settings.enableChatAutocomplete.description" />
+						</div>
+					</div>
+
 					<div className="flex flex-col gap-1">
 						<div className="flex items-center gap-2 font-bold">
 							<Bot className="w-4" />

+ 13 - 0
webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx

@@ -95,6 +95,7 @@ const defaultGhostServiceSettings: GhostServiceSettings = {
 	enableAutoTrigger: false,
 	enableQuickInlineTaskKeybinding: false,
 	enableSmartInlineTaskKeybinding: false,
+	enableChatAutocomplete: false,
 	provider: "openrouter",
 	model: "openai/gpt-4o-mini",
 }
@@ -177,6 +178,18 @@ describe("GhostServiceSettingsView", () => {
 		expect(onGhostServiceSettingsChange).toHaveBeenCalledWith("enableSmartInlineTaskKeybinding", true)
 	})
 
+	it("toggles chat autocomplete checkbox correctly", () => {
+		const onGhostServiceSettingsChange = vi.fn()
+		renderComponent({ onGhostServiceSettingsChange })
+
+		const checkboxLabel = screen.getByText(/kilocode:ghost.settings.enableChatAutocomplete.label/).closest("label")
+		const checkbox = checkboxLabel?.querySelector('input[type="checkbox"]') as HTMLInputElement
+
+		fireEvent.click(checkbox)
+
+		expect(onGhostServiceSettingsChange).toHaveBeenCalledWith("enableChatAutocomplete", true)
+	})
+
 	it("renders Trans components with proper structure", () => {
 		renderComponent()
 

+ 5 - 1
webview-ui/src/i18n/locales/ar/kilocode.json

@@ -240,7 +240,11 @@
 			"noModelConfigured": "لم يتم العثور على نموذج إكمال تلقائي مناسب. يرجى تكوين مزود في إعدادات API.",
 			"configureAutocompleteProfile": "استخدم أي نموذج بالانتقال إلى الملفات الشخصية وتكوين ملف شخصي من نوع الإكمال التلقائي.",
 			"model": "النموذج",
-			"provider": "المزود"
+			"provider": "المزود",
+			"enableChatAutocomplete": {
+				"label": "الإكمال التلقائي لمدخلات الدردشة",
+				"description": "عند التفعيل، سيقترح Kilo Code إكمالات أثناء كتابتك في مدخل الدردشة. اضغط على Tab لقبول الاقتراحات."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/ca/kilocode.json

@@ -231,7 +231,11 @@
 			"noModelConfigured": "No s'ha trobat cap model d'autocompletat adequat. Configura un proveïdor a la configuració de l'API.",
 			"configureAutocompleteProfile": "Utilitza qualsevol model anant a perfils i configurant un Perfil del Tipus de Perfil Autocompletat.",
 			"model": "Model",
-			"provider": "Proveïdor"
+			"provider": "Proveïdor",
+			"enableChatAutocomplete": {
+				"description": "Quan està activat, Kilo Code suggerirà complecions mentre escriviu a l'entrada del xat. Premeu Tab per acceptar els suggeriments.",
+				"label": "Autocompletat d'entrada de xat"
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/cs/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "Nebyl nalezen žádný vhodný model pro automatické dokončování. Nakonfiguruj prosím poskytovatele v nastavení API.",
 			"configureAutocompleteProfile": "Použij libovolný model tak, že přejdeš do profilů a nakonfiguruješ Profil typu Automatické dokončování.",
 			"model": "Model",
-			"provider": "Poskytovatel"
+			"provider": "Poskytovatel",
+			"enableChatAutocomplete": {
+				"label": "Automatické doplňování při psaní zpráv",
+				"description": "Když je tato funkce povolena, Kilo Code bude navrhovat dokončování textu během psaní ve vstupním poli chatu. Stisknutím klávesy Tab přijmete návrhy."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/de/kilocode.json

@@ -237,7 +237,11 @@
 			},
 			"keybindingNotFound": "nicht gefunden",
 			"noModelConfigured": "Kein geeignetes Autocomplete-Modell gefunden. Bitte konfiguriere einen Provider in den API-Einstellungen.",
-			"configureAutocompleteProfile": "Verwende ein beliebiges Modell, indem du zu Profilen gehst und ein Profil vom Profiltyp Autocomplete konfigurierst."
+			"configureAutocompleteProfile": "Verwende ein beliebiges Modell, indem du zu Profilen gehst und ein Profil vom Profiltyp Autocomplete konfigurierst.",
+			"enableChatAutocomplete": {
+				"label": "Chat-Eingabe-Autovervollständigung",
+				"description": "Wenn aktiviert, wird Kilo Code Vervollständigungen vorschlagen, während Sie im Chat-Eingabefeld tippen. Drücken Sie Tab, um Vorschläge zu akzeptieren."
+			}
 		}
 	},
 	"virtualProvider": {

+ 4 - 0
webview-ui/src/i18n/locales/en/kilocode.json

@@ -249,6 +249,10 @@
 				"label": "Manual Autocomplete ({{keybinding}})",
 				"description": "Need a quick fix, completion, or refactor? Kilo will use the surrounding context to offer immediate improvements, keeping you in the flow. <DocsLink>Edit shortcut</DocsLink>"
 			},
+			"enableChatAutocomplete": {
+				"label": "Chat Input Autocomplete",
+				"description": "When enabled, Kilo Code will suggest completions as you type in the chat input. Press Tab to accept suggestions."
+			},
 			"keybindingNotFound": "not found",
 			"noModelConfigured": "No suitable autocomplete model found. Please configure a provider in the API settings.",
 			"configureAutocompleteProfile": "Use any model by going to profiles and configuring a Profile of the Profile Type Autocomplete."

+ 5 - 1
webview-ui/src/i18n/locales/es/kilocode.json

@@ -238,7 +238,11 @@
 			"noModelConfigured": "No se encontró ningún modelo de autocompletado adecuado. Por favor, configura un proveedor en la configuración de API.",
 			"configureAutocompleteProfile": "Usa cualquier modelo yendo a perfiles y configurando un Perfil del Tipo de Perfil Autocompletado.",
 			"model": "Modelo",
-			"provider": "Proveedor"
+			"provider": "Proveedor",
+			"enableChatAutocomplete": {
+				"description": "Cuando está habilitado, Kilo Code sugerirá completaciones mientras escribes en el campo de chat. Presiona Tab para aceptar las sugerencias.",
+				"label": "Autocompletado de Entrada de Chat"
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/fr/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "Aucun modèle d'autocomplétion approprié trouvé. Configure un fournisseur dans les paramètres API.",
 			"configureAutocompleteProfile": "Utilise n'importe quel modèle en allant dans les profils et en configurant un Profil du Type de Profil Autocomplétion.",
 			"model": "Modèle",
-			"provider": "Fournisseur"
+			"provider": "Fournisseur",
+			"enableChatAutocomplete": {
+				"label": "Saisie automatique de discussion",
+				"description": "Lorsqu'il est activé, Kilo Code proposera des suggestions de complétion pendant que vous tapez dans le champ de discussion. Appuyez sur Tab pour accepter les suggestions."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/hi/kilocode.json

@@ -231,7 +231,11 @@
 			"noModelConfigured": "कोई उपयुक्त ऑटोकम्पलीट मॉडल नहीं मिला। कृपया API सेटिंग्स में एक प्रदाता कॉन्फ़िगर करें।",
 			"configureAutocompleteProfile": "प्रोफाइल में जाकर और ऑटोकम्पलीट प्रोफाइल टाइप की एक प्रोफाइल कॉन्फ़िगर करके किसी भी मॉडल का उपयोग करें।",
 			"model": "मॉडल",
-			"provider": "प्रदाता"
+			"provider": "प्रदाता",
+			"enableChatAutocomplete": {
+				"label": "चैट इनपुट स्वतःपूर्ण",
+				"description": "जब सक्षम होता है, Kilo Code चैट इनपुट में टाइप करते समय पूर्णताएं सुझाएगा। सुझावों को स्वीकार करने के लिए Tab दबाएं।"
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/id/kilocode.json

@@ -231,7 +231,11 @@
 			"noModelConfigured": "Tidak ditemukan model autocomplete yang sesuai. Silakan konfigurasi penyedia di pengaturan API.",
 			"configureAutocompleteProfile": "Gunakan model apa pun dengan pergi ke profil dan mengonfigurasi Profil dengan Tipe Profil Autocomplete.",
 			"model": "Model",
-			"provider": "Penyedia"
+			"provider": "Penyedia",
+			"enableChatAutocomplete": {
+				"description": "Jika diaktifkan, Kilo Code akan menyarankan pelengkapan saat Anda mengetik di kotak obrolan. Tekan Tab untuk menerima saran.",
+				"label": "Pelengkapan Otomatis Input Obrolan"
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/it/kilocode.json

@@ -238,7 +238,11 @@
 			"noModelConfigured": "Nessun modello di autocompletamento adatto trovato. Configura un provider nelle impostazioni API.",
 			"configureAutocompleteProfile": "Usa qualsiasi modello andando nei profili e configurando un Profilo del Tipo di Profilo Autocompletamento.",
 			"model": "Modello",
-			"provider": "Provider"
+			"provider": "Provider",
+			"enableChatAutocomplete": {
+				"label": "Completamento Automatico dell'Input della Chat",
+				"description": "Quando abilitato, Kilo Code suggerirà completamenti mentre digiti nell'area di chat. Premi Tab per accettare i suggerimenti."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/ja/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "適切なオートコンプリートモデルが見つかりませんでした。API設定でプロバイダーを設定してください。",
 			"configureAutocompleteProfile": "プロファイルに移動し、プロファイルタイプがオートコンプリートのプロファイルを設定することで、任意のモデルを使用できます。",
 			"model": "モデル",
-			"provider": "プロバイダー"
+			"provider": "プロバイダー",
+			"enableChatAutocomplete": {
+				"description": "有効にすると、Kilo Codeはチャット入力の入力中に候補を提案します。Tabキーを押して候補を受け入れることができます。",
+				"label": "チャット入力オートコンプリート"
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/ko/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "적합한 자동완성 모델을 찾을 수 없습니다. API 설정에서 제공자를 구성하세요.",
 			"configureAutocompleteProfile": "프로필로 이동하여 프로필 유형이 자동완성인 프로필을 구성하면 모든 모델을 사용할 수 있습니다.",
 			"model": "모델",
-			"provider": "제공자"
+			"provider": "제공자",
+			"enableChatAutocomplete": {
+				"label": "채팅 입력 자동완성",
+				"description": "활성화되면, Kilo Code는 채팅 입력 시 입력하는 대로 완성 제안을 제공합니다. Tab 키를 눌러 제안을 수락하세요."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/nl/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "Geen geschikt autocomplete-model gevonden. Configureer een provider in de API-instellingen.",
 			"configureAutocompleteProfile": "Gebruik elk model door naar profielen te gaan en een Profiel van het Profieltype Autocomplete te configureren.",
 			"model": "Model",
-			"provider": "Provider"
+			"provider": "Provider",
+			"enableChatAutocomplete": {
+				"label": "Chatinvoer Automatisch Aanvullen",
+				"description": "Wanneer ingeschakeld, zal Kilo Code suggesties voor aanvullingen geven terwijl je typt in de chatinvoer. Druk op Tab om suggesties te accepteren."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/pl/kilocode.json

@@ -238,7 +238,11 @@
 			"noModelConfigured": "Nie znaleziono odpowiedniego modelu autouzupełniania. Skonfiguruj dostawcę w ustawieniach API.",
 			"configureAutocompleteProfile": "Użyj dowolnego modelu, przechodząc do profili i konfigurując Profil typu Autouzupełnianie.",
 			"model": "Model",
-			"provider": "Dostawca"
+			"provider": "Dostawca",
+			"enableChatAutocomplete": {
+				"label": "Automatyczne uzupełnianie wpisów czatu",
+				"description": "Po włączeniu, Kilo Code będzie sugerować uzupełnienia podczas pisania w oknie czatu. Naciśnij Tab, aby zaakceptować sugestie."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/pt-BR/kilocode.json

@@ -238,7 +238,11 @@
 			"noModelConfigured": "Nenhum modelo de autocompletar adequado encontrado. Configure um provedor nas configurações da API.",
 			"configureAutocompleteProfile": "Use qualquer modelo indo para perfis e configurando um Perfil do Tipo de Perfil Autocompletar.",
 			"model": "Modelo",
-			"provider": "Provedor"
+			"provider": "Provedor",
+			"enableChatAutocomplete": {
+				"label": "Preenchimento Automático do Chat",
+				"description": "Quando ativado, o Kilo Code irá sugerir conclusões enquanto você digita no campo de chat. Pressione Tab para aceitar as sugestões."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/ru/kilocode.json

@@ -238,7 +238,11 @@
 			"noModelConfigured": "Подходящая модель автодополнения не найдена. Настрой провайдера в настройках API.",
 			"configureAutocompleteProfile": "Используй любую модель, перейдя в профили и настроив Профиль типа Автодополнение.",
 			"model": "Модель",
-			"provider": "Провайдер"
+			"provider": "Провайдер",
+			"enableChatAutocomplete": {
+				"label": "Автодополнение в чате",
+				"description": "При включении Kilo Code будет предлагать варианты завершения во время ввода в окне чата. Нажмите Tab, чтобы принять предложения."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/th/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "ไม่พบโมเดลเติมข้อความอัตโนมัติที่เหมาะสม กรุณาตั้งค่าผู้ให้บริการในการตั้งค่า API",
 			"configureAutocompleteProfile": "ใช้โมเดลใดก็ได้โดยไปที่โปรไฟล์และตั้งค่าโปรไฟล์ประเภทเติมข้อความอัตโนมัติ",
 			"model": "โมเดล",
-			"provider": "ผู้ให้บริการ"
+			"provider": "ผู้ให้บริการ",
+			"enableChatAutocomplete": {
+				"label": "การเติมข้อความอัตโนมัติในการแชท",
+				"description": "เมื่อเปิดใช้งาน Kilo Code จะแนะนำข้อความเพื่อให้คุณพิมพ์ต่อในช่องแชท กดแท็บเพื่อยอมรับคำแนะนำ"
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/tr/kilocode.json

@@ -238,7 +238,11 @@
 			"noModelConfigured": "Uygun otomatik tamamlama modeli bulunamadı. Lütfen API ayarlarında bir sağlayıcı yapılandır.",
 			"configureAutocompleteProfile": "Profillere giderek ve Otomatik Tamamlama Profil Türünde bir Profil yapılandırarak herhangi bir model kullan.",
 			"model": "Model",
-			"provider": "Sağlayıcı"
+			"provider": "Sağlayıcı",
+			"enableChatAutocomplete": {
+				"label": "Sohbet Girişi Otomatik Tamamlama",
+				"description": "Etkinleştirildiğinde, Kilo Code sohbet girişinde yazarken tamamlama önerileri sunacaktır. Önerileri kabul etmek için Tab tuşuna basın."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/uk/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "Не знайдено відповідної моделі автодоповнення. Налаштуй провайдера в налаштуваннях API.",
 			"configureAutocompleteProfile": "Використовуй будь-яку модель, перейшовши до профілів і налаштувавши Профіль типу Автодоповнення.",
 			"model": "Модель",
-			"provider": "Провайдер"
+			"provider": "Провайдер",
+			"enableChatAutocomplete": {
+				"label": "Автозаповнення вводу чату",
+				"description": "Якщо ввімкнено, Kilo Code пропонуватиме доповнення під час введення в чаті. Натисніть Tab, щоб прийняти пропозиції."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/vi/kilocode.json

@@ -238,7 +238,11 @@
 			"noModelConfigured": "Không tìm thấy mô hình tự động hoàn thành phù hợp. Vui lòng cấu hình nhà cung cấp trong cài đặt API.",
 			"configureAutocompleteProfile": "Sử dụng bất kỳ mô hình nào bằng cách vào hồ sơ và cấu hình Hồ sơ có Loại Hồ sơ là Tự động hoàn thành.",
 			"model": "Mô hình",
-			"provider": "Nhà cung cấp"
+			"provider": "Nhà cung cấp",
+			"enableChatAutocomplete": {
+				"label": "Gợi ý Tự động Hoàn thành Trong Trò chuyện",
+				"description": "Khi được bật, Kilo Code sẽ đề xuất các gợi ý hoàn thành khi bạn nhập trong ô chat. Nhấn Tab để chấp nhận các gợi ý."
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/zh-CN/kilocode.json

@@ -245,7 +245,11 @@
 			"noModelConfigured": "未找到合适的自动补全模型。请在 API 设置中配置提供商。",
 			"configureAutocompleteProfile": "前往配置文件并配置自动补全类型的配置文件即可使用任意模型。",
 			"model": "模型",
-			"provider": "提供商"
+			"provider": "提供商",
+			"enableChatAutocomplete": {
+				"label": "聊天输入自动完成",
+				"description": "启用后,Kilo Code 将在您输入聊天内容时提供补全建议。按 Tab 键接受建议。"
+			}
 		}
 	},
 	"virtualProvider": {

+ 5 - 1
webview-ui/src/i18n/locales/zh-TW/kilocode.json

@@ -240,7 +240,11 @@
 			"noModelConfigured": "找不到合適的自動補全模型。請在 API 設定中配置提供者。",
 			"configureAutocompleteProfile": "前往設定檔並設定自動補全類型的設定檔即可使用任意模型。",
 			"model": "模型",
-			"provider": "供應商"
+			"provider": "供應商",
+			"enableChatAutocomplete": {
+				"label": "聊天输入自动完成",
+				"description": "启用后,Kilo Code 将在您在聊天输入框中输入时提供补全建议。按 Tab 键接受建议。"
+			}
 		}
 	},
 	"virtualProvider": {

+ 9 - 0
webview-ui/src/index.css

@@ -410,6 +410,15 @@ vscode-dropdown::part(listbox) {
 	font-family: var(--font-mono);
 }
 
+/* kilocode_change start: FIM autocomplete ghost text */
+.chat-ghost-text {
+	color: var(--vscode-editorGhostText-foreground, rgba(255, 255, 255, 0.4));
+	opacity: 0.6;
+	pointer-events: none;
+	user-select: none;
+}
+/* kilocode_change end: FIM autocomplete ghost text */
+
 /**
  * vscrui Overrides / Hacks
  */

+ 1 - 1
webview-ui/src/utils/highlight.ts

@@ -3,7 +3,7 @@ import { LRUCache } from "lru-cache"
 // LRU cache for escapeHtml with reasonable size limit
 const escapeHtmlCache = new LRUCache<string, string>({ max: 500 })
 
-function escapeHtml(text: string): string {
+export function escapeHtml(text: string): string {
 	// Check cache first
 	const cached = escapeHtmlCache.get(text)
 	if (cached !== undefined) {