Bläddra i källkod

feat: add per-workspace indexing opt-in and stop/cancel control (#11456)

* feat: add per-workspace indexing opt-in and stop/cancel control

- Add codeIndexWorkspaceEnabled flag in workspaceState (default: false)
- Thread AbortController/AbortSignal through orchestrator → scanner
- Add Stop Indexing button and Stopping state to UI
- Fix handleSettingsChange() to abort active scan when disabling toggle
- Add translations for all 18 locales

* fix: correct abort handling in indexing scanner and orchestrator

- Re-throw AbortError in scanner's file processing catch block to prevent
  abort signals from being silently swallowed as file errors
- Reorder stopWatcher() before setSystemState() in orchestrator abort
  catch path to ensure watcher cleanup before state transition
- Update scanner test to assert AbortError propagation on mid-scan abort

* fix: optimize workspace check ordering, translate new i18n keys, fix abort handling

- Move workspace-enabled check before _recreateServices() in initialize()
  to avoid creating Qdrant/embedder connections for disabled workspaces
- Translate new i18n keys (indexingStopped, indexingStoppedPartial, stopping,
  stopIndexingButton, stoppingButton, workspaceToggleLabel,
  workspaceDisabledMessage) in all 17 non-English locales
- Re-throw AbortError in scanner catch block to prevent silent swallowing
- Reorder stopWatcher() before setSystemState() in orchestrator abort path
- Update scanner test to assert AbortError propagation on mid-scan abort
- Fix recoverFromError test for workspace-enabled check ordering

* fix: per-folder enablement key, abort-safe dispose and back-pressure, translate i18n

Addresses 0xMink review feedback:
- Store workspace enablement keyed by folder path to support multi-root
  workspaces (codeIndexWorkspaceEnabled:<path> instead of single boolean)
- Add test proving folder A enabled does not enable folder B
- dispose() now calls stopIndexing() to abort orphaned scans on folder removal
- Scanner back-pressure loop checks abort signal to avoid spin-waiting
- Move workspace-enabled check before _recreateServices() in initialize()
- Translate new i18n keys in all 17 non-English locales
- Fix abort handling in orchestrator and scanner catch blocks

* fix: flush debounced cache writes on abort to preserve indexing progress

* feat: add global auto-enable default for backward-compatible workspace indexing

* fix: stop/start indexer when auto-enable default changes effective state

* fix: URI-keyed enablement, throw AbortError in back-pressure, stopWatcher on early-return

* fix: iterate all managers when auto-enable default changes in multi-root workspaces

---------

Co-authored-by: James Mtendamema <[email protected]>
James Mtendamema 5 dagar sedan
förälder
incheckning
67ea856fd0
70 ändrade filer med 983 tillägg och 101 borttagningar
  1. 9 0
      .changeset/indexing-workspace-opt-in-and-stop-control.md
  2. 6 1
      packages/types/src/vscode-extension-host.ts
  3. 81 9
      src/core/webview/webviewMessageHandler.ts
  4. 3 1
      src/i18n/locales/ca/embeddings.json
  5. 3 1
      src/i18n/locales/de/embeddings.json
  6. 3 1
      src/i18n/locales/en/embeddings.json
  7. 3 1
      src/i18n/locales/es/embeddings.json
  8. 3 1
      src/i18n/locales/fr/embeddings.json
  9. 3 1
      src/i18n/locales/hi/embeddings.json
  10. 3 1
      src/i18n/locales/id/embeddings.json
  11. 3 1
      src/i18n/locales/it/embeddings.json
  12. 3 1
      src/i18n/locales/ja/embeddings.json
  13. 3 1
      src/i18n/locales/ko/embeddings.json
  14. 3 1
      src/i18n/locales/nl/embeddings.json
  15. 3 1
      src/i18n/locales/pl/embeddings.json
  16. 3 1
      src/i18n/locales/pt-BR/embeddings.json
  17. 3 1
      src/i18n/locales/ru/embeddings.json
  18. 3 1
      src/i18n/locales/tr/embeddings.json
  19. 3 1
      src/i18n/locales/vi/embeddings.json
  20. 3 1
      src/i18n/locales/zh-CN/embeddings.json
  21. 3 1
      src/i18n/locales/zh-TW/embeddings.json
  22. 201 7
      src/services/code-index/__tests__/manager.spec.ts
  23. 176 0
      src/services/code-index/__tests__/orchestrator.spec.ts
  24. 7 0
      src/services/code-index/cache-manager.ts
  25. 1 0
      src/services/code-index/interfaces/cache.ts
  26. 1 0
      src/services/code-index/interfaces/file-processor.ts
  27. 6 1
      src/services/code-index/interfaces/manager.ts
  28. 80 26
      src/services/code-index/manager.ts
  29. 42 1
      src/services/code-index/orchestrator.ts
  30. 63 0
      src/services/code-index/processors/__tests__/scanner.spec.ts
  31. 38 1
      src/services/code-index/processors/scanner.ts
  32. 5 1
      src/services/code-index/state-manager.ts
  33. 66 0
      webview-ui/src/components/chat/CodeIndexPopover.tsx
  34. 3 0
      webview-ui/src/components/chat/IndexingStatusBadge.tsx
  35. 2 1
      webview-ui/src/i18n/locales/ca/chat.json
  36. 6 1
      webview-ui/src/i18n/locales/ca/settings.json
  37. 2 1
      webview-ui/src/i18n/locales/de/chat.json
  38. 6 1
      webview-ui/src/i18n/locales/de/settings.json
  39. 2 1
      webview-ui/src/i18n/locales/en/chat.json
  40. 6 1
      webview-ui/src/i18n/locales/en/settings.json
  41. 2 1
      webview-ui/src/i18n/locales/es/chat.json
  42. 6 1
      webview-ui/src/i18n/locales/es/settings.json
  43. 2 1
      webview-ui/src/i18n/locales/fr/chat.json
  44. 6 1
      webview-ui/src/i18n/locales/fr/settings.json
  45. 2 1
      webview-ui/src/i18n/locales/hi/chat.json
  46. 6 1
      webview-ui/src/i18n/locales/hi/settings.json
  47. 2 1
      webview-ui/src/i18n/locales/id/chat.json
  48. 6 1
      webview-ui/src/i18n/locales/id/settings.json
  49. 2 1
      webview-ui/src/i18n/locales/it/chat.json
  50. 6 1
      webview-ui/src/i18n/locales/it/settings.json
  51. 2 1
      webview-ui/src/i18n/locales/ja/chat.json
  52. 6 1
      webview-ui/src/i18n/locales/ja/settings.json
  53. 2 1
      webview-ui/src/i18n/locales/ko/chat.json
  54. 6 1
      webview-ui/src/i18n/locales/ko/settings.json
  55. 2 1
      webview-ui/src/i18n/locales/nl/chat.json
  56. 6 1
      webview-ui/src/i18n/locales/nl/settings.json
  57. 2 1
      webview-ui/src/i18n/locales/pl/chat.json
  58. 6 1
      webview-ui/src/i18n/locales/pl/settings.json
  59. 2 1
      webview-ui/src/i18n/locales/pt-BR/chat.json
  60. 6 1
      webview-ui/src/i18n/locales/pt-BR/settings.json
  61. 2 1
      webview-ui/src/i18n/locales/ru/chat.json
  62. 6 1
      webview-ui/src/i18n/locales/ru/settings.json
  63. 2 1
      webview-ui/src/i18n/locales/tr/chat.json
  64. 6 1
      webview-ui/src/i18n/locales/tr/settings.json
  65. 2 1
      webview-ui/src/i18n/locales/vi/chat.json
  66. 6 1
      webview-ui/src/i18n/locales/vi/settings.json
  67. 2 1
      webview-ui/src/i18n/locales/zh-CN/chat.json
  68. 6 1
      webview-ui/src/i18n/locales/zh-CN/settings.json
  69. 2 1
      webview-ui/src/i18n/locales/zh-TW/chat.json
  70. 6 1
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 9 - 0
.changeset/indexing-workspace-opt-in-and-stop-control.md

@@ -0,0 +1,9 @@
+---
+"roo-cline": minor
+---
+
+Add per-workspace indexing opt-in and stop/cancel indexing controls
+
+- **Per-workspace indexing opt-in**: Indexing no longer auto-starts on every workspace. A new `codeIndexWorkspaceEnabled` flag (stored in `workspaceState`, default: false) requires users to explicitly enable indexing per workspace via a toggle in the CodeIndex popover. The choice is remembered across sessions.
+- **Stop/cancel indexing**: Users can stop an in-progress indexing operation via a "Stop Indexing" button. Uses `AbortController`/`AbortSignal` threaded through the orchestrator → scanner pipeline with graceful abort at file and batch boundaries.
+- **Disable toggle bug fix**: Unchecking "Enable Codebase Indexing" during active indexing now properly stops the scan via `stopIndexing()` instead of only calling `stopWatcher()`, which left the scanner running asynchronously.

+ 6 - 1
packages/types/src/vscode-extension-host.ts

@@ -508,9 +508,12 @@ export interface WebviewMessage {
 		| "condenseTaskContextRequest"
 		| "condenseTaskContextRequest"
 		| "requestIndexingStatus"
 		| "requestIndexingStatus"
 		| "startIndexing"
 		| "startIndexing"
+		| "stopIndexing"
 		| "clearIndexData"
 		| "clearIndexData"
 		| "indexingStatusUpdate"
 		| "indexingStatusUpdate"
 		| "indexCleared"
 		| "indexCleared"
+		| "toggleWorkspaceIndexing"
+		| "setAutoEnableDefault"
 		| "focusPanelRequest"
 		| "focusPanelRequest"
 		| "openExternal"
 		| "openExternal"
 		| "filterMarketplaceItems"
 		| "filterMarketplaceItems"
@@ -705,7 +708,7 @@ export const checkoutRestorePayloadSchema = z.object({
 export type CheckpointRestorePayload = z.infer<typeof checkoutRestorePayloadSchema>
 export type CheckpointRestorePayload = z.infer<typeof checkoutRestorePayloadSchema>
 
 
 export interface IndexingStatusPayload {
 export interface IndexingStatusPayload {
-	state: "Standby" | "Indexing" | "Indexed" | "Error"
+	state: "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping"
 	message: string
 	message: string
 }
 }
 
 
@@ -739,6 +742,8 @@ export interface IndexingStatus {
 	totalItems: number
 	totalItems: number
 	currentItemUnit?: string
 	currentItemUnit?: string
 	workspacePath?: string
 	workspacePath?: string
+	workspaceEnabled?: boolean
+	autoEnableDefault?: boolean
 }
 }
 
 
 export interface IndexingStatusUpdateMessage {
 export interface IndexingStatusUpdateMessage {

+ 81 - 9
src/core/webview/webviewMessageHandler.ts

@@ -44,6 +44,7 @@ import { Package } from "../../shared/package"
 import { type RouterName, toRouterName } from "../../shared/api"
 import { type RouterName, toRouterName } from "../../shared/api"
 import { MessageEnhancer } from "./messageEnhancer"
 import { MessageEnhancer } from "./messageEnhancer"
 
 
+import { CodeIndexManager } from "../../services/code-index/manager"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
 import { experimentDefault } from "../../shared/experiments"
 import { experimentDefault } from "../../shared/experiments"
 import { Terminal } from "../../integrations/terminal/Terminal"
 import { Terminal } from "../../integrations/terminal/Terminal"
@@ -2608,7 +2609,6 @@ export const webviewMessageHandler = async (
 			try {
 			try {
 				const manager = provider.getCurrentWorkspaceCodeIndexManager()
 				const manager = provider.getCurrentWorkspaceCodeIndexManager()
 				if (!manager) {
 				if (!manager) {
-					// No workspace open - send error status
 					provider.postMessageToWebview({
 					provider.postMessageToWebview({
 						type: "indexingStatusUpdate",
 						type: "indexingStatusUpdate",
 						values: {
 						values: {
@@ -2622,23 +2622,19 @@ export const webviewMessageHandler = async (
 					provider.log("Cannot start indexing: No workspace folder open")
 					provider.log("Cannot start indexing: No workspace folder open")
 					return
 					return
 				}
 				}
+
+				// "Start Indexing" implicitly enables the workspace
+				await manager.setWorkspaceEnabled(true)
+
 				if (manager.isFeatureEnabled && manager.isFeatureConfigured) {
 				if (manager.isFeatureEnabled && manager.isFeatureConfigured) {
-					// Mimic extension startup behavior: initialize first, which will
-					// check if Qdrant container is active and reuse existing collection
 					await manager.initialize(provider.contextProxy)
 					await manager.initialize(provider.contextProxy)
 
 
-					// Only call startIndexing if we're in a state that requires it
-					// (e.g., Standby or Error). If already Indexed or Indexing, the
-					// initialize() call above will have already started the watcher.
 					const currentState = manager.state
 					const currentState = manager.state
 					if (currentState === "Standby" || currentState === "Error") {
 					if (currentState === "Standby" || currentState === "Error") {
-						// startIndexing now handles error recovery internally
 						manager.startIndexing()
 						manager.startIndexing()
 
 
-						// If startIndexing recovered from error, we need to reinitialize
 						if (!manager.isInitialized) {
 						if (!manager.isInitialized) {
 							await manager.initialize(provider.contextProxy)
 							await manager.initialize(provider.contextProxy)
-							// Try starting again after initialization
 							if (manager.state === "Standby" || manager.state === "Error") {
 							if (manager.state === "Standby" || manager.state === "Error") {
 								manager.startIndexing()
 								manager.startIndexing()
 							}
 							}
@@ -2650,6 +2646,82 @@ export const webviewMessageHandler = async (
 			}
 			}
 			break
 			break
 		}
 		}
+		case "stopIndexing": {
+			try {
+				const manager = provider.getCurrentWorkspaceCodeIndexManager()
+				if (!manager) {
+					provider.log("Cannot stop indexing: No workspace folder open")
+					return
+				}
+				manager.stopIndexing()
+				provider.postMessageToWebview({
+					type: "indexingStatusUpdate",
+					values: manager.getCurrentStatus(),
+				})
+			} catch (error) {
+				provider.log(`Error stopping indexing: ${error instanceof Error ? error.message : String(error)}`)
+			}
+			break
+		}
+		case "toggleWorkspaceIndexing": {
+			try {
+				const manager = provider.getCurrentWorkspaceCodeIndexManager()
+				if (!manager) {
+					provider.log("Cannot toggle workspace indexing: No workspace folder open")
+					return
+				}
+				const enabled = message.bool ?? false
+				await manager.setWorkspaceEnabled(enabled)
+				if (enabled && manager.isFeatureEnabled && manager.isFeatureConfigured) {
+					await manager.initialize(provider.contextProxy)
+					manager.startIndexing()
+				} else if (!enabled) {
+					manager.stopIndexing()
+				}
+				provider.postMessageToWebview({
+					type: "indexingStatusUpdate",
+					values: manager.getCurrentStatus(),
+				})
+			} catch (error) {
+				provider.log(
+					`Error toggling workspace indexing: ${error instanceof Error ? error.message : String(error)}`,
+				)
+			}
+			break
+		}
+		case "setAutoEnableDefault": {
+			try {
+				const manager = provider.getCurrentWorkspaceCodeIndexManager()
+				if (!manager) {
+					provider.log("Cannot set auto-enable default: No workspace folder open")
+					return
+				}
+				// Capture prior state for every manager before persisting the global change
+				const allManagers = CodeIndexManager.getAllInstances()
+				const priorStates = new Map(allManagers.map((m) => [m, m.isWorkspaceEnabled]))
+				await manager.setAutoEnableDefault(message.bool ?? true)
+				// Apply stop/start to every affected manager
+				for (const m of allManagers) {
+					const wasEnabled = priorStates.get(m)!
+					const isNowEnabled = m.isWorkspaceEnabled
+					if (wasEnabled && !isNowEnabled) {
+						m.stopIndexing()
+					} else if (!wasEnabled && isNowEnabled && m.isFeatureEnabled && m.isFeatureConfigured) {
+						await m.initialize(provider.contextProxy)
+						m.startIndexing()
+					}
+				}
+				provider.postMessageToWebview({
+					type: "indexingStatusUpdate",
+					values: manager.getCurrentStatus(),
+				})
+			} catch (error) {
+				provider.log(
+					`Error setting auto-enable default: ${error instanceof Error ? error.message : String(error)}`,
+				)
+			}
+			break
+		}
 		case "clearIndexData": {
 		case "clearIndexData": {
 			try {
 			try {
 				const manager = provider.getCurrentWorkspaceCodeIndexManager()
 				const manager = provider.getCurrentWorkspaceCodeIndexManager()

+ 3 - 1
src/i18n/locales/ca/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Monitor de fitxers aturat.",
 		"fileWatcherStopped": "Monitor de fitxers aturat.",
 		"failedDuringInitialScan": "Ha fallat durant l'escaneig inicial: {{errorMessage}}",
 		"failedDuringInitialScan": "Ha fallat durant l'escaneig inicial: {{errorMessage}}",
 		"unknownError": "Error desconegut",
 		"unknownError": "Error desconegut",
-		"indexingRequiresWorkspace": "Indexació requereix una carpeta de workspace oberta"
+		"indexingRequiresWorkspace": "Indexació requereix una carpeta de workspace oberta",
+		"indexingStopped": "Indexació aturada per l'usuari.",
+		"indexingStoppedPartial": "Indexació aturada. Dades d'índex parcials conservades."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/de/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Datei-Watcher gestoppt.",
 		"fileWatcherStopped": "Datei-Watcher gestoppt.",
 		"failedDuringInitialScan": "Fehler während des ersten Scans: {{errorMessage}}",
 		"failedDuringInitialScan": "Fehler während des ersten Scans: {{errorMessage}}",
 		"unknownError": "Unbekannter Fehler",
 		"unknownError": "Unbekannter Fehler",
-		"indexingRequiresWorkspace": "Indexierung erfordert einen offenen Workspace-Ordner"
+		"indexingRequiresWorkspace": "Indexierung erfordert einen offenen Workspace-Ordner",
+		"indexingStopped": "Indexierung vom Benutzer gestoppt.",
+		"indexingStoppedPartial": "Indexierung gestoppt. Teilweise Indexdaten beibehalten."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/en/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "File watcher stopped.",
 		"fileWatcherStopped": "File watcher stopped.",
 		"failedDuringInitialScan": "Failed during initial scan: {{errorMessage}}",
 		"failedDuringInitialScan": "Failed during initial scan: {{errorMessage}}",
 		"unknownError": "Unknown error",
 		"unknownError": "Unknown error",
-		"indexingRequiresWorkspace": "Indexing requires an open workspace folder"
+		"indexingRequiresWorkspace": "Indexing requires an open workspace folder",
+		"indexingStopped": "Indexing stopped by user.",
+		"indexingStoppedPartial": "Indexing stopped. Partial index data preserved."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/es/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Monitor de archivos detenido.",
 		"fileWatcherStopped": "Monitor de archivos detenido.",
 		"failedDuringInitialScan": "Falló durante el escaneo inicial: {{errorMessage}}",
 		"failedDuringInitialScan": "Falló durante el escaneo inicial: {{errorMessage}}",
 		"unknownError": "Error desconocido",
 		"unknownError": "Error desconocido",
-		"indexingRequiresWorkspace": "La indexación requiere una carpeta de workspace abierta"
+		"indexingRequiresWorkspace": "La indexación requiere una carpeta de workspace abierta",
+		"indexingStopped": "Indexación detenida por el usuario.",
+		"indexingStoppedPartial": "Indexación detenida. Datos de índice parciales conservados."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/fr/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Surveillant de fichiers arrêté.",
 		"fileWatcherStopped": "Surveillant de fichiers arrêté.",
 		"failedDuringInitialScan": "Échec lors du scan initial : {{errorMessage}}",
 		"failedDuringInitialScan": "Échec lors du scan initial : {{errorMessage}}",
 		"unknownError": "Erreur inconnue",
 		"unknownError": "Erreur inconnue",
-		"indexingRequiresWorkspace": "L'indexation nécessite l'ouverture d'un dossier workspace"
+		"indexingRequiresWorkspace": "L'indexation nécessite l'ouverture d'un dossier workspace",
+		"indexingStopped": "Indexation arrêtée par l'utilisateur.",
+		"indexingStoppedPartial": "Indexation arrêtée. Données d'index partielles conservées."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/hi/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "फाइल वॉचर रुक गया।",
 		"fileWatcherStopped": "फाइल वॉचर रुक गया।",
 		"failedDuringInitialScan": "प्रारंभिक स्कैन के दौरान असफल: {{errorMessage}}",
 		"failedDuringInitialScan": "प्रारंभिक स्कैन के दौरान असफल: {{errorMessage}}",
 		"unknownError": "अज्ञात त्रुटि",
 		"unknownError": "अज्ञात त्रुटि",
-		"indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है"
+		"indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है",
+		"indexingStopped": "उपयोगकर्ता द्वारा इंडेक्सिंग रोकी गई।",
+		"indexingStoppedPartial": "इंडेक्सिंग रोकी गई। आंशिक इंडेक्स डेटा संरक्षित।"
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/id/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Pemantau file dihentikan.",
 		"fileWatcherStopped": "Pemantau file dihentikan.",
 		"failedDuringInitialScan": "Gagal selama pemindaian awal: {{errorMessage}}",
 		"failedDuringInitialScan": "Gagal selama pemindaian awal: {{errorMessage}}",
 		"unknownError": "Kesalahan tidak diketahui",
 		"unknownError": "Kesalahan tidak diketahui",
-		"indexingRequiresWorkspace": "Pengindeksan memerlukan folder workspace yang terbuka"
+		"indexingRequiresWorkspace": "Pengindeksan memerlukan folder workspace yang terbuka",
+		"indexingStopped": "Pengindeksan dihentikan oleh pengguna.",
+		"indexingStoppedPartial": "Pengindeksan dihentikan. Data indeks parsial dipertahankan."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/it/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Monitoraggio file fermato.",
 		"fileWatcherStopped": "Monitoraggio file fermato.",
 		"failedDuringInitialScan": "Fallito durante la scansione iniziale: {{errorMessage}}",
 		"failedDuringInitialScan": "Fallito durante la scansione iniziale: {{errorMessage}}",
 		"unknownError": "Errore sconosciuto",
 		"unknownError": "Errore sconosciuto",
-		"indexingRequiresWorkspace": "L'indicizzazione richiede una cartella di workspace aperta"
+		"indexingRequiresWorkspace": "L'indicizzazione richiede una cartella di workspace aperta",
+		"indexingStopped": "Indicizzazione interrotta dall'utente.",
+		"indexingStoppedPartial": "Indicizzazione interrotta. Dati di indice parziali conservati."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/ja/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "ファイルウォッチャーが停止されました。",
 		"fileWatcherStopped": "ファイルウォッチャーが停止されました。",
 		"failedDuringInitialScan": "初期スキャン中に失敗しました:{{errorMessage}}",
 		"failedDuringInitialScan": "初期スキャン中に失敗しました:{{errorMessage}}",
 		"unknownError": "不明なエラー",
 		"unknownError": "不明なエラー",
-		"indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です"
+		"indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です",
+		"indexingStopped": "ユーザーによりインデックス作成が停止されました。",
+		"indexingStoppedPartial": "インデックス作成が停止されました。部分的なインデックスデータは保持されています。"
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/ko/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "파일 감시자가 중지되었습니다.",
 		"fileWatcherStopped": "파일 감시자가 중지되었습니다.",
 		"failedDuringInitialScan": "초기 스캔 중 실패: {{errorMessage}}",
 		"failedDuringInitialScan": "초기 스캔 중 실패: {{errorMessage}}",
 		"unknownError": "알 수 없는 오류",
 		"unknownError": "알 수 없는 오류",
-		"indexingRequiresWorkspace": "인덱싱에는 열린 워크스페이스 폴더가 필요합니다"
+		"indexingRequiresWorkspace": "인덱싱에는 열린 워크스페이스 폴더가 필요합니다",
+		"indexingStopped": "사용자에 의해 인덱싱이 중지되었습니다.",
+		"indexingStoppedPartial": "인덱싱이 중지되었습니다. 부분 인덱스 데이터가 보존되었습니다."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/nl/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Bestandsmonitor gestopt.",
 		"fileWatcherStopped": "Bestandsmonitor gestopt.",
 		"failedDuringInitialScan": "Mislukt tijdens initiële scan: {{errorMessage}}",
 		"failedDuringInitialScan": "Mislukt tijdens initiële scan: {{errorMessage}}",
 		"unknownError": "Onbekende fout",
 		"unknownError": "Onbekende fout",
-		"indexingRequiresWorkspace": "Indexering vereist een geopende workspace map"
+		"indexingRequiresWorkspace": "Indexering vereist een geopende workspace map",
+		"indexingStopped": "Indexering gestopt door gebruiker.",
+		"indexingStoppedPartial": "Indexering gestopt. Gedeeltelijke indexgegevens bewaard."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/pl/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Monitor plików zatrzymany.",
 		"fileWatcherStopped": "Monitor plików zatrzymany.",
 		"failedDuringInitialScan": "Niepowodzenie podczas początkowego skanowania: {{errorMessage}}",
 		"failedDuringInitialScan": "Niepowodzenie podczas początkowego skanowania: {{errorMessage}}",
 		"unknownError": "Nieznany błąd",
 		"unknownError": "Nieznany błąd",
-		"indexingRequiresWorkspace": "Indeksowanie wymaga otwartego folderu workspace"
+		"indexingRequiresWorkspace": "Indeksowanie wymaga otwartego folderu workspace",
+		"indexingStopped": "Indeksowanie zatrzymane przez użytkownika.",
+		"indexingStoppedPartial": "Indeksowanie zatrzymane. Częściowe dane indeksu zachowane."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/pt-BR/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Monitor de arquivos parado.",
 		"fileWatcherStopped": "Monitor de arquivos parado.",
 		"failedDuringInitialScan": "Falhou durante a varredura inicial: {{errorMessage}}",
 		"failedDuringInitialScan": "Falhou durante a varredura inicial: {{errorMessage}}",
 		"unknownError": "Erro desconhecido",
 		"unknownError": "Erro desconhecido",
-		"indexingRequiresWorkspace": "A indexação requer uma pasta de workspace aberta"
+		"indexingRequiresWorkspace": "A indexação requer uma pasta de workspace aberta",
+		"indexingStopped": "Indexação interrompida pelo usuário.",
+		"indexingStoppedPartial": "Indexação interrompida. Dados de índice parciais preservados."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/ru/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Наблюдатель файлов остановлен.",
 		"fileWatcherStopped": "Наблюдатель файлов остановлен.",
 		"failedDuringInitialScan": "Ошибка во время первоначального сканирования: {{errorMessage}}",
 		"failedDuringInitialScan": "Ошибка во время первоначального сканирования: {{errorMessage}}",
 		"unknownError": "Неизвестная ошибка",
 		"unknownError": "Неизвестная ошибка",
-		"indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства"
+		"indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства",
+		"indexingStopped": "Индексация остановлена пользователем.",
+		"indexingStoppedPartial": "Индексация остановлена. Частичные данные индекса сохранены."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/tr/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Dosya izleyici durduruldu.",
 		"fileWatcherStopped": "Dosya izleyici durduruldu.",
 		"failedDuringInitialScan": "İlk tarama sırasında başarısız: {{errorMessage}}",
 		"failedDuringInitialScan": "İlk tarama sırasında başarısız: {{errorMessage}}",
 		"unknownError": "Bilinmeyen hata",
 		"unknownError": "Bilinmeyen hata",
-		"indexingRequiresWorkspace": "İndeksleme açık bir workspace klasörü gerektirir"
+		"indexingRequiresWorkspace": "İndeksleme açık bir workspace klasörü gerektirir",
+		"indexingStopped": "İndeksleme kullanıcı tarafından durduruldu.",
+		"indexingStoppedPartial": "İndeksleme durduruldu. Kısmi indeks verileri korundu."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/vi/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Trình theo dõi tệp đã dừng.",
 		"fileWatcherStopped": "Trình theo dõi tệp đã dừng.",
 		"failedDuringInitialScan": "Thất bại trong quá trình quét ban đầu: {{errorMessage}}",
 		"failedDuringInitialScan": "Thất bại trong quá trình quét ban đầu: {{errorMessage}}",
 		"unknownError": "Lỗi không xác định",
 		"unknownError": "Lỗi không xác định",
-		"indexingRequiresWorkspace": "Lập chỉ mục yêu cầu một thư mục workspace đang mở"
+		"indexingRequiresWorkspace": "Lập chỉ mục yêu cầu một thư mục workspace đang mở",
+		"indexingStopped": "Lập chỉ mục đã bị dừng bởi người dùng.",
+		"indexingStoppedPartial": "Lập chỉ mục đã dừng. Dữ liệu chỉ mục một phần được bảo toàn."
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/zh-CN/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "文件监控已停止。",
 		"fileWatcherStopped": "文件监控已停止。",
 		"failedDuringInitialScan": "初始扫描失败:{{errorMessage}}",
 		"failedDuringInitialScan": "初始扫描失败:{{errorMessage}}",
 		"unknownError": "未知错误",
 		"unknownError": "未知错误",
-		"indexingRequiresWorkspace": "索引需要打开的工作区文件夹"
+		"indexingRequiresWorkspace": "索引需要打开的工作区文件夹",
+		"indexingStopped": "用户已停止索引。",
+		"indexingStoppedPartial": "索引已停止。部分索引数据已保留。"
 	}
 	}
 }
 }

+ 3 - 1
src/i18n/locales/zh-TW/embeddings.json

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "檔案監控已停止。",
 		"fileWatcherStopped": "檔案監控已停止。",
 		"failedDuringInitialScan": "初始掃描失敗:{{errorMessage}}",
 		"failedDuringInitialScan": "初始掃描失敗:{{errorMessage}}",
 		"unknownError": "未知錯誤",
 		"unknownError": "未知錯誤",
-		"indexingRequiresWorkspace": "索引需要開啟的工作區資料夾"
+		"indexingRequiresWorkspace": "索引需要開啟的工作區資料夾",
+		"indexingStopped": "使用者已停止索引。",
+		"indexingStoppedPartial": "索引已停止。部分索引資料已保留。"
 	}
 	}
 }
 }

+ 201 - 7
src/services/code-index/__tests__/manager.spec.ts

@@ -3,18 +3,45 @@ import { CodeIndexServiceFactory } from "../service-factory"
 import type { MockedClass } from "vitest"
 import type { MockedClass } from "vitest"
 import * as path from "path"
 import * as path from "path"
 
 
+// Helper: create a mock vscode.Uri from an fsPath
+function mockUri(fsPath: string, scheme = "file") {
+	return {
+		fsPath,
+		scheme,
+		authority: "",
+		path: fsPath,
+		toString: (skipEncoding?: boolean) => `${scheme}://${fsPath}`,
+	}
+}
+
 // Mock vscode module
 // Mock vscode module
 vi.mock("vscode", () => {
 vi.mock("vscode", () => {
 	const testPath = require("path")
 	const testPath = require("path")
 	const testWorkspacePath = testPath.join(testPath.sep, "test", "workspace")
 	const testWorkspacePath = testPath.join(testPath.sep, "test", "workspace")
 	return {
 	return {
+		Uri: {
+			file: (p: string) => ({
+				fsPath: p,
+				scheme: "file",
+				authority: "",
+				path: p,
+				toString: (_skipEncoding?: boolean) => `file://${p}`,
+			}),
+			joinPath: vi.fn((...args: any[]) => ({ fsPath: args.join("/") })),
+		},
 		window: {
 		window: {
 			activeTextEditor: null,
 			activeTextEditor: null,
 		},
 		},
 		workspace: {
 		workspace: {
 			workspaceFolders: [
 			workspaceFolders: [
 				{
 				{
-					uri: { fsPath: testWorkspacePath },
+					uri: {
+						fsPath: testWorkspacePath,
+						scheme: "file",
+						authority: "",
+						path: testWorkspacePath,
+						toString: (_skipEncoding?: boolean) => `file://${testWorkspacePath}`,
+					},
 					name: "test",
 					name: "test",
 					index: 0,
 					index: 0,
 				},
 				},
@@ -25,8 +52,9 @@ vi.mock("vscode", () => {
 				onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }),
 				onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }),
 				dispose: vi.fn(),
 				dispose: vi.fn(),
 			}),
 			}),
+			getWorkspaceFolder: vi.fn(),
 		},
 		},
-		RelativePattern: vi.fn().mockImplementation((base, pattern) => ({ base, pattern })),
+		RelativePattern: vi.fn().mockImplementation((base: any, pattern: any) => ({ base, pattern })),
 	}
 	}
 })
 })
 
 
@@ -95,10 +123,22 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 		// Clear all instances before each test
 		// Clear all instances before each test
 		CodeIndexManager.disposeAll()
 		CodeIndexManager.disposeAll()
 
 
+		const workspaceStateStore: Record<string, any> = {}
+		const globalStateStore: Record<string, any> = {}
 		mockContext = {
 		mockContext = {
 			subscriptions: [],
 			subscriptions: [],
-			workspaceState: {} as any,
-			globalState: {} as any,
+			workspaceState: {
+				get: vi.fn((key: string, defaultValue?: any) => workspaceStateStore[key] ?? defaultValue),
+				update: vi.fn(async (key: string, value: any) => {
+					workspaceStateStore[key] = value
+				}),
+			} as any,
+			globalState: {
+				get: vi.fn((key: string, defaultValue?: any) => globalStateStore[key] ?? defaultValue),
+				update: vi.fn(async (key: string, value: any) => {
+					globalStateStore[key] = value
+				}),
+			} as any,
 			extensionUri: {} as any,
 			extensionUri: {} as any,
 			extensionPath: testExtensionPath,
 			extensionPath: testExtensionPath,
 			asAbsolutePath: vi.fn(),
 			asAbsolutePath: vi.fn(),
@@ -222,7 +262,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			;(manager as any)._cacheManager = mockCacheManager
 			;(manager as any)._cacheManager = mockCacheManager
 
 
 			// Simulate an initialized manager by setting the required properties
 			// Simulate an initialized manager by setting the required properties
-			;(manager as any)._orchestrator = { stopWatcher: vi.fn() }
+			;(manager as any)._orchestrator = { stopWatcher: vi.fn(), stopIndexing: vi.fn() }
 			;(manager as any)._searchService = {}
 			;(manager as any)._searchService = {}
 
 
 			// Verify manager is considered initialized
 			// Verify manager is considered initialized
@@ -456,7 +496,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			})
 			})
 
 
 			// Mock orchestrator and search service to simulate initialized state
 			// Mock orchestrator and search service to simulate initialized state
-			;(manager as any)._orchestrator = { stopWatcher: vi.fn(), state: "Error" }
+			;(manager as any)._orchestrator = { stopWatcher: vi.fn(), stopIndexing: vi.fn(), state: "Error" }
 			;(manager as any)._searchService = {}
 			;(manager as any)._searchService = {}
 			;(manager as any)._serviceFactory = {}
 			;(manager as any)._serviceFactory = {}
 		})
 		})
@@ -540,6 +580,9 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 				}),
 				}),
 			}
 			}
 
 
+			// Enable workspace indexing before re-initialization
+			await manager.setWorkspaceEnabled(true)
+
 			// Re-initialize
 			// Re-initialize
 			await manager.initialize(mockContextProxy as any)
 			await manager.initialize(mockContextProxy as any)
 
 
@@ -583,7 +626,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			// Setup manager with service instances
 			// Setup manager with service instances
 			;(manager as any)._configManager = mockConfigManager
 			;(manager as any)._configManager = mockConfigManager
 			;(manager as any)._serviceFactory = {}
 			;(manager as any)._serviceFactory = {}
-			;(manager as any)._orchestrator = { stopWatcher: vi.fn() }
+			;(manager as any)._orchestrator = { stopWatcher: vi.fn(), stopIndexing: vi.fn() }
 			;(manager as any)._searchService = {}
 			;(manager as any)._searchService = {}
 
 
 			// Spy on console.error
 			// Spy on console.error
@@ -608,4 +651,155 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			consoleErrorSpy.mockRestore()
 			consoleErrorSpy.mockRestore()
 		})
 		})
 	})
 	})
+
+	describe("workspace-enabled gating", () => {
+		it("should not start indexing when workspace is not enabled", async () => {
+			await manager.setAutoEnableDefault(false)
+
+			const mockStateManager = (manager as any)._stateManager
+			mockStateManager.setSystemState = vi.fn()
+			mockStateManager.getCurrentStatus = vi.fn().mockReturnValue({
+				systemStatus: "Standby",
+				message: "",
+				processedItems: 0,
+				totalItems: 0,
+				currentItemUnit: "items",
+			})
+
+			expect(manager.isWorkspaceEnabled).toBe(false)
+
+			await manager.startIndexing()
+
+			expect(mockStateManager.setSystemState).not.toHaveBeenCalledWith("Indexing", expect.any(String))
+		})
+
+		it("should include workspaceEnabled in getCurrentStatus", async () => {
+			await manager.setAutoEnableDefault(false)
+
+			const mockStateManager = (manager as any)._stateManager
+			mockStateManager.getCurrentStatus = vi.fn().mockReturnValue({
+				systemStatus: "Standby",
+				message: "",
+				processedItems: 0,
+				totalItems: 0,
+				currentItemUnit: "items",
+			})
+
+			const status = manager.getCurrentStatus()
+			expect(status.workspaceEnabled).toBe(false)
+		})
+
+		it("should persist workspace enabled state", async () => {
+			await manager.setAutoEnableDefault(false)
+			expect(manager.isWorkspaceEnabled).toBe(false)
+
+			await manager.setWorkspaceEnabled(true)
+			expect(manager.isWorkspaceEnabled).toBe(true)
+
+			await manager.setWorkspaceEnabled(false)
+			expect(manager.isWorkspaceEnabled).toBe(false)
+		})
+
+		it("should store enablement per folder URI, not per window", async () => {
+			CodeIndexManager.disposeAll()
+
+			const vscode = await import("vscode")
+
+			const folderAPath = path.join(path.sep, "test", "folderA")
+			const folderBPath = path.join(path.sep, "test", "folderB")
+			const folderAUri = mockUri(folderAPath)
+			const folderBUri = mockUri(folderBPath)
+
+			// Both folders share the same workspaceState (same window)
+			const sharedStore: Record<string, any> = {}
+			const sharedContext = {
+				...mockContext,
+				workspaceState: {
+					get: vi.fn((key: string, defaultValue?: any) => sharedStore[key] ?? defaultValue),
+					update: vi.fn(async (key: string, value: any) => {
+						sharedStore[key] = value
+					}),
+				} as any,
+				globalState: {
+					get: vi.fn((_key: string, _defaultValue?: any) => false),
+					update: vi.fn(),
+				} as any,
+			}
+
+			// Patch workspaceFolders to include both folders
+			;(vscode.workspace as any).workspaceFolders = [
+				{ uri: folderAUri, name: "folderA", index: 0 },
+				{ uri: folderBUri, name: "folderB", index: 1 },
+			]
+
+			const managerA = CodeIndexManager.getInstance(sharedContext as any, folderAPath)!
+			const managerB = CodeIndexManager.getInstance(sharedContext as any, folderBPath)!
+
+			// Both start disabled (autoEnableDefault is false via globalState mock)
+			expect(managerA.isWorkspaceEnabled).toBe(false)
+			expect(managerB.isWorkspaceEnabled).toBe(false)
+
+			// Enable A only
+			await managerA.setWorkspaceEnabled(true)
+
+			expect(managerA.isWorkspaceEnabled).toBe(true)
+			expect(managerB.isWorkspaceEnabled).toBe(false)
+
+			// Enable B, disable A
+			await managerB.setWorkspaceEnabled(true)
+			await managerA.setWorkspaceEnabled(false)
+
+			expect(managerA.isWorkspaceEnabled).toBe(false)
+			expect(managerB.isWorkspaceEnabled).toBe(true)
+
+			CodeIndexManager.disposeAll()
+		})
+	})
+
+	describe("stopIndexing", () => {
+		it("should delegate to orchestrator.stopIndexing()", () => {
+			const mockOrchestrator = {
+				stopIndexing: vi.fn(),
+				stopWatcher: vi.fn(),
+				state: "Indexing",
+			}
+			;(manager as any)._orchestrator = mockOrchestrator
+
+			manager.stopIndexing()
+
+			expect(mockOrchestrator.stopIndexing).toHaveBeenCalled()
+		})
+
+		it("should be safe to call when orchestrator is not set", () => {
+			;(manager as any)._orchestrator = undefined
+
+			expect(() => manager.stopIndexing()).not.toThrow()
+		})
+	})
+
+	describe("handleSettingsChange - disable toggle bug fix", () => {
+		it("should abort active indexing when feature is disabled", async () => {
+			const mockOrchestrator = {
+				stopIndexing: vi.fn(),
+				stopWatcher: vi.fn(),
+				state: "Indexing",
+			}
+			;(manager as any)._orchestrator = mockOrchestrator
+
+			const mockConfigManager = {
+				loadConfiguration: vi.fn().mockResolvedValue({ requiresRestart: false }),
+				isFeatureConfigured: true,
+				isFeatureEnabled: false,
+			}
+			;(manager as any)._configManager = mockConfigManager
+
+			const mockStateManager = (manager as any)._stateManager
+			mockStateManager.setSystemState = vi.fn()
+
+			await manager.handleSettingsChange()
+
+			expect(mockOrchestrator.stopIndexing).toHaveBeenCalled()
+			expect(mockStateManager.setSystemState).toHaveBeenCalledWith("Standby", "Code indexing is disabled")
+		})
+	})
 })
 })

+ 176 - 0
src/services/code-index/__tests__/orchestrator.spec.ts

@@ -79,6 +79,7 @@ describe("CodeIndexOrchestrator - error path cleanup gating", () => {
 
 
 		cacheManager = {
 		cacheManager = {
 			clearCacheFile: vi.fn().mockResolvedValue(undefined),
 			clearCacheFile: vi.fn().mockResolvedValue(undefined),
+			flush: vi.fn().mockResolvedValue(undefined),
 		}
 		}
 
 
 		vectorStore = {
 		vectorStore = {
@@ -158,3 +159,178 @@ describe("CodeIndexOrchestrator - error path cleanup gating", () => {
 		expect(lastCall[0]).toBe("Error")
 		expect(lastCall[0]).toBe("Error")
 	})
 	})
 })
 })
+
+describe("CodeIndexOrchestrator - stopIndexing", () => {
+	const workspacePath = "/test/workspace"
+
+	let configManager: any
+	let stateManager: any
+	let cacheManager: any
+	let vectorStore: any
+	let scanner: any
+	let fileWatcher: any
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+
+		configManager = {
+			isFeatureConfigured: true,
+		}
+
+		let currentState = "Standby"
+		stateManager = {
+			get state() {
+				return currentState
+			},
+			setSystemState: vi.fn().mockImplementation((state: string, _msg: string) => {
+				currentState = state
+			}),
+			reportFileQueueProgress: vi.fn(),
+			reportBlockIndexingProgress: vi.fn(),
+		}
+
+		cacheManager = {
+			clearCacheFile: vi.fn().mockResolvedValue(undefined),
+			flush: vi.fn().mockResolvedValue(undefined),
+		}
+
+		vectorStore = {
+			initialize: vi.fn().mockResolvedValue(false),
+			hasIndexedData: vi.fn().mockResolvedValue(false),
+			markIndexingIncomplete: vi.fn().mockResolvedValue(undefined),
+			markIndexingComplete: vi.fn().mockResolvedValue(undefined),
+			clearCollection: vi.fn().mockResolvedValue(undefined),
+		}
+
+		scanner = {
+			scanDirectory: vi.fn(),
+		}
+
+		fileWatcher = {
+			initialize: vi.fn().mockResolvedValue(undefined),
+			onDidStartBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }),
+			onBatchProgressUpdate: vi.fn().mockReturnValue({ dispose: vi.fn() }),
+			onDidFinishBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }),
+			dispose: vi.fn(),
+		}
+	})
+
+	it("should abort indexing when stopIndexing() is called", async () => {
+		// Make scanner hang until aborted
+		scanner.scanDirectory.mockImplementation(
+			async (_dir: string, _onError?: any, _onBlocksIndexed?: any, _onFileParsed?: any, signal?: AbortSignal) => {
+				// Wait for abort signal
+				await new Promise<void>((resolve) => {
+					if (signal?.aborted) {
+						resolve()
+						return
+					}
+					signal?.addEventListener("abort", () => resolve())
+				})
+				return { stats: { processed: 0, skipped: 0 }, totalBlockCount: 0 }
+			},
+		)
+
+		const orchestrator = new CodeIndexOrchestrator(
+			configManager,
+			stateManager,
+			workspacePath,
+			cacheManager,
+			vectorStore,
+			scanner,
+			fileWatcher,
+		)
+
+		// Start indexing (async, don't await)
+		const indexingPromise = orchestrator.startIndexing()
+
+		// Give it a tick to begin
+		await new Promise((resolve) => setTimeout(resolve, 10))
+
+		// Stop indexing
+		orchestrator.stopIndexing()
+
+		// Wait for indexing to complete
+		await indexingPromise
+
+		// State should be Standby (not Error)
+		const setStateCalls = stateManager.setSystemState.mock.calls
+		const lastCall = setStateCalls[setStateCalls.length - 1]
+		expect(lastCall[0]).toBe("Standby")
+	})
+
+	it("should set state to Standby after abort, not Error", async () => {
+		// Make scanner throw AbortError when signal is aborted
+		scanner.scanDirectory.mockImplementation(
+			async (_dir: string, _onError?: any, _onBlocksIndexed?: any, _onFileParsed?: any, signal?: AbortSignal) => {
+				await new Promise<void>((resolve) => {
+					if (signal?.aborted) {
+						resolve()
+						return
+					}
+					signal?.addEventListener("abort", () => resolve())
+				})
+				throw new DOMException("Indexing aborted", "AbortError")
+			},
+		)
+
+		const orchestrator = new CodeIndexOrchestrator(
+			configManager,
+			stateManager,
+			workspacePath,
+			cacheManager,
+			vectorStore,
+			scanner,
+			fileWatcher,
+		)
+
+		const indexingPromise = orchestrator.startIndexing()
+		await new Promise((resolve) => setTimeout(resolve, 10))
+
+		orchestrator.stopIndexing()
+		await indexingPromise
+
+		// Should NOT have set Error state — abort is handled gracefully
+		const errorCalls = stateManager.setSystemState.mock.calls.filter((call: any[]) => call[0] === "Error")
+		expect(errorCalls).toHaveLength(0)
+
+		// Should NOT have cleared collection on abort
+		expect(vectorStore.clearCollection).not.toHaveBeenCalled()
+	})
+
+	it("should preserve partial index data after stop", async () => {
+		scanner.scanDirectory.mockImplementation(
+			async (_dir: string, _onError?: any, _onBlocksIndexed?: any, _onFileParsed?: any, signal?: AbortSignal) => {
+				await new Promise<void>((resolve) => {
+					if (signal?.aborted) {
+						resolve()
+						return
+					}
+					signal?.addEventListener("abort", () => resolve())
+				})
+				return { stats: { processed: 5, skipped: 0 }, totalBlockCount: 5 }
+			},
+		)
+
+		const orchestrator = new CodeIndexOrchestrator(
+			configManager,
+			stateManager,
+			workspacePath,
+			cacheManager,
+			vectorStore,
+			scanner,
+			fileWatcher,
+		)
+
+		const indexingPromise = orchestrator.startIndexing()
+		await new Promise((resolve) => setTimeout(resolve, 10))
+
+		orchestrator.stopIndexing()
+		await indexingPromise
+
+		// Cache should NOT be cleared on user-initiated stop
+		expect(cacheManager.clearCacheFile).not.toHaveBeenCalled()
+		// Collection should NOT be cleared on user-initiated stop
+		expect(vectorStore.clearCollection).not.toHaveBeenCalled()
+	})
+})

+ 7 - 0
src/services/code-index/cache-manager.ts

@@ -110,6 +110,13 @@ export class CacheManager implements ICacheManager {
 		this._debouncedSaveCache()
 		this._debouncedSaveCache()
 	}
 	}
 
 
+	/**
+	 * Flushes any pending debounced cache writes to disk immediately.
+	 */
+	async flush(): Promise<void> {
+		await this._performSave()
+	}
+
 	/**
 	/**
 	 * Gets a copy of all file hashes
 	 * Gets a copy of all file hashes
 	 * @returns A copy of the file hashes record
 	 * @returns A copy of the file hashes record

+ 1 - 0
src/services/code-index/interfaces/cache.ts

@@ -2,5 +2,6 @@ export interface ICacheManager {
 	getHash(filePath: string): string | undefined
 	getHash(filePath: string): string | undefined
 	updateHash(filePath: string, hash: string): void
 	updateHash(filePath: string, hash: string): void
 	deleteHash(filePath: string): void
 	deleteHash(filePath: string): void
+	flush(): Promise<void>
 	getAllHashes(): Record<string, string>
 	getAllHashes(): Record<string, string>
 }
 }

+ 1 - 0
src/services/code-index/interfaces/file-processor.ts

@@ -37,6 +37,7 @@ export interface IDirectoryScanner {
 		onError?: (error: Error) => void,
 		onError?: (error: Error) => void,
 		onBlocksIndexed?: (indexedCount: number) => void,
 		onBlocksIndexed?: (indexedCount: number) => void,
 		onFileParsed?: (fileBlockCount: number) => void,
 		onFileParsed?: (fileBlockCount: number) => void,
+		signal?: AbortSignal,
 	): Promise<{
 	): Promise<{
 		stats: {
 		stats: {
 			processed: number
 			processed: number

+ 6 - 1
src/services/code-index/interfaces/manager.ts

@@ -39,6 +39,11 @@ export interface ICodeIndexManager {
 	 */
 	 */
 	startIndexing(): Promise<void>
 	startIndexing(): Promise<void>
 
 
+	/**
+	 * Stops any in-progress indexing operation and the file watcher
+	 */
+	stopIndexing(): void
+
 	/**
 	/**
 	 * Stops the file watcher
 	 * Stops the file watcher
 	 */
 	 */
@@ -69,7 +74,7 @@ export interface ICodeIndexManager {
 	dispose(): void
 	dispose(): void
 }
 }
 
 
-export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
+export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping"
 export type EmbedderProvider =
 export type EmbedderProvider =
 	| "openai"
 	| "openai"
 	| "ollama"
 	| "ollama"

+ 80 - 26
src/services/code-index/manager.ts

@@ -32,30 +32,47 @@ export class CodeIndexManager {
 	private _isRecoveringFromError = false
 	private _isRecoveringFromError = false
 
 
 	public static getInstance(context: vscode.ExtensionContext, workspacePath?: string): CodeIndexManager | undefined {
 	public static getInstance(context: vscode.ExtensionContext, workspacePath?: string): CodeIndexManager | undefined {
-		// If workspacePath is not provided, try to get it from the active editor or first workspace folder
-		if (!workspacePath) {
+		// Resolve the workspace folder to get both fsPath and the real URI
+		let folder: vscode.WorkspaceFolder | undefined
+
+		if (workspacePath) {
+			folder = vscode.workspace.workspaceFolders?.find((f) => f.uri.fsPath === workspacePath)
+		} else {
 			const activeEditor = vscode.window.activeTextEditor
 			const activeEditor = vscode.window.activeTextEditor
 			if (activeEditor) {
 			if (activeEditor) {
-				const workspaceFolder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri)
-				workspacePath = workspaceFolder?.uri.fsPath
+				folder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri)
 			}
 			}
-
-			if (!workspacePath) {
+			if (!folder) {
 				const workspaceFolders = vscode.workspace.workspaceFolders
 				const workspaceFolders = vscode.workspace.workspaceFolders
 				if (!workspaceFolders || workspaceFolders.length === 0) {
 				if (!workspaceFolders || workspaceFolders.length === 0) {
 					return undefined
 					return undefined
 				}
 				}
-				// Use the first workspace folder as fallback
-				workspacePath = workspaceFolders[0].uri.fsPath
+				folder = workspaceFolders[0]
 			}
 			}
+			workspacePath = folder.uri.fsPath
 		}
 		}
 
 
 		if (!CodeIndexManager.instances.has(workspacePath)) {
 		if (!CodeIndexManager.instances.has(workspacePath)) {
-			CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, context))
+			// folder may be undefined when workspacePath was provided but doesn't match
+			// any workspace folder (e.g. cwd passed from a tool). Fall back to file:// URI.
+			const folderUri =
+				folder?.uri ??
+				({
+					fsPath: workspacePath,
+					scheme: "file",
+					authority: "",
+					path: workspacePath,
+					toString: () => `file://${workspacePath}`,
+				} as unknown as vscode.Uri)
+			CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, folderUri, context))
 		}
 		}
 		return CodeIndexManager.instances.get(workspacePath)!
 		return CodeIndexManager.instances.get(workspacePath)!
 	}
 	}
 
 
+	public static getAllInstances(): CodeIndexManager[] {
+		return Array.from(CodeIndexManager.instances.values())
+	}
+
 	public static disposeAll(): void {
 	public static disposeAll(): void {
 		for (const instance of CodeIndexManager.instances.values()) {
 		for (const instance of CodeIndexManager.instances.values()) {
 			instance.dispose()
 			instance.dispose()
@@ -64,17 +81,45 @@ export class CodeIndexManager {
 	}
 	}
 
 
 	private readonly workspacePath: string
 	private readonly workspacePath: string
+	private readonly _folderUri: vscode.Uri
 	private readonly context: vscode.ExtensionContext
 	private readonly context: vscode.ExtensionContext
 
 
 	// Private constructor for singleton pattern
 	// Private constructor for singleton pattern
-	private constructor(workspacePath: string, context: vscode.ExtensionContext) {
+	private constructor(workspacePath: string, folderUri: vscode.Uri, context: vscode.ExtensionContext) {
 		this.workspacePath = workspacePath
 		this.workspacePath = workspacePath
+		this._folderUri = folderUri
 		this.context = context
 		this.context = context
 		this._stateManager = new CodeIndexStateManager()
 		this._stateManager = new CodeIndexStateManager()
 	}
 	}
 
 
 	// --- Public API ---
 	// --- Public API ---
 
 
+	/**
+	 * Returns the workspaceState key for per-folder indexing enablement,
+	 * keyed by the real workspace folder URI so local/remote schemes cannot collide.
+	 */
+	private _workspaceEnabledKey(): string {
+		return "codeIndexWorkspaceEnabled:" + this._folderUri.toString(true)
+	}
+
+	public get isWorkspaceEnabled(): boolean {
+		const explicit = this.context.workspaceState.get<boolean | undefined>(this._workspaceEnabledKey(), undefined)
+		if (explicit !== undefined) return explicit
+		return this.autoEnableDefault
+	}
+
+	public async setWorkspaceEnabled(enabled: boolean): Promise<void> {
+		await this.context.workspaceState.update(this._workspaceEnabledKey(), enabled)
+	}
+
+	public get autoEnableDefault(): boolean {
+		return this.context.globalState.get("codeIndexAutoEnableDefault", true)
+	}
+
+	public async setAutoEnableDefault(enabled: boolean): Promise<void> {
+		await this.context.globalState.update("codeIndexAutoEnableDefault", enabled)
+	}
+
 	public get onProgressUpdate() {
 	public get onProgressUpdate() {
 		return this._stateManager.onProgressUpdate
 		return this._stateManager.onProgressUpdate
 	}
 	}
@@ -138,28 +183,32 @@ export class CodeIndexManager {
 			return { requiresRestart }
 			return { requiresRestart }
 		}
 		}
 
 
-		// 4. CacheManager Initialization
+		// 4. Check workspace-level enablement (before creating expensive services)
+		if (!this.isWorkspaceEnabled) {
+			this._stateManager.setSystemState("Standby", "Indexing not enabled for this workspace")
+			return { requiresRestart }
+		}
+
+		// 5. CacheManager Initialization
 		if (!this._cacheManager) {
 		if (!this._cacheManager) {
 			this._cacheManager = new CacheManager(this.context, this.workspacePath)
 			this._cacheManager = new CacheManager(this.context, this.workspacePath)
 			await this._cacheManager.initialize()
 			await this._cacheManager.initialize()
 		}
 		}
 
 
-		// 4. Determine if Core Services Need Recreation
+		// 6. Determine if Core Services Need Recreation
 		const needsServiceRecreation = !this._serviceFactory || requiresRestart
 		const needsServiceRecreation = !this._serviceFactory || requiresRestart
 
 
 		if (needsServiceRecreation) {
 		if (needsServiceRecreation) {
 			await this._recreateServices()
 			await this._recreateServices()
 		}
 		}
 
 
-		// 5. Handle Indexing Start/Restart
-		// The enhanced vectorStore.initialize() in startIndexing() now handles dimension changes automatically
-		// by detecting incompatible collections and recreating them, so we rely on that for dimension changes
+		// 7. Handle Indexing Start/Restart
 		const shouldStartOrRestartIndexing =
 		const shouldStartOrRestartIndexing =
 			requiresRestart ||
 			requiresRestart ||
 			(needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing"))
 			(needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing"))
 
 
 		if (shouldStartOrRestartIndexing) {
 		if (shouldStartOrRestartIndexing) {
-			this._orchestrator?.startIndexing() // This method is async, but we don't await it here
+			this._orchestrator?.startIndexing()
 		}
 		}
 
 
 		return { requiresRestart }
 		return { requiresRestart }
@@ -173,7 +222,7 @@ export class CodeIndexManager {
 	 * The indexing will continue asynchronously and progress will be reported through events.
 	 * The indexing will continue asynchronously and progress will be reported through events.
 	 */
 	 */
 	public async startIndexing(): Promise<void> {
 	public async startIndexing(): Promise<void> {
-		if (!this.isFeatureEnabled) {
+		if (!this.isFeatureEnabled || !this.isWorkspaceEnabled) {
 			return
 			return
 		}
 		}
 
 
@@ -191,6 +240,15 @@ export class CodeIndexManager {
 		await this._orchestrator!.startIndexing()
 		await this._orchestrator!.startIndexing()
 	}
 	}
 
 
+	/**
+	 * Stops any in-progress indexing operation and the file watcher.
+	 */
+	public stopIndexing(): void {
+		if (this._orchestrator) {
+			this._orchestrator.stopIndexing()
+		}
+	}
+
 	/**
 	/**
 	 * Stops the file watcher and potentially cleans up resources.
 	 * Stops the file watcher and potentially cleans up resources.
 	 */
 	 */
@@ -247,9 +305,7 @@ export class CodeIndexManager {
 	 * Cleans up the manager instance.
 	 * Cleans up the manager instance.
 	 */
 	 */
 	public dispose(): void {
 	public dispose(): void {
-		if (this._orchestrator) {
-			this.stopWatcher()
-		}
+		this.stopIndexing()
 		this._stateManager.dispose()
 		this._stateManager.dispose()
 	}
 	}
 
 
@@ -273,6 +329,8 @@ export class CodeIndexManager {
 		return {
 		return {
 			...status,
 			...status,
 			workspacePath: this.workspacePath,
 			workspacePath: this.workspacePath,
+			workspaceEnabled: this.isWorkspaceEnabled,
+			autoEnableDefault: this.autoEnableDefault,
 		}
 		}
 	}
 	}
 
 
@@ -384,13 +442,9 @@ export class CodeIndexManager {
 			const isFeatureEnabled = this.isFeatureEnabled
 			const isFeatureEnabled = this.isFeatureEnabled
 			const isFeatureConfigured = this.isFeatureConfigured
 			const isFeatureConfigured = this.isFeatureConfigured
 
 
-			// If feature is disabled, stop the service
+			// If feature is disabled, stop the service (including any active scan)
 			if (!isFeatureEnabled) {
 			if (!isFeatureEnabled) {
-				// Stop the orchestrator if it exists
-				if (this._orchestrator) {
-					this._orchestrator.stopWatcher()
-				}
-				// Set state to indicate service is disabled
+				this.stopIndexing()
 				this._stateManager.setSystemState("Standby", "Code indexing is disabled")
 				this._stateManager.setSystemState("Standby", "Code indexing is disabled")
 				return
 				return
 			}
 			}

+ 42 - 1
src/services/code-index/orchestrator.ts

@@ -15,6 +15,7 @@ import { t } from "../../i18n"
 export class CodeIndexOrchestrator {
 export class CodeIndexOrchestrator {
 	private _fileWatcherSubscriptions: vscode.Disposable[] = []
 	private _fileWatcherSubscriptions: vscode.Disposable[] = []
 	private _isProcessing: boolean = false
 	private _isProcessing: boolean = false
+	private _abortController: AbortController | null = null
 
 
 	constructor(
 	constructor(
 		private readonly configManager: CodeIndexConfigManager,
 		private readonly configManager: CodeIndexConfigManager,
@@ -121,6 +122,8 @@ export class CodeIndexOrchestrator {
 		}
 		}
 
 
 		this._isProcessing = true
 		this._isProcessing = true
+		this._abortController = new AbortController()
+		const signal = this._abortController.signal
 		this.stateManager.setSystemState("Indexing", "Initializing services...")
 		this.stateManager.setSystemState("Indexing", "Initializing services...")
 
 
 		// Track whether we successfully connected to Qdrant and started indexing
 		// Track whether we successfully connected to Qdrant and started indexing
@@ -178,8 +181,16 @@ export class CodeIndexOrchestrator {
 					},
 					},
 					handleBlocksIndexed,
 					handleBlocksIndexed,
 					handleFileParsed,
 					handleFileParsed,
+					signal,
 				)
 				)
 
 
+				if (signal.aborted) {
+					await this.cacheManager.flush()
+					this.stopWatcher()
+					this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped"))
+					return
+				}
+
 				if (!result) {
 				if (!result) {
 					throw new Error("Incremental scan failed, is scanner initialized?")
 					throw new Error("Incremental scan failed, is scanner initialized?")
 				}
 				}
@@ -231,8 +242,16 @@ export class CodeIndexOrchestrator {
 					},
 					},
 					handleBlocksIndexed,
 					handleBlocksIndexed,
 					handleFileParsed,
 					handleFileParsed,
+					signal,
 				)
 				)
 
 
+				if (signal.aborted) {
+					await this.cacheManager.flush()
+					this.stopWatcher()
+					this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped"))
+					return
+				}
+
 				if (!result) {
 				if (!result) {
 					throw new Error("Scan failed, is scanner initialized?")
 					throw new Error("Scan failed, is scanner initialized?")
 				}
 				}
@@ -282,6 +301,15 @@ export class CodeIndexOrchestrator {
 				this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
 				this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
 			}
 			}
 		} catch (error: any) {
 		} catch (error: any) {
+			// Handle abort gracefully — not an error, just a user-initiated stop
+			if (error?.name === "AbortError" || signal.aborted) {
+				console.log("[CodeIndexOrchestrator] Indexing aborted by user.")
+				await this.cacheManager.flush()
+				this.stopWatcher()
+				this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped"))
+				return
+			}
+
 			console.error("[CodeIndexOrchestrator] Error during indexing:", error)
 			console.error("[CodeIndexOrchestrator] Error during indexing:", error)
 			TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
 			TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
 				error: error instanceof Error ? error.message : String(error),
 				error: error instanceof Error ? error.message : String(error),
@@ -325,7 +353,20 @@ export class CodeIndexOrchestrator {
 			this.stopWatcher()
 			this.stopWatcher()
 		} finally {
 		} finally {
 			this._isProcessing = false
 			this._isProcessing = false
+			this._abortController = null
+		}
+	}
+
+	/**
+	 * Stops any in-progress indexing by aborting the scan and stopping the file watcher.
+	 */
+	public stopIndexing(): void {
+		if (this._abortController) {
+			this.stateManager.setSystemState("Stopping", t("embeddings:orchestrator.indexingStoppedPartial"))
+			this._abortController.abort()
+			this._abortController = null
 		}
 		}
+		this.stopWatcher()
 	}
 	}
 
 
 	/**
 	/**
@@ -336,7 +377,7 @@ export class CodeIndexOrchestrator {
 		this._fileWatcherSubscriptions.forEach((sub) => sub.dispose())
 		this._fileWatcherSubscriptions.forEach((sub) => sub.dispose())
 		this._fileWatcherSubscriptions = []
 		this._fileWatcherSubscriptions = []
 
 
-		if (this.stateManager.state !== "Error") {
+		if (this.stateManager.state !== "Error" && this.stateManager.state !== "Stopping") {
 			this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.fileWatcherStopped"))
 			this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.fileWatcherStopped"))
 		}
 		}
 		this._isProcessing = false
 		this._isProcessing = false

+ 63 - 0
src/services/code-index/processors/__tests__/scanner.spec.ts

@@ -394,5 +394,68 @@ describe("DirectoryScanner", () => {
 			expect(points[1].payload.segmentHash).toBe("unique-segment-hash-2")
 			expect(points[1].payload.segmentHash).toBe("unique-segment-hash-2")
 			expect(points[2].payload.segmentHash).toBe("unique-segment-hash-3")
 			expect(points[2].payload.segmentHash).toBe("unique-segment-hash-3")
 		})
 		})
+
+		it("should stop processing files when signal is aborted", async () => {
+			const { listFiles } = await import("../../../glob/list-files")
+			vi.mocked(listFiles).mockResolvedValue([["test/file1.js", "test/file2.js", "test/file3.js"], false])
+
+			// Create an already-aborted signal
+			const controller = new AbortController()
+			controller.abort()
+
+			const result = await scanner.scanDirectory("/test", undefined, undefined, undefined, controller.signal)
+
+			// No files should have been processed since signal was already aborted
+			expect(mockCodeParser.parseFile).not.toHaveBeenCalled()
+			expect(result.stats.processed).toBe(0)
+		})
+
+		it("should stop processing batches when signal is aborted mid-scan", async () => {
+			const { listFiles } = await import("../../../glob/list-files")
+			vi.mocked(listFiles).mockResolvedValue([["test/file1.js", "test/file2.js"], false])
+
+			const controller = new AbortController()
+
+			const mockBlocks: any[] = [
+				{
+					file_path: "test/file1.js",
+					content: "function hello() {}",
+					start_line: 1,
+					end_line: 3,
+					identifier: "hello",
+					type: "function",
+					fileHash: "hash1",
+					segmentHash: "seg-hash-1",
+				},
+			]
+
+			// Abort after first file is parsed
+			;(mockCodeParser.parseFile as any).mockImplementation(async () => {
+				controller.abort()
+				return mockBlocks
+			})
+
+			// AbortError should propagate up (the orchestrator handles it in its catch block)
+			await expect(
+				scanner.scanDirectory("/test", undefined, undefined, undefined, controller.signal),
+			).rejects.toThrow("Indexing aborted")
+		})
+
+		it("should not process deleted files when signal is aborted", async () => {
+			const { listFiles } = await import("../../../glob/list-files")
+			vi.mocked(listFiles).mockResolvedValue([[], false])
+
+			// Set up cached files that would normally be detected as deleted
+			;(mockCacheManager.getAllHashes as any).mockReturnValue({ "old/file.js": "old-hash" })
+
+			// Create an already-aborted signal
+			const controller = new AbortController()
+			controller.abort()
+
+			await scanner.scanDirectory("/test", undefined, undefined, undefined, controller.signal)
+
+			// Deleted file cleanup should not have run
+			expect(mockVectorStore.deletePointsByFilePath).not.toHaveBeenCalled()
+		})
 	})
 	})
 })
 })

+ 38 - 1
src/services/code-index/processors/scanner.ts

@@ -71,6 +71,7 @@ export class DirectoryScanner implements IDirectoryScanner {
 		onError?: (error: Error) => void,
 		onError?: (error: Error) => void,
 		onBlocksIndexed?: (indexedCount: number) => void,
 		onBlocksIndexed?: (indexedCount: number) => void,
 		onFileParsed?: (fileBlockCount: number) => void,
 		onFileParsed?: (fileBlockCount: number) => void,
+		signal?: AbortSignal,
 	): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> {
 	): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> {
 		const directoryPath = directory
 		const directoryPath = directory
 		// Capture workspace context at scan start
 		// Capture workspace context at scan start
@@ -127,6 +128,9 @@ export class DirectoryScanner implements IDirectoryScanner {
 		// Process all files in parallel with concurrency control
 		// Process all files in parallel with concurrency control
 		const parsePromises = supportedPaths.map((filePath) =>
 		const parsePromises = supportedPaths.map((filePath) =>
 			parseLimiter(async () => {
 			parseLimiter(async () => {
+				// Check abort signal before processing each file
+				if (signal?.aborted) return
+
 				try {
 				try {
 					// Check file size
 					// Check file size
 					const stats = await stat(filePath)
 					const stats = await stat(filePath)
@@ -173,10 +177,17 @@ export class DirectoryScanner implements IDirectoryScanner {
 									addedBlocksFromFile = true
 									addedBlocksFromFile = true
 
 
 									// Check if batch threshold is met
 									// Check if batch threshold is met
+									// Check abort signal before dispatching batch
+									if (signal?.aborted) {
+										throw new DOMException("Indexing aborted", "AbortError")
+									}
+
 									if (currentBatchBlocks.length >= this.batchSegmentThreshold) {
 									if (currentBatchBlocks.length >= this.batchSegmentThreshold) {
 										// Wait if we've reached the maximum pending batches
 										// Wait if we've reached the maximum pending batches
 										while (pendingBatchCount >= MAX_PENDING_BATCHES) {
 										while (pendingBatchCount >= MAX_PENDING_BATCHES) {
-											// Wait for at least one batch to complete
+											if (signal?.aborted) {
+												throw new DOMException("Indexing aborted", "AbortError")
+											}
 											await Promise.race(activeBatchPromises)
 											await Promise.race(activeBatchPromises)
 										}
 										}
 
 
@@ -235,6 +246,10 @@ export class DirectoryScanner implements IDirectoryScanner {
 						await this.cacheManager.updateHash(filePath, currentFileHash)
 						await this.cacheManager.updateHash(filePath, currentFileHash)
 					}
 					}
 				} catch (error) {
 				} catch (error) {
+					// Re-throw AbortError — it's not a file processing error, just a user-initiated stop
+					if (error instanceof DOMException && error.name === "AbortError") {
+						throw error
+					}
 					console.error(`Error processing file ${filePath} in workspace ${scanWorkspace}:`, error)
 					console.error(`Error processing file ${filePath} in workspace ${scanWorkspace}:`, error)
 					TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
 					TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
 						error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
 						error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
@@ -258,6 +273,17 @@ export class DirectoryScanner implements IDirectoryScanner {
 		// Wait for all parsing to complete
 		// Wait for all parsing to complete
 		await Promise.all(parsePromises)
 		await Promise.all(parsePromises)
 
 
+		// Check abort signal before processing remaining batch
+		if (signal?.aborted) {
+			return {
+				stats: {
+					processed: processedCount,
+					skipped: skippedCount,
+				},
+				totalBlockCount,
+			}
+		}
+
 		// Process any remaining items in batch
 		// Process any remaining items in batch
 		if (currentBatchBlocks.length > 0) {
 		if (currentBatchBlocks.length > 0) {
 			const release = await mutex.acquire()
 			const release = await mutex.acquire()
@@ -292,6 +318,17 @@ export class DirectoryScanner implements IDirectoryScanner {
 		// Wait for all batch processing to complete
 		// Wait for all batch processing to complete
 		await Promise.all(activeBatchPromises)
 		await Promise.all(activeBatchPromises)
 
 
+		// Check abort signal before handling deleted files
+		if (signal?.aborted) {
+			return {
+				stats: {
+					processed: processedCount,
+					skipped: skippedCount,
+				},
+				totalBlockCount,
+			}
+		}
+
 		// Handle deleted files
 		// Handle deleted files
 		const oldHashes = this.cacheManager.getAllHashes()
 		const oldHashes = this.cacheManager.getAllHashes()
 		for (const cachedFilePath of Object.keys(oldHashes)) {
 		for (const cachedFilePath of Object.keys(oldHashes)) {

+ 5 - 1
src/services/code-index/state-manager.ts

@@ -1,6 +1,6 @@
 import * as vscode from "vscode"
 import * as vscode from "vscode"
 
 
-export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
+export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping"
 
 
 export class CodeIndexStateManager {
 export class CodeIndexStateManager {
 	private _systemStatus: IndexingState = "Standby"
 	private _systemStatus: IndexingState = "Standby"
@@ -58,6 +58,8 @@ export class CodeIndexStateManager {
 	public reportBlockIndexingProgress(processedItems: number, totalItems: number): void {
 	public reportBlockIndexingProgress(processedItems: number, totalItems: number): void {
 		const progressChanged = processedItems !== this._processedItems || totalItems !== this._totalItems
 		const progressChanged = processedItems !== this._processedItems || totalItems !== this._totalItems
 
 
+		// Don't override Stopping state with progress updates
+		if (this._systemStatus === "Stopping") return
 		// Update if progress changes OR if the system wasn't already in 'Indexing' state
 		// Update if progress changes OR if the system wasn't already in 'Indexing' state
 		if (progressChanged || this._systemStatus !== "Indexing") {
 		if (progressChanged || this._systemStatus !== "Indexing") {
 			this._processedItems = processedItems
 			this._processedItems = processedItems
@@ -81,6 +83,8 @@ export class CodeIndexStateManager {
 	public reportFileQueueProgress(processedFiles: number, totalFiles: number, currentFileBasename?: string): void {
 	public reportFileQueueProgress(processedFiles: number, totalFiles: number, currentFileBasename?: string): void {
 		const progressChanged = processedFiles !== this._processedItems || totalFiles !== this._totalItems
 		const progressChanged = processedFiles !== this._processedItems || totalFiles !== this._totalItems
 
 
+		// Don't override Stopping state with progress updates
+		if (this._systemStatus === "Stopping") return
 		if (progressChanged || this._systemStatus !== "Indexing") {
 		if (progressChanged || this._systemStatus !== "Indexing") {
 			this._processedItems = processedFiles
 			this._processedItems = processedFiles
 			this._totalItems = totalFiles
 			this._totalItems = totalFiles

+ 66 - 0
webview-ui/src/components/chat/CodeIndexPopover.tsx

@@ -1590,6 +1590,58 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 							)}
 							)}
 						</div>
 						</div>
 
 
+						{/* Auto-enable default */}
+						{currentSettings.codebaseIndexEnabled && (
+							<div className="flex items-center gap-2 pt-4 pb-1">
+								<input
+									type="checkbox"
+									id="auto-enable-default-toggle"
+									checked={indexingStatus.autoEnableDefault ?? true}
+									onChange={(e) =>
+										vscode.postMessage({
+											type: "setAutoEnableDefault",
+											bool: e.target.checked,
+										})
+									}
+									className="accent-vscode-focusBorder"
+								/>
+								<label
+									htmlFor="auto-enable-default-toggle"
+									className="text-xs text-vscode-foreground cursor-pointer">
+									{t("settings:codeIndex.autoEnableDefaultLabel")}
+								</label>
+							</div>
+						)}
+
+						{/* Workspace Toggle */}
+						{currentSettings.codebaseIndexEnabled && (
+							<div className="flex items-center gap-2 pt-1 pb-2">
+								<input
+									type="checkbox"
+									id="workspace-indexing-toggle"
+									checked={indexingStatus.workspaceEnabled ?? false}
+									onChange={(e) =>
+										vscode.postMessage({
+											type: "toggleWorkspaceIndexing",
+											bool: e.target.checked,
+										})
+									}
+									className="accent-vscode-focusBorder"
+								/>
+								<label
+									htmlFor="workspace-indexing-toggle"
+									className="text-xs text-vscode-foreground cursor-pointer">
+									{t("settings:codeIndex.workspaceToggleLabel")}
+								</label>
+							</div>
+						)}
+
+						{currentSettings.codebaseIndexEnabled && !indexingStatus.workspaceEnabled && (
+							<p className="text-xs text-vscode-descriptionForeground pb-2">
+								{t("settings:codeIndex.workspaceDisabledMessage")}
+							</p>
+						)}
+
 						{/* Action Buttons */}
 						{/* Action Buttons */}
 						<div className="flex items-center justify-between gap-2 pt-6">
 						<div className="flex items-center justify-between gap-2 pt-6">
 							<div className="flex gap-2">
 							<div className="flex gap-2">
@@ -1603,6 +1655,20 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 										</Button>
 										</Button>
 									)}
 									)}
 
 
+								{currentSettings.codebaseIndexEnabled && indexingStatus.systemStatus === "Indexing" && (
+									<Button
+										variant="destructive"
+										onClick={() => vscode.postMessage({ type: "stopIndexing" })}>
+										{t("settings:codeIndex.stopIndexingButton")}
+									</Button>
+								)}
+
+								{currentSettings.codebaseIndexEnabled && indexingStatus.systemStatus === "Stopping" && (
+									<Button variant="destructive" disabled>
+										{t("settings:codeIndex.stoppingButton")}
+									</Button>
+								)}
+
 								{currentSettings.codebaseIndexEnabled &&
 								{currentSettings.codebaseIndexEnabled &&
 									(indexingStatus.systemStatus === "Indexed" ||
 									(indexingStatus.systemStatus === "Indexed" ||
 										indexingStatus.systemStatus === "Error") && (
 										indexingStatus.systemStatus === "Error") && (

+ 3 - 0
webview-ui/src/components/chat/IndexingStatusBadge.tsx

@@ -64,6 +64,8 @@ export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ classN
 				return t("chat:indexingStatus.indexing", { percentage: progressPercentage })
 				return t("chat:indexingStatus.indexing", { percentage: progressPercentage })
 			case "Indexed":
 			case "Indexed":
 				return t("chat:indexingStatus.indexed")
 				return t("chat:indexingStatus.indexed")
+			case "Stopping":
+				return t("chat:indexingStatus.stopping")
 			case "Error":
 			case "Error":
 				return t("chat:indexingStatus.error")
 				return t("chat:indexingStatus.error")
 			default:
 			default:
@@ -76,6 +78,7 @@ export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ classN
 			Standby: "bg-vscode-descriptionForeground/60",
 			Standby: "bg-vscode-descriptionForeground/60",
 			Indexing: "bg-yellow-500 animate-pulse",
 			Indexing: "bg-yellow-500 animate-pulse",
 			Indexed: "bg-green-500",
 			Indexed: "bg-green-500",
+			Stopping: "bg-amber-500 animate-pulse",
 			Error: "bg-red-500",
 			Error: "bg-red-500",
 		}
 		}
 
 

+ 2 - 1
webview-ui/src/i18n/locales/ca/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indexant {{percentage}}%",
 		"indexing": "Indexant {{percentage}}%",
 		"indexed": "Indexat",
 		"indexed": "Indexat",
 		"error": "Error d'índex",
 		"error": "Error d'índex",
-		"status": "Estat de l'índex"
+		"status": "Estat de l'índex",
+		"stopping": "Aturant la indexació..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Versió {{version}} - Feu clic per veure les notes de llançament"
 		"ariaLabel": "Versió {{version}} - Feu clic per veure les notes de llançament"

+ 6 - 1
webview-ui/src/i18n/locales/ca/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Restablir al valor per defecte (0.4)",
 		"searchMinScoreResetTooltip": "Restablir al valor per defecte (0.4)",
 		"searchMaxResultsLabel": "Màxim de resultats de cerca",
 		"searchMaxResultsLabel": "Màxim de resultats de cerca",
 		"searchMaxResultsDescription": "Nombre màxim de resultats de cerca a retornar quan es consulta l'índex de la base de codi. Els valors més alts proporcionen més context però poden incloure resultats menys rellevants.",
 		"searchMaxResultsDescription": "Nombre màxim de resultats de cerca a retornar quan es consulta l'índex de la base de codi. Els valors més alts proporcionen més context però poden incloure resultats menys rellevants.",
-		"resetToDefault": "Restablir al valor per defecte"
+		"resetToDefault": "Restablir al valor per defecte",
+		"stopIndexingButton": "Aturar indexació",
+		"stoppingButton": "Aturant...",
+		"workspaceToggleLabel": "Activar la indexació per a aquest espai de treball",
+		"workspaceDisabledMessage": "La indexació està configurada però no habilitada per a aquest espai de treball.",
+		"autoEnableDefaultLabel": "Habilitar automàticament la indexació per a nous espais de treball"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Pots configurar una drecera global per a aquesta configuració <SettingsLink>a les preferències del teu IDE</SettingsLink>.",
 		"toggleShortcut": "Pots configurar una drecera global per a aquesta configuració <SettingsLink>a les preferències del teu IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/de/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indizierung {{percentage}}%",
 		"indexing": "Indizierung {{percentage}}%",
 		"indexed": "Indiziert",
 		"indexed": "Indiziert",
 		"error": "Index-Fehler",
 		"error": "Index-Fehler",
-		"status": "Index-Status"
+		"status": "Index-Status",
+		"stopping": "Indexierung wird gestoppt..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Version {{version}} - Klicken Sie, um die Versionshinweise anzuzeigen"
 		"ariaLabel": "Version {{version}} - Klicken Sie, um die Versionshinweise anzuzeigen"

+ 6 - 1
webview-ui/src/i18n/locales/de/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Auf Standardwert zurücksetzen (0.4)",
 		"searchMinScoreResetTooltip": "Auf Standardwert zurücksetzen (0.4)",
 		"searchMaxResultsLabel": "Maximale Suchergebnisse",
 		"searchMaxResultsLabel": "Maximale Suchergebnisse",
 		"searchMaxResultsDescription": "Maximale Anzahl von Suchergebnissen, die bei der Abfrage des Codebase-Index zurückgegeben werden. Höhere Werte bieten mehr Kontext, können aber weniger relevante Ergebnisse enthalten.",
 		"searchMaxResultsDescription": "Maximale Anzahl von Suchergebnissen, die bei der Abfrage des Codebase-Index zurückgegeben werden. Höhere Werte bieten mehr Kontext, können aber weniger relevante Ergebnisse enthalten.",
-		"resetToDefault": "Auf Standard zurücksetzen"
+		"resetToDefault": "Auf Standard zurücksetzen",
+		"stopIndexingButton": "Indexierung stoppen",
+		"stoppingButton": "Wird gestoppt...",
+		"workspaceToggleLabel": "Indexierung für diesen Arbeitsbereich aktivieren",
+		"workspaceDisabledMessage": "Indexierung ist konfiguriert, aber nicht für diesen Arbeitsbereich aktiviert.",
+		"autoEnableDefaultLabel": "Indexierung für neue Arbeitsbereiche automatisch aktivieren"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Du kannst <SettingsLink>in deinen IDE-Einstellungen</SettingsLink> einen globalen Shortcut für diese Einstellung konfigurieren.",
 		"toggleShortcut": "Du kannst <SettingsLink>in deinen IDE-Einstellungen</SettingsLink> einen globalen Shortcut für diese Einstellung konfigurieren.",

+ 2 - 1
webview-ui/src/i18n/locales/en/chat.json

@@ -424,7 +424,8 @@
 		"indexing": "Indexing {{percentage}}%",
 		"indexing": "Indexing {{percentage}}%",
 		"indexed": "Indexed",
 		"indexed": "Indexed",
 		"error": "Index error",
 		"error": "Index error",
-		"status": "Index status"
+		"status": "Index status",
+		"stopping": "Stopping indexing..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Version {{version}} - Click to view release notes"
 		"ariaLabel": "Version {{version}} - Click to view release notes"

+ 6 - 1
webview-ui/src/i18n/locales/en/settings.json

@@ -274,7 +274,12 @@
 			"baseUrlRequired": "Base URL is required",
 			"baseUrlRequired": "Base URL is required",
 			"modelDimensionMinValue": "Model dimension must be greater than 0"
 			"modelDimensionMinValue": "Model dimension must be greater than 0"
 		},
 		},
-		"optional": "optional"
+		"optional": "optional",
+		"stopIndexingButton": "Stop Indexing",
+		"stoppingButton": "Stopping...",
+		"workspaceToggleLabel": "Enable indexing for this workspace",
+		"workspaceDisabledMessage": "Indexing is configured but not enabled for this workspace.",
+		"autoEnableDefaultLabel": "Auto-enable indexing for new workspaces"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"description": "Run these actions without asking for permission. Only enable for actions you fully trust and if you understand the security risks.",
 		"description": "Run these actions without asking for permission. Only enable for actions you fully trust and if you understand the security risks.",

+ 2 - 1
webview-ui/src/i18n/locales/es/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indexando {{percentage}}%",
 		"indexing": "Indexando {{percentage}}%",
 		"indexed": "Indexado",
 		"indexed": "Indexado",
 		"error": "Error de índice",
 		"error": "Error de índice",
-		"status": "Estado del índice"
+		"status": "Estado del índice",
+		"stopping": "Deteniendo la indexación..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Versión {{version}} - Haz clic para ver las notas de la versión"
 		"ariaLabel": "Versión {{version}} - Haz clic para ver las notas de la versión"

+ 6 - 1
webview-ui/src/i18n/locales/es/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)",
 		"searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)",
 		"searchMaxResultsLabel": "Resultados máximos de búsqueda",
 		"searchMaxResultsLabel": "Resultados máximos de búsqueda",
 		"searchMaxResultsDescription": "Número máximo de resultados de búsqueda a devolver al consultar el índice de código. Valores más altos proporcionan más contexto pero pueden incluir resultados menos relevantes.",
 		"searchMaxResultsDescription": "Número máximo de resultados de búsqueda a devolver al consultar el índice de código. Valores más altos proporcionan más contexto pero pueden incluir resultados menos relevantes.",
-		"resetToDefault": "Restablecer al valor predeterminado"
+		"resetToDefault": "Restablecer al valor predeterminado",
+		"stopIndexingButton": "Detener indexación",
+		"stoppingButton": "Deteniendo...",
+		"workspaceToggleLabel": "Activar indexación para este espacio de trabajo",
+		"workspaceDisabledMessage": "La indexación está configurada pero no habilitada para este espacio de trabajo.",
+		"autoEnableDefaultLabel": "Habilitar automáticamente la indexación para nuevos espacios de trabajo"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Puedes configurar un atajo global para esta configuración <SettingsLink>en las preferencias de tu IDE</SettingsLink>.",
 		"toggleShortcut": "Puedes configurar un atajo global para esta configuración <SettingsLink>en las preferencias de tu IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/fr/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indexation {{percentage}}%",
 		"indexing": "Indexation {{percentage}}%",
 		"indexed": "Indexé",
 		"indexed": "Indexé",
 		"error": "Erreur d'index",
 		"error": "Erreur d'index",
-		"status": "Statut de l'index"
+		"status": "Statut de l'index",
+		"stopping": "Arrêt de l'indexation..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Version {{version}} - Cliquez pour voir les notes de version"
 		"ariaLabel": "Version {{version}} - Cliquez pour voir les notes de version"

+ 6 - 1
webview-ui/src/i18n/locales/fr/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Réinitialiser à la valeur par défaut (0.4)",
 		"searchMinScoreResetTooltip": "Réinitialiser à la valeur par défaut (0.4)",
 		"searchMaxResultsLabel": "Résultats de recherche maximum",
 		"searchMaxResultsLabel": "Résultats de recherche maximum",
 		"searchMaxResultsDescription": "Nombre maximum de résultats de recherche à retourner lors de l'interrogation de l'index de code. Des valeurs plus élevées fournissent plus de contexte mais peuvent inclure des résultats moins pertinents.",
 		"searchMaxResultsDescription": "Nombre maximum de résultats de recherche à retourner lors de l'interrogation de l'index de code. Des valeurs plus élevées fournissent plus de contexte mais peuvent inclure des résultats moins pertinents.",
-		"resetToDefault": "Réinitialiser par défaut"
+		"resetToDefault": "Réinitialiser par défaut",
+		"stopIndexingButton": "Arrêter l'indexation",
+		"stoppingButton": "Arrêt en cours...",
+		"workspaceToggleLabel": "Activer l'indexation pour cet espace de travail",
+		"workspaceDisabledMessage": "L'indexation est configurée mais non activée pour cet espace de travail.",
+		"autoEnableDefaultLabel": "Activer automatiquement l'indexation pour les nouveaux espaces de travail"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Vous pouvez configurer un raccourci global pour ce paramètre <SettingsLink>dans les préférences de votre IDE</SettingsLink>.",
 		"toggleShortcut": "Vous pouvez configurer un raccourci global pour ce paramètre <SettingsLink>dans les préférences de votre IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/hi/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "इंडेक्सिंग {{percentage}}%",
 		"indexing": "इंडेक्सिंग {{percentage}}%",
 		"indexed": "इंडेक्स किया गया",
 		"indexed": "इंडेक्स किया गया",
 		"error": "इंडेक्स त्रुटि",
 		"error": "इंडेक्स त्रुटि",
-		"status": "इंडेक्स स्थिति"
+		"status": "इंडेक्स स्थिति",
+		"stopping": "इंडेक्सिंग रोक रहा है..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "संस्करण {{version}} - रिलीज़ नोट्स देखने के लिए क्लिक करें"
 		"ariaLabel": "संस्करण {{version}} - रिलीज़ नोट्स देखने के लिए क्लिक करें"

+ 6 - 1
webview-ui/src/i18n/locales/hi/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "डिफ़ॉल्ट मान पर रीसेट करें (0.4)",
 		"searchMinScoreResetTooltip": "डिफ़ॉल्ट मान पर रीसेट करें (0.4)",
 		"searchMaxResultsLabel": "अधिकतम खोज परिणाम",
 		"searchMaxResultsLabel": "अधिकतम खोज परिणाम",
 		"searchMaxResultsDescription": "कोडबेस इंडेक्स को क्वेरी करते समय वापस करने के लिए खोज परिणामों की अधिकतम संख्या। उच्च मान अधिक संदर्भ प्रदान करते हैं लेकिन कम प्रासंगिक परिणाम शामिल कर सकते हैं।",
 		"searchMaxResultsDescription": "कोडबेस इंडेक्स को क्वेरी करते समय वापस करने के लिए खोज परिणामों की अधिकतम संख्या। उच्च मान अधिक संदर्भ प्रदान करते हैं लेकिन कम प्रासंगिक परिणाम शामिल कर सकते हैं।",
-		"resetToDefault": "डिफ़ॉल्ट पर रीसेट करें"
+		"resetToDefault": "डिफ़ॉल्ट पर रीसेट करें",
+		"stopIndexingButton": "इंडेक्सिंग रोकें",
+		"stoppingButton": "रोक रहा है...",
+		"workspaceToggleLabel": "इस वर्कस्पेस के लिए इंडेक्सिंग सक्षम करें",
+		"workspaceDisabledMessage": "इंडेक्सिंग कॉन्फ़िगर की गई है लेकिन इस वर्कस्पेस के लिए सक्षम नहीं है।",
+		"autoEnableDefaultLabel": "नए वर्कस्पेस के लिए स्वचालित रूप से इंडेक्सिंग सक्षम करें"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "आप <SettingsLink>अपनी आईडीई वरीयताओं में</SettingsLink> इस सेटिंग के लिए एक वैश्विक शॉर्टकट कॉन्फ़िगर कर सकते हैं।",
 		"toggleShortcut": "आप <SettingsLink>अपनी आईडीई वरीयताओं में</SettingsLink> इस सेटिंग के लिए एक वैश्विक शॉर्टकट कॉन्फ़िगर कर सकते हैं।",

+ 2 - 1
webview-ui/src/i18n/locales/id/chat.json

@@ -434,7 +434,8 @@
 		"indexing": "Mengindeks {{percentage}}%",
 		"indexing": "Mengindeks {{percentage}}%",
 		"indexed": "Terindeks",
 		"indexed": "Terindeks",
 		"error": "Error indeks",
 		"error": "Error indeks",
-		"status": "Status indeks"
+		"status": "Status indeks",
+		"stopping": "Menghentikan pengindeksan..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Versi {{version}} - Klik untuk melihat catatan rilis"
 		"ariaLabel": "Versi {{version}} - Klik untuk melihat catatan rilis"

+ 6 - 1
webview-ui/src/i18n/locales/id/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Reset ke nilai default (0.4)",
 		"searchMinScoreResetTooltip": "Reset ke nilai default (0.4)",
 		"searchMaxResultsLabel": "Hasil Pencarian Maksimum",
 		"searchMaxResultsLabel": "Hasil Pencarian Maksimum",
 		"searchMaxResultsDescription": "Jumlah maksimum hasil pencarian yang dikembalikan saat melakukan query indeks basis kode. Nilai yang lebih tinggi memberikan lebih banyak konteks tetapi mungkin menyertakan hasil yang kurang relevan.",
 		"searchMaxResultsDescription": "Jumlah maksimum hasil pencarian yang dikembalikan saat melakukan query indeks basis kode. Nilai yang lebih tinggi memberikan lebih banyak konteks tetapi mungkin menyertakan hasil yang kurang relevan.",
-		"resetToDefault": "Reset ke default"
+		"resetToDefault": "Reset ke default",
+		"stopIndexingButton": "Hentikan pengindeksan",
+		"stoppingButton": "Menghentikan...",
+		"workspaceToggleLabel": "Aktifkan pengindeksan untuk ruang kerja ini",
+		"workspaceDisabledMessage": "Pengindeksan dikonfigurasi tetapi tidak diaktifkan untuk ruang kerja ini.",
+		"autoEnableDefaultLabel": "Aktifkan pengindeksan secara otomatis untuk ruang kerja baru"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Anda dapat mengonfigurasi pintasan global untuk pengaturan ini <SettingsLink>di preferensi IDE Anda</SettingsLink>.",
 		"toggleShortcut": "Anda dapat mengonfigurasi pintasan global untuk pengaturan ini <SettingsLink>di preferensi IDE Anda</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/it/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indicizzazione {{percentage}}%",
 		"indexing": "Indicizzazione {{percentage}}%",
 		"indexed": "Indicizzato",
 		"indexed": "Indicizzato",
 		"error": "Errore indice",
 		"error": "Errore indice",
-		"status": "Stato indice"
+		"status": "Stato indice",
+		"stopping": "Interruzione dell'indicizzazione..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Versione {{version}} - Clicca per visualizzare le note di rilascio"
 		"ariaLabel": "Versione {{version}} - Clicca per visualizzare le note di rilascio"

+ 6 - 1
webview-ui/src/i18n/locales/it/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Ripristina al valore predefinito (0.4)",
 		"searchMinScoreResetTooltip": "Ripristina al valore predefinito (0.4)",
 		"searchMaxResultsLabel": "Risultati di ricerca massimi",
 		"searchMaxResultsLabel": "Risultati di ricerca massimi",
 		"searchMaxResultsDescription": "Numero massimo di risultati di ricerca da restituire quando si interroga l'indice del codice. Valori più alti forniscono più contesto ma possono includere risultati meno pertinenti.",
 		"searchMaxResultsDescription": "Numero massimo di risultati di ricerca da restituire quando si interroga l'indice del codice. Valori più alti forniscono più contesto ma possono includere risultati meno pertinenti.",
-		"resetToDefault": "Ripristina al valore predefinito"
+		"resetToDefault": "Ripristina al valore predefinito",
+		"stopIndexingButton": "Interrompi indicizzazione",
+		"stoppingButton": "Interruzione...",
+		"workspaceToggleLabel": "Abilita l'indicizzazione per questo workspace",
+		"workspaceDisabledMessage": "L'indicizzazione è configurata ma non abilitata per questo workspace.",
+		"autoEnableDefaultLabel": "Abilita automaticamente l'indicizzazione per i nuovi workspace"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Puoi configurare una scorciatoia globale per questa impostazione <SettingsLink>nelle preferenze del tuo IDE</SettingsLink>.",
 		"toggleShortcut": "Puoi configurare una scorciatoia globale per questa impostazione <SettingsLink>nelle preferenze del tuo IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/ja/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "インデックス作成中 {{percentage}}%",
 		"indexing": "インデックス作成中 {{percentage}}%",
 		"indexed": "インデックス作成済み",
 		"indexed": "インデックス作成済み",
 		"error": "インデックスエラー",
 		"error": "インデックスエラー",
-		"status": "インデックス状態"
+		"status": "インデックス状態",
+		"stopping": "インデックス作成を停止中..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "バージョン {{version}} - クリックしてリリースノートを表示"
 		"ariaLabel": "バージョン {{version}} - クリックしてリリースノートを表示"

+ 6 - 1
webview-ui/src/i18n/locales/ja/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "デフォルト値(0.4)にリセット",
 		"searchMinScoreResetTooltip": "デフォルト値(0.4)にリセット",
 		"searchMaxResultsLabel": "最大検索結果数",
 		"searchMaxResultsLabel": "最大検索結果数",
 		"searchMaxResultsDescription": "コードベースインデックスをクエリする際に返される検索結果の最大数。値を高くするとより多くのコンテキストが提供されますが、関連性の低い結果が含まれる可能性があります。",
 		"searchMaxResultsDescription": "コードベースインデックスをクエリする際に返される検索結果の最大数。値を高くするとより多くのコンテキストが提供されますが、関連性の低い結果が含まれる可能性があります。",
-		"resetToDefault": "デフォルトにリセット"
+		"resetToDefault": "デフォルトにリセット",
+		"stopIndexingButton": "インデックス作成を停止",
+		"stoppingButton": "停止中...",
+		"workspaceToggleLabel": "このワークスペースのインデックス作成を有効にする",
+		"workspaceDisabledMessage": "インデックス作成は設定済みですが、このワークスペースでは有効になっていません。",
+		"autoEnableDefaultLabel": "新しいワークスペースのインデックス作成を自動的に有効にする"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "<SettingsLink>IDEの環境設定</SettingsLink>で、この設定のグローバルショートカットを設定できます。",
 		"toggleShortcut": "<SettingsLink>IDEの環境設定</SettingsLink>で、この設定のグローバルショートカットを設定できます。",

+ 2 - 1
webview-ui/src/i18n/locales/ko/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "인덱싱 중 {{percentage}}%",
 		"indexing": "인덱싱 중 {{percentage}}%",
 		"indexed": "인덱싱 완료",
 		"indexed": "인덱싱 완료",
 		"error": "인덱스 오류",
 		"error": "인덱스 오류",
-		"status": "인덱스 상태"
+		"status": "인덱스 상태",
+		"stopping": "인덱싱 중지 중..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "버전 {{version}} - 릴리스 노트를 보려면 클릭하세요"
 		"ariaLabel": "버전 {{version}} - 릴리스 노트를 보려면 클릭하세요"

+ 6 - 1
webview-ui/src/i18n/locales/ko/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "기본값(0.4)으로 재설정",
 		"searchMinScoreResetTooltip": "기본값(0.4)으로 재설정",
 		"searchMaxResultsLabel": "최대 검색 결과",
 		"searchMaxResultsLabel": "최대 검색 결과",
 		"searchMaxResultsDescription": "코드베이스 인덱스를 쿼리할 때 반환할 최대 검색 결과 수입니다. 값이 높을수록 더 많은 컨텍스트를 제공하지만 관련성이 낮은 결과가 포함될 수 있습니다.",
 		"searchMaxResultsDescription": "코드베이스 인덱스를 쿼리할 때 반환할 최대 검색 결과 수입니다. 값이 높을수록 더 많은 컨텍스트를 제공하지만 관련성이 낮은 결과가 포함될 수 있습니다.",
-		"resetToDefault": "기본값으로 재설정"
+		"resetToDefault": "기본값으로 재설정",
+		"stopIndexingButton": "인덱싱 중지",
+		"stoppingButton": "중지 중...",
+		"workspaceToggleLabel": "이 워크스페이스에 대한 인덱싱 활성화",
+		"workspaceDisabledMessage": "인덱싱이 구성되었지만 이 워크스페이스에서는 활성화되지 않았습니다.",
+		"autoEnableDefaultLabel": "새 워크스페이스에 대한 인덱싱 자동 활성화"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "<SettingsLink>IDE 환경 설정</SettingsLink>에서 이 설정에 대한 전역 바로 가기를 구성할 수 있습니다.",
 		"toggleShortcut": "<SettingsLink>IDE 환경 설정</SettingsLink>에서 이 설정에 대한 전역 바로 가기를 구성할 수 있습니다.",

+ 2 - 1
webview-ui/src/i18n/locales/nl/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indexeren {{percentage}}%",
 		"indexing": "Indexeren {{percentage}}%",
 		"indexed": "Geïndexeerd",
 		"indexed": "Geïndexeerd",
 		"error": "Index fout",
 		"error": "Index fout",
-		"status": "Index status"
+		"status": "Index status",
+		"stopping": "Indexering wordt gestopt..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Versie {{version}} - Klik om release notes te bekijken"
 		"ariaLabel": "Versie {{version}} - Klik om release notes te bekijken"

+ 6 - 1
webview-ui/src/i18n/locales/nl/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)",
 		"searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)",
 		"searchMaxResultsLabel": "Maximum Zoekresultaten",
 		"searchMaxResultsLabel": "Maximum Zoekresultaten",
 		"searchMaxResultsDescription": "Maximum aantal zoekresultaten dat wordt geretourneerd bij het doorzoeken van de codebase-index. Hogere waarden bieden meer context maar kunnen minder relevante resultaten bevatten.",
 		"searchMaxResultsDescription": "Maximum aantal zoekresultaten dat wordt geretourneerd bij het doorzoeken van de codebase-index. Hogere waarden bieden meer context maar kunnen minder relevante resultaten bevatten.",
-		"resetToDefault": "Reset naar standaard"
+		"resetToDefault": "Reset naar standaard",
+		"stopIndexingButton": "Indexering stoppen",
+		"stoppingButton": "Stoppen...",
+		"workspaceToggleLabel": "Indexering inschakelen voor deze werkruimte",
+		"workspaceDisabledMessage": "Indexering is geconfigureerd maar niet ingeschakeld voor deze werkruimte.",
+		"autoEnableDefaultLabel": "Indexering automatisch inschakelen voor nieuwe werkruimtes"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "U kunt een globale sneltoets voor deze instelling configureren <SettingsLink>in de voorkeuren van uw IDE</SettingsLink>.",
 		"toggleShortcut": "U kunt een globale sneltoets voor deze instelling configureren <SettingsLink>in de voorkeuren van uw IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/pl/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indeksowanie {{percentage}}%",
 		"indexing": "Indeksowanie {{percentage}}%",
 		"indexed": "Zaindeksowane",
 		"indexed": "Zaindeksowane",
 		"error": "Błąd indeksu",
 		"error": "Błąd indeksu",
-		"status": "Status indeksu"
+		"status": "Status indeksu",
+		"stopping": "Zatrzymywanie indeksowania..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Wersja {{version}} - Kliknij, aby wyświetlić informacje o wydaniu"
 		"ariaLabel": "Wersja {{version}} - Kliknij, aby wyświetlić informacje o wydaniu"

+ 6 - 1
webview-ui/src/i18n/locales/pl/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)",
 		"searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)",
 		"searchMaxResultsLabel": "Maksymalna liczba wyników wyszukiwania",
 		"searchMaxResultsLabel": "Maksymalna liczba wyników wyszukiwania",
 		"searchMaxResultsDescription": "Maksymalna liczba wyników wyszukiwania zwracanych podczas zapytania do indeksu bazy kodu. Wyższe wartości zapewniają więcej kontekstu, ale mogą zawierać mniej istotne wyniki.",
 		"searchMaxResultsDescription": "Maksymalna liczba wyników wyszukiwania zwracanych podczas zapytania do indeksu bazy kodu. Wyższe wartości zapewniają więcej kontekstu, ale mogą zawierać mniej istotne wyniki.",
-		"resetToDefault": "Przywróć domyślne"
+		"resetToDefault": "Przywróć domyślne",
+		"stopIndexingButton": "Zatrzymaj indeksowanie",
+		"stoppingButton": "Zatrzymywanie...",
+		"workspaceToggleLabel": "Włącz indeksowanie dla tego workspace'a",
+		"workspaceDisabledMessage": "Indeksowanie jest skonfigurowane, ale nie włączone dla tego workspace'a.",
+		"autoEnableDefaultLabel": "Automatycznie włączaj indeksowanie dla nowych workspace'ów"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Możesz skonfigurować globalny skrót dla tego ustawienia <SettingsLink>w preferencjach swojego IDE</SettingsLink>.",
 		"toggleShortcut": "Możesz skonfigurować globalny skrót dla tego ustawienia <SettingsLink>w preferencjach swojego IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -428,7 +428,8 @@
 		"indexing": "Indexando {{percentage}}%",
 		"indexing": "Indexando {{percentage}}%",
 		"indexed": "Indexado",
 		"indexed": "Indexado",
 		"error": "Erro do índice",
 		"error": "Erro do índice",
-		"status": "Status do índice"
+		"status": "Status do índice",
+		"stopping": "Parando a indexação..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Versão {{version}} - Clique para ver as notas de lançamento"
 		"ariaLabel": "Versão {{version}} - Clique para ver as notas de lançamento"

+ 6 - 1
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Redefinir para o valor padrão (0.4)",
 		"searchMinScoreResetTooltip": "Redefinir para o valor padrão (0.4)",
 		"searchMaxResultsLabel": "Resultados máximos de busca",
 		"searchMaxResultsLabel": "Resultados máximos de busca",
 		"searchMaxResultsDescription": "Número máximo de resultados de busca a retornar ao consultar o índice de código. Valores mais altos fornecem mais contexto, mas podem incluir resultados menos relevantes.",
 		"searchMaxResultsDescription": "Número máximo de resultados de busca a retornar ao consultar o índice de código. Valores mais altos fornecem mais contexto, mas podem incluir resultados menos relevantes.",
-		"resetToDefault": "Redefinir para o padrão"
+		"resetToDefault": "Redefinir para o padrão",
+		"stopIndexingButton": "Parar indexação",
+		"stoppingButton": "Parando...",
+		"workspaceToggleLabel": "Ativar indexação para este workspace",
+		"workspaceDisabledMessage": "A indexação está configurada, mas não ativada para este workspace.",
+		"autoEnableDefaultLabel": "Ativar indexação automaticamente para novos workspaces"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Você pode configurar um atalho global para esta configuração <SettingsLink>nas preferências do seu IDE</SettingsLink>.",
 		"toggleShortcut": "Você pode configurar um atalho global para esta configuração <SettingsLink>nas preferências do seu IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/ru/chat.json

@@ -429,7 +429,8 @@
 		"indexing": "Индексация {{percentage}}%",
 		"indexing": "Индексация {{percentage}}%",
 		"indexed": "Проиндексировано",
 		"indexed": "Проиндексировано",
 		"error": "Ошибка индекса",
 		"error": "Ошибка индекса",
-		"status": "Статус индекса"
+		"status": "Статус индекса",
+		"stopping": "Остановка индексации..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Версия {{version}} - Нажмите, чтобы просмотреть примечания к выпуску"
 		"ariaLabel": "Версия {{version}} - Нажмите, чтобы просмотреть примечания к выпуску"

+ 6 - 1
webview-ui/src/i18n/locales/ru/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Сбросить к значению по умолчанию (0.4)",
 		"searchMinScoreResetTooltip": "Сбросить к значению по умолчанию (0.4)",
 		"searchMaxResultsLabel": "Максимальное количество результатов поиска",
 		"searchMaxResultsLabel": "Максимальное количество результатов поиска",
 		"searchMaxResultsDescription": "Максимальное количество результатов поиска, возвращаемых при запросе индекса кодовой базы. Более высокие значения предоставляют больше контекста, но могут включать менее релевантные результаты.",
 		"searchMaxResultsDescription": "Максимальное количество результатов поиска, возвращаемых при запросе индекса кодовой базы. Более высокие значения предоставляют больше контекста, но могут включать менее релевантные результаты.",
-		"resetToDefault": "Сбросить к значению по умолчанию"
+		"resetToDefault": "Сбросить к значению по умолчанию",
+		"stopIndexingButton": "Остановить индексацию",
+		"stoppingButton": "Остановка...",
+		"workspaceToggleLabel": "Включить индексацию для этого рабочего пространства",
+		"workspaceDisabledMessage": "Индексация настроена, но не включена для этого рабочего пространства.",
+		"autoEnableDefaultLabel": "Автоматически включать индексацию для новых рабочих пространств"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Вы можете настроить глобальное сочетание клавиш для этого параметра <SettingsLink>в настройках вашей IDE</SettingsLink>.",
 		"toggleShortcut": "Вы можете настроить глобальное сочетание клавиш для этого параметра <SettingsLink>в настройках вашей IDE</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/tr/chat.json

@@ -429,7 +429,8 @@
 		"indexing": "İndeksleniyor {{percentage}}%",
 		"indexing": "İndeksleniyor {{percentage}}%",
 		"indexed": "İndekslendi",
 		"indexed": "İndekslendi",
 		"error": "İndeks hatası",
 		"error": "İndeks hatası",
-		"status": "İndeks durumu"
+		"status": "İndeks durumu",
+		"stopping": "İndeksleme durduruluyor..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Sürüm {{version}} - Sürüm notlarını görüntülemek için tıklayın"
 		"ariaLabel": "Sürüm {{version}} - Sürüm notlarını görüntülemek için tıklayın"

+ 6 - 1
webview-ui/src/i18n/locales/tr/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Varsayılan değere sıfırla (0.4)",
 		"searchMinScoreResetTooltip": "Varsayılan değere sıfırla (0.4)",
 		"searchMaxResultsLabel": "Maksimum Arama Sonuçları",
 		"searchMaxResultsLabel": "Maksimum Arama Sonuçları",
 		"searchMaxResultsDescription": "Kod tabanı dizinini sorgularken döndürülecek maksimum arama sonucu sayısı. Daha yüksek değerler daha fazla bağlam sağlar ancak daha az alakalı sonuçlar içerebilir.",
 		"searchMaxResultsDescription": "Kod tabanı dizinini sorgularken döndürülecek maksimum arama sonucu sayısı. Daha yüksek değerler daha fazla bağlam sağlar ancak daha az alakalı sonuçlar içerebilir.",
-		"resetToDefault": "Varsayılana sıfırla"
+		"resetToDefault": "Varsayılana sıfırla",
+		"stopIndexingButton": "İndekslemeyi durdur",
+		"stoppingButton": "Durduruluyor...",
+		"workspaceToggleLabel": "Bu çalışma alanı için indekslemeyi etkinleştir",
+		"workspaceDisabledMessage": "İndeksleme yapılandırıldı ancak bu çalışma alanı için etkinleştirilmedi.",
+		"autoEnableDefaultLabel": "Yeni çalışma alanları için indekslemeyi otomatik etkinleştir"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "<SettingsLink>IDE tercihlerinizde</SettingsLink> bu ayar için genel bir kısayol yapılandırabilirsiniz.",
 		"toggleShortcut": "<SettingsLink>IDE tercihlerinizde</SettingsLink> bu ayar için genel bir kısayol yapılandırabilirsiniz.",

+ 2 - 1
webview-ui/src/i18n/locales/vi/chat.json

@@ -429,7 +429,8 @@
 		"indexing": "Đang lập chỉ mục {{percentage}}%",
 		"indexing": "Đang lập chỉ mục {{percentage}}%",
 		"indexed": "Đã lập chỉ mục",
 		"indexed": "Đã lập chỉ mục",
 		"error": "Lỗi chỉ mục",
 		"error": "Lỗi chỉ mục",
-		"status": "Trạng thái chỉ mục"
+		"status": "Trạng thái chỉ mục",
+		"stopping": "Đang dừng lập chỉ mục..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "Phiên bản {{version}} - Nhấp để xem ghi chú phát hành"
 		"ariaLabel": "Phiên bản {{version}} - Nhấp để xem ghi chú phát hành"

+ 6 - 1
webview-ui/src/i18n/locales/vi/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)",
 		"searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)",
 		"searchMaxResultsLabel": "Số Kết Quả Tìm Kiếm Tối Đa",
 		"searchMaxResultsLabel": "Số Kết Quả Tìm Kiếm Tối Đa",
 		"searchMaxResultsDescription": "Số lượng kết quả tìm kiếm tối đa được trả về khi truy vấn chỉ mục cơ sở mã. Giá trị cao hơn cung cấp nhiều ngữ cảnh hơn nhưng có thể bao gồm các kết quả ít liên quan hơn.",
 		"searchMaxResultsDescription": "Số lượng kết quả tìm kiếm tối đa được trả về khi truy vấn chỉ mục cơ sở mã. Giá trị cao hơn cung cấp nhiều ngữ cảnh hơn nhưng có thể bao gồm các kết quả ít liên quan hơn.",
-		"resetToDefault": "Đặt lại về mặc định"
+		"resetToDefault": "Đặt lại về mặc định",
+		"stopIndexingButton": "Dừng lập chỉ mục",
+		"stoppingButton": "Đang dừng...",
+		"workspaceToggleLabel": "Bật lập chỉ mục cho không gian làm việc này",
+		"workspaceDisabledMessage": "Lập chỉ mục đã được cấu hình nhưng chưa được bật cho không gian làm việc này.",
+		"autoEnableDefaultLabel": "Tự động bật lập chỉ mục cho không gian làm việc mới"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "Bạn có thể định cấu hình một phím tắt chung cho cài đặt này <SettingsLink>trong tùy chọn IDE của bạn</SettingsLink>.",
 		"toggleShortcut": "Bạn có thể định cấu hình một phím tắt chung cho cài đặt này <SettingsLink>trong tùy chọn IDE của bạn</SettingsLink>.",

+ 2 - 1
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -429,7 +429,8 @@
 		"indexing": "索引中 {{percentage}}%",
 		"indexing": "索引中 {{percentage}}%",
 		"indexed": "已索引",
 		"indexed": "已索引",
 		"error": "索引错误",
 		"error": "索引错误",
-		"status": "索引状态"
+		"status": "索引状态",
+		"stopping": "正在停止索引..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "版本 {{version}} - 点击查看发布说明"
 		"ariaLabel": "版本 {{version}} - 点击查看发布说明"

+ 6 - 1
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -211,7 +211,12 @@
 		"searchMinScoreResetTooltip": "恢复默认值 (0.4)",
 		"searchMinScoreResetTooltip": "恢复默认值 (0.4)",
 		"searchMaxResultsLabel": "最大搜索结果数",
 		"searchMaxResultsLabel": "最大搜索结果数",
 		"searchMaxResultsDescription": "查询代码库索引时返回的最大搜索结果数。较高的值提供更多上下文,但可能包含相关性较低的结果。",
 		"searchMaxResultsDescription": "查询代码库索引时返回的最大搜索结果数。较高的值提供更多上下文,但可能包含相关性较低的结果。",
-		"resetToDefault": "恢复默认值"
+		"resetToDefault": "恢复默认值",
+		"stopIndexingButton": "停止索引",
+		"stoppingButton": "正在停止...",
+		"workspaceToggleLabel": "为此工作区启用索引",
+		"workspaceDisabledMessage": "索引已配置,但尚未为此工作区启用。",
+		"autoEnableDefaultLabel": "自动为新工作区启用索引"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"toggleShortcut": "您可以<SettingsLink>在 IDE 首选项中</SettingsLink>为此设置配置全局快捷方式。",
 		"toggleShortcut": "您可以<SettingsLink>在 IDE 首选项中</SettingsLink>为此设置配置全局快捷方式。",

+ 2 - 1
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -427,7 +427,8 @@
 		"indexing": "索引中 {{percentage}}%",
 		"indexing": "索引中 {{percentage}}%",
 		"indexed": "已索引",
 		"indexed": "已索引",
 		"error": "索引錯誤",
 		"error": "索引錯誤",
-		"status": "索引狀態"
+		"status": "索引狀態",
+		"stopping": "正在停止索引..."
 	},
 	},
 	"versionIndicator": {
 	"versionIndicator": {
 		"ariaLabel": "版本 {{version}} - 點選查看發布說明"
 		"ariaLabel": "版本 {{version}} - 點選查看發布說明"

+ 6 - 1
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -221,7 +221,12 @@
 			"baseUrlRequired": "需要基礎 URL",
 			"baseUrlRequired": "需要基礎 URL",
 			"modelDimensionMinValue": "模型維度必須大於 0"
 			"modelDimensionMinValue": "模型維度必須大於 0"
 		},
 		},
-		"optional": "選用"
+		"optional": "選用",
+		"stopIndexingButton": "停止索引",
+		"stoppingButton": "正在停止...",
+		"workspaceToggleLabel": "為此工作區啟用索引",
+		"workspaceDisabledMessage": "索引已設定,但尚未為此工作區啟用。",
+		"autoEnableDefaultLabel": "自動為新工作區啟用索引"
 	},
 	},
 	"autoApprove": {
 	"autoApprove": {
 		"description": "無需詢問許可即可執行下列動作。請僅在您完全信任且了解安全風險的情況下啟用此功能。",
 		"description": "無需詢問許可即可執行下列動作。請僅在您完全信任且了解安全風險的情況下啟用此功能。",