diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx
index 93dd2f1f4f..ef8ea2dddc 100644
--- a/webview-ui/src/components/chat/ModeSelector.tsx
+++ b/webview-ui/src/components/chat/ModeSelector.tsx
@@ -7,7 +7,7 @@ import { IconButton } from "./IconButton"
import { vscode } from "@/utils/vscode"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useAppTranslation } from "@/i18n/TranslationContext"
-import { Mode, getAllModes } from "@roo/modes"
+import { Mode, getAllModes, isCustomMode } from "@roo/modes"
import { ModeConfig, CustomModePrompts } from "@roo-code/types"
import { telemetryClient } from "@/utils/TelemetryClient"
import { TelemetryEventName } from "@roo-code/types"
@@ -16,6 +16,32 @@ import { Fzf } from "fzf"
// Minimum number of modes required to show search functionality
const SEARCH_THRESHOLD = 6
+// Helper function to get the source of a custom mode
+function getModeSource(mode: ModeConfig, customModes?: ModeConfig[]): "global" | "project" | null {
+ if (!isCustomMode(mode.slug, customModes)) {
+ return null // Built-in mode, no source indicator needed
+ }
+
+ // Find the mode in customModes to get its source
+ const customMode = customModes?.find((m) => m.slug === mode.slug)
+ return customMode?.source || "global" // Default to global if source is not specified
+}
+
+// Helper function to get the display text for mode source
+function getSourceDisplayText(
+ source: "global" | "project" | null,
+ t: (key: string) => string,
+ short: boolean = false,
+): string {
+ if (!source) return ""
+
+ if (short) {
+ return source === "global" ? t("chat:modeSelector.globalShort") : t("chat:modeSelector.projectShort")
+ }
+
+ return source === "global" ? t("chat:modeSelector.global") : t("chat:modeSelector.project")
+}
+
interface ModeSelectorProps {
value: Mode
onChange: (value: Mode) => void
@@ -159,6 +185,10 @@ export const ModeSelector = ({
// Combine instruction text for tooltip
const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}`
+ // Get source indicator for selected mode
+ const selectedModeSource = selectedMode ? getModeSource(selectedMode, customModes) : null
+ const selectedModeSourceText = getSourceDisplayText(selectedModeSource, t, false)
+
const trigger = (
- {selectedMode?.name || ""}
+
+ {selectedMode?.name || ""}
+ {selectedModeSourceText && (
+ ({selectedModeSourceText})
+ )}
+
)
@@ -225,29 +260,41 @@ export const ModeSelector = ({
) : (
- {filteredModes.map((mode) => (
-
handleSelect(mode.slug)}
- className={cn(
- "px-3 py-1.5 text-sm cursor-pointer flex items-center",
- "hover:bg-vscode-list-hoverBackground",
- mode.slug === value
- ? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
- : "",
- )}
- data-testid="mode-selector-item">
-
-
{mode.name}
- {mode.description && (
-
- {mode.description}
-
+ {filteredModes.map((mode) => {
+ const modeSource = getModeSource(mode, customModes)
+ const sourceShortText = getSourceDisplayText(modeSource, t, true)
+
+ return (
+
handleSelect(mode.slug)}
+ className={cn(
+ "px-3 py-1.5 text-sm cursor-pointer flex items-center",
+ "hover:bg-vscode-list-hoverBackground",
+ mode.slug === value
+ ? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
+ : "",
)}
+ data-testid="mode-selector-item">
+
+
+ {mode.name}
+ {sourceShortText && (
+
+ ({sourceShortText})
+
+ )}
+
+ {mode.description && (
+
+ {mode.description}
+
+ )}
+
+ {mode.slug === value &&
}
- {mode.slug === value &&
}
-
- ))}
+ )
+ })}
)}
diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
index a829168893..ff97306165 100644
--- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
@@ -199,4 +199,140 @@ describe("ModeSelector", () => {
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).toBeInTheDocument()
})
+
+ test("shows source indicators for custom modes", () => {
+ // Set up mock to return custom modes with source
+ mockModes = [
+ {
+ slug: "custom-global",
+ name: "Custom Global Mode",
+ description: "A global custom mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"] as const,
+ source: "global",
+ },
+ {
+ slug: "custom-project",
+ name: "Custom Project Mode",
+ description: "A project custom mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"] as const,
+ source: "project",
+ },
+ {
+ slug: "code",
+ name: "Code Mode",
+ description: "Built-in code mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"] as const,
+ },
+ ]
+
+ const customModes: ModeConfig[] = [
+ {
+ slug: "custom-global",
+ name: "Custom Global Mode",
+ description: "A global custom mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"],
+ source: "global",
+ },
+ {
+ slug: "custom-project",
+ name: "Custom Project Mode",
+ description: "A project custom mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"],
+ source: "project",
+ },
+ ]
+
+ render(
+ ,
+ )
+
+ // Click to open the popover
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+
+ // Check that custom modes show source indicators in dropdown
+ const modeItems = screen.getAllByTestId("mode-selector-item")
+
+ // Find the custom modes in the dropdown
+ const globalModeItem = modeItems.find((item) => item.textContent?.includes("Custom Global Mode"))
+ const projectModeItem = modeItems.find((item) => item.textContent?.includes("Custom Project Mode"))
+ const builtinModeItem = modeItems.find((item) => item.textContent?.includes("Code Mode"))
+
+ // Custom modes should show source indicators
+ expect(globalModeItem?.textContent).toContain("(chat:modeSelector.globalShort)")
+ expect(projectModeItem?.textContent).toContain("(chat:modeSelector.projectShort)")
+
+ // Built-in mode should not show source indicator
+ expect(builtinModeItem?.textContent).not.toContain("(chat:modeSelector.globalShort)")
+ expect(builtinModeItem?.textContent).not.toContain("(chat:modeSelector.projectShort)")
+ })
+
+ test("shows source indicator in selected mode button", () => {
+ // Set up mock to return custom modes with source
+ mockModes = [
+ {
+ slug: "custom-project",
+ name: "Custom Project Mode",
+ description: "A project custom mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"] as const,
+ source: "project",
+ },
+ ]
+
+ const customModes: ModeConfig[] = [
+ {
+ slug: "custom-project",
+ name: "Custom Project Mode",
+ description: "A project custom mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"],
+ source: "project",
+ },
+ ]
+
+ render(
+ ,
+ )
+
+ // Check that the trigger button shows the source indicator
+ const trigger = screen.getByTestId("mode-selector-trigger")
+ expect(trigger.textContent).toContain("Custom Project Mode")
+ expect(trigger.textContent).toContain("(chat:modeSelector.project)")
+ })
+
+ test("does not show source indicator for built-in modes", () => {
+ // Set up mock to return only built-in modes
+ mockModes = [
+ {
+ slug: "code",
+ name: "Code Mode",
+ description: "Built-in code mode",
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"] as const,
+ },
+ ]
+
+ render()
+
+ // Check that the trigger button does not show source indicator
+ const trigger = screen.getByTestId("mode-selector-trigger")
+ expect(trigger.textContent).toContain("Code Mode")
+ expect(trigger.textContent).not.toContain("(Global)")
+ expect(trigger.textContent).not.toContain("(Project)")
+ })
})
diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json
index da0eb00a44..3f3d0bf91b 100644
--- a/webview-ui/src/i18n/locales/ca/chat.json
+++ b/webview-ui/src/i18n/locales/ca/chat.json
@@ -118,7 +118,11 @@
"settings": "Configuració de Modes",
"description": "Personalitats especialitzades que adapten el comportament de Roo.",
"searchPlaceholder": "Cerca modes...",
- "noResults": "No s'han trobat resultats"
+ "noResults": "No s'han trobat resultats",
+ "global": "Global",
+ "project": "Projecte",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Error en llegir el fitxer:",
"noValidImages": "No s'ha processat cap imatge vàlida",
diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json
index 36c03c9309..e22e4faae4 100644
--- a/webview-ui/src/i18n/locales/de/chat.json
+++ b/webview-ui/src/i18n/locales/de/chat.json
@@ -118,7 +118,11 @@
"settings": "Modus-Einstellungen",
"description": "Spezialisierte Personas, die Roos Verhalten anpassen.",
"searchPlaceholder": "Modi suchen...",
- "noResults": "Keine Ergebnisse gefunden"
+ "noResults": "Keine Ergebnisse gefunden",
+ "global": "Global",
+ "project": "Projekt",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Fehler beim Lesen der Datei:",
"noValidImages": "Keine gültigen Bilder wurden verarbeitet",
diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json
index b33ddd4ab4..33a11a2d7f 100644
--- a/webview-ui/src/i18n/locales/en/chat.json
+++ b/webview-ui/src/i18n/locales/en/chat.json
@@ -120,7 +120,11 @@
"settings": "Mode Settings",
"description": "Specialized personas that tailor Roo's behavior.",
"searchPlaceholder": "Search modes...",
- "noResults": "No results found"
+ "noResults": "No results found",
+ "global": "Global",
+ "project": "Project",
+ "globalShort": "G",
+ "projectShort": "P"
},
"enhancePromptDescription": "The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works.",
"addImages": "Add images to message",
diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json
index 156af85012..a1237b5861 100644
--- a/webview-ui/src/i18n/locales/es/chat.json
+++ b/webview-ui/src/i18n/locales/es/chat.json
@@ -118,7 +118,11 @@
"settings": "Configuración de Modos",
"description": "Personalidades especializadas que adaptan el comportamiento de Roo.",
"searchPlaceholder": "Buscar modos...",
- "noResults": "No se encontraron resultados"
+ "noResults": "No se encontraron resultados",
+ "global": "Global",
+ "project": "Proyecto",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Error al leer el archivo:",
"noValidImages": "No se procesaron imágenes válidas",
diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json
index 959db06b39..cab2746290 100644
--- a/webview-ui/src/i18n/locales/fr/chat.json
+++ b/webview-ui/src/i18n/locales/fr/chat.json
@@ -118,7 +118,11 @@
"settings": "Paramètres des Modes",
"description": "Personas spécialisés qui adaptent le comportement de Roo.",
"searchPlaceholder": "Rechercher des modes...",
- "noResults": "Aucun résultat trouvé"
+ "noResults": "Aucun résultat trouvé",
+ "global": "Global",
+ "project": "Projet",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Erreur lors de la lecture du fichier :",
"noValidImages": "Aucune image valide n'a été traitée",
diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json
index 21d81e4b88..21a257588e 100644
--- a/webview-ui/src/i18n/locales/hi/chat.json
+++ b/webview-ui/src/i18n/locales/hi/chat.json
@@ -118,7 +118,11 @@
"settings": "मोड सेटिंग्स",
"description": "विशेष व्यक्तित्व जो Roo के व्यवहार को अनुकूलित करते हैं।",
"searchPlaceholder": "मोड खोजें...",
- "noResults": "कोई परिणाम नहीं मिला"
+ "noResults": "कोई परिणाम नहीं मिला",
+ "global": "ग्लोबल",
+ "project": "प्रोजेक्ट",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "फ़ाइल पढ़ने में त्रुटि:",
"noValidImages": "कोई मान्य चित्र प्रोसेस नहीं किया गया",
diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json
index fd594480a8..d734709f3c 100644
--- a/webview-ui/src/i18n/locales/id/chat.json
+++ b/webview-ui/src/i18n/locales/id/chat.json
@@ -124,7 +124,11 @@
"settings": "Pengaturan Mode",
"description": "Persona khusus yang menyesuaikan perilaku Roo.",
"searchPlaceholder": "Cari mode...",
- "noResults": "Tidak ada hasil yang ditemukan"
+ "noResults": "Tidak ada hasil yang ditemukan",
+ "global": "Global",
+ "project": "Proyek",
+ "globalShort": "G",
+ "projectShort": "P"
},
"addImages": "Tambahkan gambar ke pesan",
"sendMessage": "Kirim pesan",
diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json
index 5b92e06322..e58555bdf4 100644
--- a/webview-ui/src/i18n/locales/it/chat.json
+++ b/webview-ui/src/i18n/locales/it/chat.json
@@ -118,7 +118,11 @@
"settings": "Impostazioni Modalità",
"description": "Personalità specializzate che adattano il comportamento di Roo.",
"searchPlaceholder": "Cerca modalità...",
- "noResults": "Nessun risultato trovato"
+ "noResults": "Nessun risultato trovato",
+ "global": "Globale",
+ "project": "Progetto",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Errore nella lettura del file:",
"noValidImages": "Nessuna immagine valida è stata elaborata",
diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json
index 0ed516f2b7..db9454f68f 100644
--- a/webview-ui/src/i18n/locales/ja/chat.json
+++ b/webview-ui/src/i18n/locales/ja/chat.json
@@ -118,7 +118,11 @@
"settings": "モード設定",
"description": "Rooの動作をカスタマイズする専門的なペルソナ。",
"searchPlaceholder": "モードを検索...",
- "noResults": "結果が見つかりません"
+ "noResults": "結果が見つかりません",
+ "global": "グローバル",
+ "project": "プロジェクト",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "ファイル読み込みエラー:",
"noValidImages": "有効な画像が処理されませんでした",
diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json
index 95f783b085..2c847981c8 100644
--- a/webview-ui/src/i18n/locales/ko/chat.json
+++ b/webview-ui/src/i18n/locales/ko/chat.json
@@ -118,7 +118,11 @@
"settings": "모드 설정",
"description": "Roo의 행동을 맞춤화하는 전문화된 페르소나.",
"searchPlaceholder": "모드 검색...",
- "noResults": "결과를 찾을 수 없습니다"
+ "noResults": "결과를 찾을 수 없습니다",
+ "global": "글로벌",
+ "project": "프로젝트",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "파일 읽기 오류:",
"noValidImages": "처리된 유효한 이미지가 없습니다",
diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json
index 4bfaf467f6..72f4f5d610 100644
--- a/webview-ui/src/i18n/locales/nl/chat.json
+++ b/webview-ui/src/i18n/locales/nl/chat.json
@@ -110,7 +110,11 @@
"settings": "Modus Instellingen",
"description": "Gespecialiseerde persona's die het gedrag van Roo aanpassen.",
"searchPlaceholder": "Zoek modi...",
- "noResults": "Geen resultaten gevonden"
+ "noResults": "Geen resultaten gevonden",
+ "global": "Globaal",
+ "project": "Project",
+ "globalShort": "G",
+ "projectShort": "P"
},
"addImages": "Afbeeldingen toevoegen aan bericht",
"sendMessage": "Bericht verzenden",
diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json
index 10ee653582..ab7e32ef79 100644
--- a/webview-ui/src/i18n/locales/pl/chat.json
+++ b/webview-ui/src/i18n/locales/pl/chat.json
@@ -118,7 +118,11 @@
"settings": "Ustawienia Trybów",
"description": "Wyspecjalizowane persony, które dostosowują zachowanie Roo.",
"searchPlaceholder": "Szukaj trybów...",
- "noResults": "Nie znaleziono wyników"
+ "noResults": "Nie znaleziono wyników",
+ "global": "Globalny",
+ "project": "Projekt",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Błąd odczytu pliku:",
"noValidImages": "Nie przetworzono żadnych prawidłowych obrazów",
diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json
index b286f5c0ad..9f956c13ea 100644
--- a/webview-ui/src/i18n/locales/pt-BR/chat.json
+++ b/webview-ui/src/i18n/locales/pt-BR/chat.json
@@ -118,7 +118,11 @@
"settings": "Configurações de Modos",
"description": "Personas especializadas que adaptam o comportamento do Roo.",
"searchPlaceholder": "Pesquisar modos...",
- "noResults": "Nenhum resultado encontrado"
+ "noResults": "Nenhum resultado encontrado",
+ "global": "Global",
+ "project": "Projeto",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Erro ao ler arquivo:",
"noValidImages": "Nenhuma imagem válida foi processada",
diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json
index af3e9aadf8..3ab66f217e 100644
--- a/webview-ui/src/i18n/locales/ru/chat.json
+++ b/webview-ui/src/i18n/locales/ru/chat.json
@@ -110,7 +110,11 @@
"settings": "Настройки режимов",
"description": "Специализированные персоны, которые настраивают поведение Roo.",
"searchPlaceholder": "Поиск режимов...",
- "noResults": "Ничего не найдено"
+ "noResults": "Ничего не найдено",
+ "global": "Глобальный",
+ "project": "Проект",
+ "globalShort": "G",
+ "projectShort": "P"
},
"addImages": "Добавить изображения к сообщению",
"sendMessage": "Отправить сообщение",
diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json
index e6868b5db1..238f695f3d 100644
--- a/webview-ui/src/i18n/locales/tr/chat.json
+++ b/webview-ui/src/i18n/locales/tr/chat.json
@@ -118,7 +118,11 @@
"settings": "Mod Ayarları",
"description": "Roo'nun davranışını özelleştiren uzmanlaşmış kişilikler.",
"searchPlaceholder": "Modları ara...",
- "noResults": "Sonuç bulunamadı"
+ "noResults": "Sonuç bulunamadı",
+ "global": "Genel",
+ "project": "Proje",
+ "globalShort": "G",
+ "projectShort": "P"
},
"errorReadingFile": "Dosya okuma hatası:",
"noValidImages": "Hiçbir geçerli resim işlenmedi",
diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json
index 2b86060ceb..a5b2b0a56e 100644
--- a/webview-ui/src/i18n/locales/vi/chat.json
+++ b/webview-ui/src/i18n/locales/vi/chat.json
@@ -118,7 +118,11 @@
"settings": "Cài đặt Chế độ",
"description": "Các nhân cách chuyên biệt điều chỉnh hành vi của Roo.",
"searchPlaceholder": "Tìm kiếm chế độ...",
- "noResults": "Không tìm thấy kết quả nào"
+ "noResults": "Không tìm thấy kết quả nào",
+ "global": "Toàn cục",
+ "project": "Dự án",
+ "globalShort": "TC",
+ "projectShort": "DA"
},
"errorReadingFile": "Lỗi khi đọc tệp:",
"noValidImages": "Không có hình ảnh hợp lệ nào được xử lý",
diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json
index dc21acee0b..7bfb394938 100644
--- a/webview-ui/src/i18n/locales/zh-CN/chat.json
+++ b/webview-ui/src/i18n/locales/zh-CN/chat.json
@@ -118,7 +118,11 @@
"settings": "模式设置",
"description": "专门定制Roo行为的角色。",
"searchPlaceholder": "搜索模式...",
- "noResults": "未找到结果"
+ "noResults": "未找到结果",
+ "global": "全局",
+ "project": "项目",
+ "globalShort": "全",
+ "projectShort": "项"
},
"errorReadingFile": "读取文件时出错:",
"noValidImages": "没有处理有效图片",
diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json
index fc38009186..4e6d2b8872 100644
--- a/webview-ui/src/i18n/locales/zh-TW/chat.json
+++ b/webview-ui/src/i18n/locales/zh-TW/chat.json
@@ -118,7 +118,11 @@
"settings": "模式設定",
"description": "專門定制Roo行為的角色。",
"searchPlaceholder": "搜尋模式...",
- "noResults": "沒有找到結果"
+ "noResults": "沒有找到結果",
+ "global": "全域",
+ "project": "專案",
+ "globalShort": "全",
+ "projectShort": "專"
},
"errorReadingFile": "讀取檔案時發生錯誤:",
"noValidImages": "未處理到任何有效圖片",