diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx
index 93dd2f1f4f..68f5c7beff 100644
--- a/webview-ui/src/components/chat/ModeSelector.tsx
+++ b/webview-ui/src/components/chat/ModeSelector.tsx
@@ -46,6 +46,22 @@ export const ModeSelector = ({
const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState()
const { t } = useAppTranslation()
+ // Helper to determine if a mode is custom and get its source
+ const getModeSource = (mode: ModeConfig): string | null => {
+ const isCustom = customModes?.some((m) => m.slug === mode.slug)
+ if (!isCustom) return null
+ return mode.source || "global" // Default to global if source is undefined
+ }
+
+ // Helper to get display text for source
+ const getSourceDisplayText = (source: string | null, isShort: boolean = false): string => {
+ if (!source) return ""
+ if (isShort) {
+ return source === "global" ? t("chat:modeSelector.globalShort") : t("chat:modeSelector.projectShort")
+ }
+ return source === "global" ? t("chat:modeSelector.global") : t("chat:modeSelector.project")
+ }
+
const trackModeSelectorOpened = React.useCallback(() => {
// Track telemetry every time the mode selector is opened
telemetryClient.capture(TelemetryEventName.MODE_SELECTOR_OPENED)
@@ -159,6 +175,13 @@ export const ModeSelector = ({
// Combine instruction text for tooltip
const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}`
+ // Helper function to render source indicator
+ const renderSourceIndicator = (mode: ModeConfig, isShort: boolean = false) => {
+ const source = getModeSource(mode)
+ if (!source) return null
+ return ({getSourceDisplayText(source, isShort)})
+ }
+
const trigger = (
- {selectedMode?.name || ""}
+
+ {selectedMode?.name || ""}
+ {selectedMode && renderSourceIndicator(selectedMode, false)}
+
)
@@ -238,7 +264,10 @@ export const ModeSelector = ({
)}
data-testid="mode-selector-item">
-
{mode.name}
+
+ {mode.name}
+ {renderSourceIndicator(mode, true)}
+
{mode.description && (
{mode.description}
diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
index a829168893..827dcab432 100644
--- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
@@ -21,7 +21,21 @@ vi.mock("@/context/ExtensionStateContext", () => ({
vi.mock("@/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
- t: (key: string) => key,
+ t: (key: string) => {
+ const translations: Record = {
+ "chat:modeSelector.global": "Global",
+ "chat:modeSelector.project": "Project",
+ "chat:modeSelector.globalShort": "G",
+ "chat:modeSelector.projectShort": "P",
+ "chat:modeSelector.description": "Select a mode to change how the assistant responds.",
+ "chat:modeSelector.searchPlaceholder": "Search modes...",
+ "chat:modeSelector.noResults": "No modes found",
+ "chat:modeSelector.marketplace": "Browse Marketplace",
+ "chat:modeSelector.settings": "Mode Settings",
+ "chat:modeSelector.title": "Modes",
+ }
+ return translations[key] || key
+ },
}),
}))
@@ -93,7 +107,7 @@ describe("ModeSelector", () => {
expect(screen.getByTestId("mode-search-input")).toBeInTheDocument()
// Info icon should be visible
- expect(screen.getByText("chat:modeSelector.title")).toBeInTheDocument()
+ expect(screen.getByText("Modes")).toBeInTheDocument()
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).toBeInTheDocument()
})
@@ -117,7 +131,7 @@ describe("ModeSelector", () => {
expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument()
// Info blurb should be visible
- expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument()
+ expect(screen.getByText(/Select a mode to change how the assistant responds./)).toBeInTheDocument()
// Info icon should NOT be visible
const infoIcon = document.querySelector(".codicon-info")
@@ -169,7 +183,7 @@ describe("ModeSelector", () => {
expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument()
// Info blurb should be visible instead
- expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument()
+ expect(screen.getByText(/Select a mode to change how the assistant responds./)).toBeInTheDocument()
// Info icon should NOT be visible
const infoIcon = document.querySelector(".codicon-info")
@@ -199,4 +213,129 @@ describe("ModeSelector", () => {
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).toBeInTheDocument()
})
+
+ test("shows source indicator for custom modes", () => {
+ const customModesWithSource: ModeConfig[] = [
+ {
+ slug: "custom-global",
+ name: "Custom Global Mode",
+ roleDefinition: "Role",
+ groups: ["read"] as ModeConfig["groups"],
+ source: "global",
+ },
+ {
+ slug: "custom-project",
+ name: "Custom Project Mode",
+ roleDefinition: "Role",
+ groups: ["read"] as ModeConfig["groups"],
+ source: "project",
+ },
+ ]
+
+ // Set up mock to return custom modes
+ mockModes = [
+ ...customModesWithSource,
+ {
+ slug: "code",
+ name: "Code",
+ roleDefinition: "Role",
+ groups: ["read"] as ModeConfig["groups"],
+ },
+ ]
+
+ render(
+ ,
+ )
+
+ // Check selected mode shows full indicator
+ const trigger = screen.getByTestId("mode-selector-trigger")
+ expect(trigger).toHaveTextContent("Custom Global Mode")
+ expect(trigger).toHaveTextContent("(Global)")
+
+ // Open dropdown
+ fireEvent.click(trigger)
+
+ // Check dropdown shows short indicators
+ const items = screen.getAllByTestId("mode-selector-item")
+ const globalItem = items.find((item) => item.textContent?.includes("Custom Global Mode"))
+ const projectItem = items.find((item) => item.textContent?.includes("Custom Project Mode"))
+
+ expect(globalItem).toHaveTextContent("(G)")
+ expect(projectItem).toHaveTextContent("(P)")
+ })
+
+ test("shows no indicator for built-in modes", () => {
+ // Set up mock to return only built-in modes
+ mockModes = [
+ {
+ slug: "code",
+ name: "Code",
+ roleDefinition: "Role",
+ groups: ["read"] as ModeConfig["groups"],
+ },
+ {
+ slug: "architect",
+ name: "Architect",
+ roleDefinition: "Role",
+ groups: ["read"] as ModeConfig["groups"],
+ },
+ ]
+
+ render()
+
+ const trigger = screen.getByTestId("mode-selector-trigger")
+ expect(trigger).toHaveTextContent("Code")
+ expect(trigger).not.toHaveTextContent("(")
+
+ // Open dropdown
+ fireEvent.click(trigger)
+
+ // Check that no items have indicators
+ const items = screen.getAllByTestId("mode-selector-item")
+ items.forEach((item) => {
+ expect(item).not.toHaveTextContent("(Global")
+ expect(item).not.toHaveTextContent("(Project")
+ })
+ })
+
+ test("defaults to global for custom modes without source", () => {
+ const customModesNoSource: ModeConfig[] = [
+ {
+ slug: "custom-old",
+ name: "Old Custom Mode",
+ roleDefinition: "Role",
+ groups: ["read"] as ModeConfig["groups"],
+ // No source field
+ },
+ ]
+
+ // Set up mock to include the custom mode
+ mockModes = [
+ ...customModesNoSource,
+ {
+ slug: "code",
+ name: "Code",
+ roleDefinition: "Role",
+ groups: ["read"] as ModeConfig["groups"],
+ },
+ ]
+
+ render(
+ ,
+ )
+
+ const trigger = screen.getByTestId("mode-selector-trigger")
+ expect(trigger).toHaveTextContent("Old Custom Mode")
+ expect(trigger).toHaveTextContent("(Global)")
+ })
})
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..74c1116b56 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..cf4134d924 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..06dfef3917 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": "G",
+ "projectShort": "P"
},
"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..cccd0d74fd 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": "G",
+ "projectShort": "P"
},
"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..24106a9892 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": "G",
+ "projectShort": "P"
},
"errorReadingFile": "讀取檔案時發生錯誤:",
"noValidImages": "未處理到任何有效圖片",