Просмотр исходного кода

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 2 дней назад
Родитель
Сommit
67ea856fd0
70 измененных файлов с 983 добавлено и 101 удалено
  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"
 		| "requestIndexingStatus"
 		| "startIndexing"
+		| "stopIndexing"
 		| "clearIndexData"
 		| "indexingStatusUpdate"
 		| "indexCleared"
+		| "toggleWorkspaceIndexing"
+		| "setAutoEnableDefault"
 		| "focusPanelRequest"
 		| "openExternal"
 		| "filterMarketplaceItems"
@@ -705,7 +708,7 @@ export const checkoutRestorePayloadSchema = z.object({
 export type CheckpointRestorePayload = z.infer<typeof checkoutRestorePayloadSchema>
 
 export interface IndexingStatusPayload {
-	state: "Standby" | "Indexing" | "Indexed" | "Error"
+	state: "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping"
 	message: string
 }
 
@@ -739,6 +742,8 @@ export interface IndexingStatus {
 	totalItems: number
 	currentItemUnit?: string
 	workspacePath?: string
+	workspaceEnabled?: boolean
+	autoEnableDefault?: boolean
 }
 
 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 { MessageEnhancer } from "./messageEnhancer"
 
+import { CodeIndexManager } from "../../services/code-index/manager"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
 import { experimentDefault } from "../../shared/experiments"
 import { Terminal } from "../../integrations/terminal/Terminal"
@@ -2608,7 +2609,6 @@ export const webviewMessageHandler = async (
 			try {
 				const manager = provider.getCurrentWorkspaceCodeIndexManager()
 				if (!manager) {
-					// No workspace open - send error status
 					provider.postMessageToWebview({
 						type: "indexingStatusUpdate",
 						values: {
@@ -2622,23 +2622,19 @@ export const webviewMessageHandler = async (
 					provider.log("Cannot start indexing: No workspace folder open")
 					return
 				}
+
+				// "Start Indexing" implicitly enables the workspace
+				await manager.setWorkspaceEnabled(true)
+
 				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)
 
-					// 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
 					if (currentState === "Standby" || currentState === "Error") {
-						// startIndexing now handles error recovery internally
 						manager.startIndexing()
 
-						// If startIndexing recovered from error, we need to reinitialize
 						if (!manager.isInitialized) {
 							await manager.initialize(provider.contextProxy)
-							// Try starting again after initialization
 							if (manager.state === "Standby" || manager.state === "Error") {
 								manager.startIndexing()
 							}
@@ -2650,6 +2646,82 @@ export const webviewMessageHandler = async (
 			}
 			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": {
 			try {
 				const manager = provider.getCurrentWorkspaceCodeIndexManager()

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

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Monitor de fitxers aturat.",
 		"failedDuringInitialScan": "Ha fallat durant l'escaneig inicial: {{errorMessage}}",
 		"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.",
 		"failedDuringInitialScan": "Fehler während des ersten Scans: {{errorMessage}}",
 		"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.",
 		"failedDuringInitialScan": "Failed during initial scan: {{errorMessage}}",
 		"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.",
 		"failedDuringInitialScan": "Falló durante el escaneo inicial: {{errorMessage}}",
 		"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é.",
 		"failedDuringInitialScan": "Échec lors du scan initial : {{errorMessage}}",
 		"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": "फाइल वॉचर रुक गया।",
 		"failedDuringInitialScan": "प्रारंभिक स्कैन के दौरान असफल: {{errorMessage}}",
 		"unknownError": "अज्ञात त्रुटि",
-		"indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है"
+		"indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है",
+		"indexingStopped": "उपयोगकर्ता द्वारा इंडेक्सिंग रोकी गई।",
+		"indexingStoppedPartial": "इंडेक्सिंग रोकी गई। आंशिक इंडेक्स डेटा संरक्षित।"
 	}
 }

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

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Pemantau file dihentikan.",
 		"failedDuringInitialScan": "Gagal selama pemindaian awal: {{errorMessage}}",
 		"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.",
 		"failedDuringInitialScan": "Fallito durante la scansione iniziale: {{errorMessage}}",
 		"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": "ファイルウォッチャーが停止されました。",
 		"failedDuringInitialScan": "初期スキャン中に失敗しました:{{errorMessage}}",
 		"unknownError": "不明なエラー",
-		"indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です"
+		"indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です",
+		"indexingStopped": "ユーザーによりインデックス作成が停止されました。",
+		"indexingStoppedPartial": "インデックス作成が停止されました。部分的なインデックスデータは保持されています。"
 	}
 }

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

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

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

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Bestandsmonitor gestopt.",
 		"failedDuringInitialScan": "Mislukt tijdens initiële scan: {{errorMessage}}",
 		"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.",
 		"failedDuringInitialScan": "Niepowodzenie podczas początkowego skanowania: {{errorMessage}}",
 		"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.",
 		"failedDuringInitialScan": "Falhou durante a varredura inicial: {{errorMessage}}",
 		"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": "Наблюдатель файлов остановлен.",
 		"failedDuringInitialScan": "Ошибка во время первоначального сканирования: {{errorMessage}}",
 		"unknownError": "Неизвестная ошибка",
-		"indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства"
+		"indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства",
+		"indexingStopped": "Индексация остановлена пользователем.",
+		"indexingStoppedPartial": "Индексация остановлена. Частичные данные индекса сохранены."
 	}
 }

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

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "Dosya izleyici durduruldu.",
 		"failedDuringInitialScan": "İlk tarama sırasında başarısız: {{errorMessage}}",
 		"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.",
 		"failedDuringInitialScan": "Thất bại trong quá trình quét ban đầu: {{errorMessage}}",
 		"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": "文件监控已停止。",
 		"failedDuringInitialScan": "初始扫描失败:{{errorMessage}}",
 		"unknownError": "未知错误",
-		"indexingRequiresWorkspace": "索引需要打开的工作区文件夹"
+		"indexingRequiresWorkspace": "索引需要打开的工作区文件夹",
+		"indexingStopped": "用户已停止索引。",
+		"indexingStoppedPartial": "索引已停止。部分索引数据已保留。"
 	}
 }

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

@@ -69,6 +69,8 @@
 		"fileWatcherStopped": "檔案監控已停止。",
 		"failedDuringInitialScan": "初始掃描失敗:{{errorMessage}}",
 		"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 * 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
 vi.mock("vscode", () => {
 	const testPath = require("path")
 	const testWorkspacePath = testPath.join(testPath.sep, "test", "workspace")
 	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: {
 			activeTextEditor: null,
 		},
 		workspace: {
 			workspaceFolders: [
 				{
-					uri: { fsPath: testWorkspacePath },
+					uri: {
+						fsPath: testWorkspacePath,
+						scheme: "file",
+						authority: "",
+						path: testWorkspacePath,
+						toString: (_skipEncoding?: boolean) => `file://${testWorkspacePath}`,
+					},
 					name: "test",
 					index: 0,
 				},
@@ -25,8 +52,9 @@ vi.mock("vscode", () => {
 				onDidDelete: vi.fn().mockReturnValue({ 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
 		CodeIndexManager.disposeAll()
 
+		const workspaceStateStore: Record<string, any> = {}
+		const globalStateStore: Record<string, any> = {}
 		mockContext = {
 			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,
 			extensionPath: testExtensionPath,
 			asAbsolutePath: vi.fn(),
@@ -222,7 +262,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			;(manager as any)._cacheManager = mockCacheManager
 
 			// 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 = {}
 
 			// Verify manager is considered initialized
@@ -456,7 +496,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			})
 
 			// 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)._serviceFactory = {}
 		})
@@ -540,6 +580,9 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 				}),
 			}
 
+			// Enable workspace indexing before re-initialization
+			await manager.setWorkspaceEnabled(true)
+
 			// Re-initialize
 			await manager.initialize(mockContextProxy as any)
 
@@ -583,7 +626,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			// Setup manager with service instances
 			;(manager as any)._configManager = mockConfigManager
 			;(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 = {}
 
 			// Spy on console.error
@@ -608,4 +651,155 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
 			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 = {
 			clearCacheFile: vi.fn().mockResolvedValue(undefined),
+			flush: vi.fn().mockResolvedValue(undefined),
 		}
 
 		vectorStore = {
@@ -158,3 +159,178 @@ describe("CodeIndexOrchestrator - error path cleanup gating", () => {
 		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()
 	}
 
+	/**
+	 * Flushes any pending debounced cache writes to disk immediately.
+	 */
+	async flush(): Promise<void> {
+		await this._performSave()
+	}
+
 	/**
 	 * Gets a copy of all file hashes
 	 * @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
 	updateHash(filePath: string, hash: string): void
 	deleteHash(filePath: string): void
+	flush(): Promise<void>
 	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,
 		onBlocksIndexed?: (indexedCount: number) => void,
 		onFileParsed?: (fileBlockCount: number) => void,
+		signal?: AbortSignal,
 	): Promise<{
 		stats: {
 			processed: number

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

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

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

@@ -32,30 +32,47 @@ export class CodeIndexManager {
 	private _isRecoveringFromError = false
 
 	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
 			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
 				if (!workspaceFolders || workspaceFolders.length === 0) {
 					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)) {
-			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)!
 	}
 
+	public static getAllInstances(): CodeIndexManager[] {
+		return Array.from(CodeIndexManager.instances.values())
+	}
+
 	public static disposeAll(): void {
 		for (const instance of CodeIndexManager.instances.values()) {
 			instance.dispose()
@@ -64,17 +81,45 @@ export class CodeIndexManager {
 	}
 
 	private readonly workspacePath: string
+	private readonly _folderUri: vscode.Uri
 	private readonly context: vscode.ExtensionContext
 
 	// 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._folderUri = folderUri
 		this.context = context
 		this._stateManager = new CodeIndexStateManager()
 	}
 
 	// --- 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() {
 		return this._stateManager.onProgressUpdate
 	}
@@ -138,28 +183,32 @@ export class CodeIndexManager {
 			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) {
 			this._cacheManager = new CacheManager(this.context, this.workspacePath)
 			await this._cacheManager.initialize()
 		}
 
-		// 4. Determine if Core Services Need Recreation
+		// 6. Determine if Core Services Need Recreation
 		const needsServiceRecreation = !this._serviceFactory || requiresRestart
 
 		if (needsServiceRecreation) {
 			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 =
 			requiresRestart ||
 			(needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing"))
 
 		if (shouldStartOrRestartIndexing) {
-			this._orchestrator?.startIndexing() // This method is async, but we don't await it here
+			this._orchestrator?.startIndexing()
 		}
 
 		return { requiresRestart }
@@ -173,7 +222,7 @@ export class CodeIndexManager {
 	 * The indexing will continue asynchronously and progress will be reported through events.
 	 */
 	public async startIndexing(): Promise<void> {
-		if (!this.isFeatureEnabled) {
+		if (!this.isFeatureEnabled || !this.isWorkspaceEnabled) {
 			return
 		}
 
@@ -191,6 +240,15 @@ export class CodeIndexManager {
 		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.
 	 */
@@ -247,9 +305,7 @@ export class CodeIndexManager {
 	 * Cleans up the manager instance.
 	 */
 	public dispose(): void {
-		if (this._orchestrator) {
-			this.stopWatcher()
-		}
+		this.stopIndexing()
 		this._stateManager.dispose()
 	}
 
@@ -273,6 +329,8 @@ export class CodeIndexManager {
 		return {
 			...status,
 			workspacePath: this.workspacePath,
+			workspaceEnabled: this.isWorkspaceEnabled,
+			autoEnableDefault: this.autoEnableDefault,
 		}
 	}
 
@@ -384,13 +442,9 @@ export class CodeIndexManager {
 			const isFeatureEnabled = this.isFeatureEnabled
 			const isFeatureConfigured = this.isFeatureConfigured
 
-			// If feature is disabled, stop the service
+			// If feature is disabled, stop the service (including any active scan)
 			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")
 				return
 			}

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

@@ -15,6 +15,7 @@ import { t } from "../../i18n"
 export class CodeIndexOrchestrator {
 	private _fileWatcherSubscriptions: vscode.Disposable[] = []
 	private _isProcessing: boolean = false
+	private _abortController: AbortController | null = null
 
 	constructor(
 		private readonly configManager: CodeIndexConfigManager,
@@ -121,6 +122,8 @@ export class CodeIndexOrchestrator {
 		}
 
 		this._isProcessing = true
+		this._abortController = new AbortController()
+		const signal = this._abortController.signal
 		this.stateManager.setSystemState("Indexing", "Initializing services...")
 
 		// Track whether we successfully connected to Qdrant and started indexing
@@ -178,8 +181,16 @@ export class CodeIndexOrchestrator {
 					},
 					handleBlocksIndexed,
 					handleFileParsed,
+					signal,
 				)
 
+				if (signal.aborted) {
+					await this.cacheManager.flush()
+					this.stopWatcher()
+					this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped"))
+					return
+				}
+
 				if (!result) {
 					throw new Error("Incremental scan failed, is scanner initialized?")
 				}
@@ -231,8 +242,16 @@ export class CodeIndexOrchestrator {
 					},
 					handleBlocksIndexed,
 					handleFileParsed,
+					signal,
 				)
 
+				if (signal.aborted) {
+					await this.cacheManager.flush()
+					this.stopWatcher()
+					this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped"))
+					return
+				}
+
 				if (!result) {
 					throw new Error("Scan failed, is scanner initialized?")
 				}
@@ -282,6 +301,15 @@ export class CodeIndexOrchestrator {
 				this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
 			}
 		} 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)
 			TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
 				error: error instanceof Error ? error.message : String(error),
@@ -325,7 +353,20 @@ export class CodeIndexOrchestrator {
 			this.stopWatcher()
 		} finally {
 			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 = []
 
-		if (this.stateManager.state !== "Error") {
+		if (this.stateManager.state !== "Error" && this.stateManager.state !== "Stopping") {
 			this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.fileWatcherStopped"))
 		}
 		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[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,
 		onBlocksIndexed?: (indexedCount: number) => void,
 		onFileParsed?: (fileBlockCount: number) => void,
+		signal?: AbortSignal,
 	): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> {
 		const directoryPath = directory
 		// Capture workspace context at scan start
@@ -127,6 +128,9 @@ export class DirectoryScanner implements IDirectoryScanner {
 		// Process all files in parallel with concurrency control
 		const parsePromises = supportedPaths.map((filePath) =>
 			parseLimiter(async () => {
+				// Check abort signal before processing each file
+				if (signal?.aborted) return
+
 				try {
 					// Check file size
 					const stats = await stat(filePath)
@@ -173,10 +177,17 @@ export class DirectoryScanner implements IDirectoryScanner {
 									addedBlocksFromFile = true
 
 									// 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) {
 										// Wait if we've reached the maximum 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)
 										}
 
@@ -235,6 +246,10 @@ export class DirectoryScanner implements IDirectoryScanner {
 						await this.cacheManager.updateHash(filePath, currentFileHash)
 					}
 				} 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)
 					TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_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
 		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
 		if (currentBatchBlocks.length > 0) {
 			const release = await mutex.acquire()
@@ -292,6 +318,17 @@ export class DirectoryScanner implements IDirectoryScanner {
 		// Wait for all batch processing to complete
 		await Promise.all(activeBatchPromises)
 
+		// Check abort signal before handling deleted files
+		if (signal?.aborted) {
+			return {
+				stats: {
+					processed: processedCount,
+					skipped: skippedCount,
+				},
+				totalBlockCount,
+			}
+		}
+
 		// Handle deleted files
 		const oldHashes = this.cacheManager.getAllHashes()
 		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"
 
-export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
+export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping"
 
 export class CodeIndexStateManager {
 	private _systemStatus: IndexingState = "Standby"
@@ -58,6 +58,8 @@ export class CodeIndexStateManager {
 	public reportBlockIndexingProgress(processedItems: number, totalItems: number): void {
 		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
 		if (progressChanged || this._systemStatus !== "Indexing") {
 			this._processedItems = processedItems
@@ -81,6 +83,8 @@ export class CodeIndexStateManager {
 	public reportFileQueueProgress(processedFiles: number, totalFiles: number, currentFileBasename?: string): void {
 		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") {
 			this._processedItems = processedFiles
 			this._totalItems = totalFiles

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

@@ -1590,6 +1590,58 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 							)}
 						</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 */}
 						<div className="flex items-center justify-between gap-2 pt-6">
 							<div className="flex gap-2">
@@ -1603,6 +1655,20 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 										</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 &&
 									(indexingStatus.systemStatus === "Indexed" ||
 										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 })
 			case "Indexed":
 				return t("chat:indexingStatus.indexed")
+			case "Stopping":
+				return t("chat:indexingStatus.stopping")
 			case "Error":
 				return t("chat:indexingStatus.error")
 			default:
@@ -76,6 +78,7 @@ export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ classN
 			Standby: "bg-vscode-descriptionForeground/60",
 			Indexing: "bg-yellow-500 animate-pulse",
 			Indexed: "bg-green-500",
+			Stopping: "bg-amber-500 animate-pulse",
 			Error: "bg-red-500",
 		}
 

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

@@ -428,7 +428,8 @@
 		"indexing": "Indexant {{percentage}}%",
 		"indexed": "Indexat",
 		"error": "Error d'índex",
-		"status": "Estat de l'índex"
+		"status": "Estat de l'índex",
+		"stopping": "Aturant la indexació..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Indiziert",
 		"error": "Index-Fehler",
-		"status": "Index-Status"
+		"status": "Index-Status",
+		"stopping": "Indexierung wird gestoppt..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Indexed",
 		"error": "Index error",
-		"status": "Index status"
+		"status": "Index status",
+		"stopping": "Stopping indexing..."
 	},
 	"versionIndicator": {
 		"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",
 			"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": {
 		"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}}%",
 		"indexed": "Indexado",
 		"error": "Error de índice",
-		"status": "Estado del índice"
+		"status": "Estado del índice",
+		"stopping": "Deteniendo la indexación..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Indexé",
 		"error": "Erreur d'index",
-		"status": "Statut de l'index"
+		"status": "Statut de l'index",
+		"stopping": "Arrêt de l'indexation..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "इंडेक्स किया गया",
 		"error": "इंडेक्स त्रुटि",
-		"status": "इंडेक्स स्थिति"
+		"status": "इंडेक्स स्थिति",
+		"stopping": "इंडेक्सिंग रोक रहा है..."
 	},
 	"versionIndicator": {
 		"ariaLabel": "संस्करण {{version}} - रिलीज़ नोट्स देखने के लिए क्लिक करें"

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

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

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

@@ -434,7 +434,8 @@
 		"indexing": "Mengindeks {{percentage}}%",
 		"indexed": "Terindeks",
 		"error": "Error indeks",
-		"status": "Status indeks"
+		"status": "Status indeks",
+		"stopping": "Menghentikan pengindeksan..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Indicizzato",
 		"error": "Errore indice",
-		"status": "Stato indice"
+		"status": "Stato indice",
+		"stopping": "Interruzione dell'indicizzazione..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "インデックス作成済み",
 		"error": "インデックスエラー",
-		"status": "インデックス状態"
+		"status": "インデックス状態",
+		"stopping": "インデックス作成を停止中..."
 	},
 	"versionIndicator": {
 		"ariaLabel": "バージョン {{version}} - クリックしてリリースノートを表示"

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

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

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

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

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

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

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

@@ -428,7 +428,8 @@
 		"indexing": "Indexeren {{percentage}}%",
 		"indexed": "Geïndexeerd",
 		"error": "Index fout",
-		"status": "Index status"
+		"status": "Index status",
+		"stopping": "Indexering wordt gestopt..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Zaindeksowane",
 		"error": "Błąd indeksu",
-		"status": "Status indeksu"
+		"status": "Status indeksu",
+		"stopping": "Zatrzymywanie indeksowania..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Indexado",
 		"error": "Erro do índice",
-		"status": "Status do índice"
+		"status": "Status do índice",
+		"stopping": "Parando a indexação..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Проиндексировано",
 		"error": "Ошибка индекса",
-		"status": "Статус индекса"
+		"status": "Статус индекса",
+		"stopping": "Остановка индексации..."
 	},
 	"versionIndicator": {
 		"ariaLabel": "Версия {{version}} - Нажмите, чтобы просмотреть примечания к выпуску"

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

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

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

@@ -429,7 +429,8 @@
 		"indexing": "İndeksleniyor {{percentage}}%",
 		"indexed": "İndekslendi",
 		"error": "İndeks hatası",
-		"status": "İndeks durumu"
+		"status": "İndeks durumu",
+		"stopping": "İndeksleme durduruluyor..."
 	},
 	"versionIndicator": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "Đã lập 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": {
 		"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)",
 		"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.",
-		"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": {
 		"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}}%",
 		"indexed": "已索引",
 		"error": "索引错误",
-		"status": "索引状态"
+		"status": "索引状态",
+		"stopping": "正在停止索引..."
 	},
 	"versionIndicator": {
 		"ariaLabel": "版本 {{version}} - 点击查看发布说明"

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

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

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

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

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

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