diff --git a/app/src/components/settings/panels/AppearancePanel.test.tsx b/app/src/components/settings/panels/AppearancePanel.test.tsx
index d8eb80763..c67739090 100644
--- a/app/src/components/settings/panels/AppearancePanel.test.tsx
+++ b/app/src/components/settings/panels/AppearancePanel.test.tsx
@@ -57,4 +57,16 @@ describe(' font size', () => {
expect(store.getState().theme.agentMessageViewMode).toBe('text');
});
+
+ it('toggles hide-agent-thinking on and off', () => {
+ const { getByRole, store } = renderPanel('medium');
+ const toggle = getByRole('switch', { name: /settings\.appearance\.hideAgentInsights/ });
+
+ expect(toggle).toHaveAttribute('aria-checked', 'false');
+ fireEvent.click(toggle);
+ expect(store.getState().theme.hideAgentInsights).toBe(true);
+
+ fireEvent.click(toggle);
+ expect(store.getState().theme.hideAgentInsights).toBe(false);
+ });
});
diff --git a/app/src/components/settings/panels/AppearancePanel.tsx b/app/src/components/settings/panels/AppearancePanel.tsx
index 73f7ca9d9..53cd4f065 100644
--- a/app/src/components/settings/panels/AppearancePanel.tsx
+++ b/app/src/components/settings/panels/AppearancePanel.tsx
@@ -7,6 +7,7 @@ import {
type FontSize,
setAgentMessageViewMode,
setFontSize,
+ setHideAgentInsights,
setTabBarLabels,
setThemeMode,
type TabBarLabels,
@@ -76,6 +77,7 @@ const AppearancePanel = () => {
const agentMessageViewMode = useAppSelector(
state => state.theme.agentMessageViewMode ?? 'bubbles'
);
+ const hideAgentInsights = useAppSelector(state => state.theme.hideAgentInsights ?? false);
const labelsAlwaysVisible = tabBarLabels === 'always';
const assistantTextModeEnabled = agentMessageViewMode === 'text';
const toggleTabBarLabels = () => {
@@ -86,6 +88,9 @@ const AppearancePanel = () => {
const next: AgentMessageViewMode = assistantTextModeEnabled ? 'bubbles' : 'text';
dispatch(setAgentMessageViewMode(next));
};
+ const toggleHideAgentInsights = () => {
+ dispatch(setHideAgentInsights(!hideAgentInsights));
+ };
// Build at render time so the labels follow the active locale; `t()` itself
// memoises on locale change, so this stays stable across re-renders within a
@@ -309,6 +314,19 @@ const AppearancePanel = () => {
/>
}
/>
+
+ }
+ />
{/* ── Display language (moved from the old settings home list) ── */}
diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts
index 393d202b8..3162645d0 100644
--- a/app/src/lib/i18n/ar.ts
+++ b/app/src/lib/i18n/ar.ts
@@ -3003,6 +3003,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'المصادر',
'conversations.agentTaskInsights.noSteps': 'لم يتم تسجيل أي خطوات',
'conversations.agentTaskInsights.viewProcessSource': 'عرض مصدر عملية الوكيل الكامل',
+ 'conversations.agentTaskInsights.processing': 'قيد المعالجة',
'daemon.serviceBlockingGate.body': 'المحتوى',
'daemon.serviceBlockingGate.downloadHint': 'تلميح التنزيل',
'daemon.serviceBlockingGate.downloadLatest': 'تنزيل أحدث إصدار',
@@ -4509,6 +4510,9 @@ const messages: TranslationMap = {
'عند إيقاف التشغيل، تظهر التسميات فقط عند التمرير أو لعلامة التبويب النشطة.',
'settings.appearance.chatHeading': 'الدردشة',
'settings.appearance.assistantTextMode': 'ردود المساعد كنص',
+ 'settings.appearance.hideAgentInsights': 'إخفاء تفكير الوكيل',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'طيّ الجدول الزمني المباشر لخطوات الوكيل في المحادثة. سيظل رابط "قيد المعالجة" الوامض يتيح لك فتح العملية الكاملة.',
'settings.appearance.assistantTextModeDesc':
'اعرض ردود المساعد كنص بلا إطار مع إبقاء رسائلك داخل فقاعات.',
'settings.mascot.active': 'نشط',
diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts
index b14158b8f..cd954a653 100644
--- a/app/src/lib/i18n/bn.ts
+++ b/app/src/lib/i18n/bn.ts
@@ -3067,6 +3067,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'উৎসসমূহ',
'conversations.agentTaskInsights.noSteps': 'কোনো ধাপ রেকর্ড করা হয়নি',
'conversations.agentTaskInsights.viewProcessSource': 'সম্পূর্ণ এজেন্ট প্রক্রিয়ার উৎস দেখুন',
+ 'conversations.agentTaskInsights.processing': 'প্রসেসিং',
'daemon.serviceBlockingGate.body': 'বডি',
'daemon.serviceBlockingGate.downloadHint': 'ডাউনলোড হিন্ট',
'daemon.serviceBlockingGate.downloadLatest': 'সর্বশেষ সংস্করণ ডাউনলোড করুন',
@@ -4602,6 +4603,9 @@ const messages: TranslationMap = {
'বন্ধ থাকা অবস্থায়, লেবেলগুলি শুধুমাত্র হোভারে বা সক্রিয় ট্যাবের জন্য প্রদর্শিত হয়৷',
'settings.appearance.chatHeading': 'চ্যাট',
'settings.appearance.assistantTextMode': 'অ্যাসিস্ট্যান্টের উত্তর টেক্সট হিসেবে',
+ 'settings.appearance.hideAgentInsights': 'এজেন্টের চিন্তা লুকান',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'চ্যাটে এজেন্টের ধাপে ধাপে লাইভ টাইমলাইন সংকুচিত করুন। একটি ব্লিঙ্কিং "প্রসেসিং" লিঙ্ক এখনও আপনাকে সম্পূর্ণ প্রক্রিয়া খুলতে দেয়।',
'settings.appearance.assistantTextModeDesc':
'আপনার বার্তাগুলি বাবলে রেখে অ্যাসিস্ট্যান্টের উত্তর ফ্রেমহীন টেক্সট হিসেবে দেখান।',
'settings.mascot.active': 'সক্রিয়',
diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts
index 885db709d..1afa1085b 100644
--- a/app/src/lib/i18n/de.ts
+++ b/app/src/lib/i18n/de.ts
@@ -3141,6 +3141,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.noSteps': 'Keine Schritte aufgezeichnet',
'conversations.agentTaskInsights.viewProcessSource':
'Vollständige Agentenprozess-Quelle anzeigen',
+ 'conversations.agentTaskInsights.processing': 'Wird verarbeitet',
'daemon.serviceBlockingGate.body': 'Körper',
'daemon.serviceBlockingGate.downloadHint': 'Hinweis herunterladen',
'daemon.serviceBlockingGate.downloadLatest': 'Lade die neueste Version herunter',
@@ -4721,6 +4722,9 @@ const messages: TranslationMap = {
'Wenn diese Option deaktiviert ist, werden Beschriftungen nur beim Hover oder für die aktive Registerkarte angezeigt.',
'settings.appearance.chatHeading': 'Chat',
'settings.appearance.assistantTextMode': 'Assistentenantworten als Text',
+ 'settings.appearance.hideAgentInsights': 'Agent-Denkprozess ausblenden',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Blendet die schrittweise Live-Zeitleiste des Agenten im Chat aus. Über einen blinkenden „Wird verarbeitet“-Link lässt sich der vollständige Ablauf weiterhin öffnen.',
'settings.appearance.assistantTextModeDesc':
'Zeigt Assistentenantworten als ungerahmten Text an und lässt deine Nachrichten in Blasen.',
'settings.mascot.active': 'Aktiv',
diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts
index 01ae6dd38..df9fc54e7 100644
--- a/app/src/lib/i18n/en.ts
+++ b/app/src/lib/i18n/en.ts
@@ -3614,6 +3614,7 @@ const en: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'Sources',
'conversations.agentTaskInsights.noSteps': 'No steps recorded',
'conversations.agentTaskInsights.viewProcessSource': 'View full agent process Source',
+ 'conversations.agentTaskInsights.processing': 'Processing',
'daemon.serviceBlockingGate.body':
'Retrying in the background. This usually resolves in a few seconds.',
'daemon.serviceBlockingGate.downloadHint':
@@ -5204,6 +5205,9 @@ const en: TranslationMap = {
'settings.appearance.assistantTextMode': 'Plain assistant responses',
'settings.appearance.assistantTextModeDesc':
'Render assistant replies as unframed text while keeping your messages in bubbles.',
+ 'settings.appearance.hideAgentInsights': 'Hide agent thinking',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Collapse the live step-by-step agent timeline in chat. A blinking “Processing” link still lets you open the full run.',
'settings.mascot.active': 'Active',
'settings.mascot.characterDesc': 'Choose your OpenHuman character.',
'settings.mascot.characterHeading': 'Character',
diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts
index ddff5829c..09c9ca174 100644
--- a/app/src/lib/i18n/es.ts
+++ b/app/src/lib/i18n/es.ts
@@ -3119,6 +3119,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.noSteps': 'No hay pasos registrados',
'conversations.agentTaskInsights.viewProcessSource':
'Ver la fuente completa del proceso del agente',
+ 'conversations.agentTaskInsights.processing': 'Procesando',
'daemon.serviceBlockingGate.body': 'Cuerpo',
'daemon.serviceBlockingGate.downloadHint': 'Sugerencia de descarga',
'daemon.serviceBlockingGate.downloadLatest': 'Descargar la última versión',
@@ -4690,6 +4691,9 @@ const messages: TranslationMap = {
'Cuando está desactivado, las etiquetas solo aparecen al pasar el mouse o para la pestaña activa.',
'settings.appearance.chatHeading': 'Chat',
'settings.appearance.assistantTextMode': 'Respuestas del asistente en texto',
+ 'settings.appearance.hideAgentInsights': 'Ocultar el razonamiento del agente',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Contrae la cronología paso a paso del agente en el chat. Un enlace «Procesando» parpadeante te permite abrir el proceso completo.',
'settings.appearance.assistantTextModeDesc':
'Muestra las respuestas del asistente como texto sin marco y mantiene tus mensajes en burbujas.',
'settings.mascot.active': 'Activo',
diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts
index 0d416bb6a..82967e49f 100644
--- a/app/src/lib/i18n/fr.ts
+++ b/app/src/lib/i18n/fr.ts
@@ -3134,6 +3134,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.noSteps': 'Aucune étape enregistrée',
'conversations.agentTaskInsights.viewProcessSource':
"Voir la source complète du processus de l'agent",
+ 'conversations.agentTaskInsights.processing': 'Traitement en cours',
'daemon.serviceBlockingGate.body': 'Corps',
'daemon.serviceBlockingGate.downloadHint': 'Indice de téléchargement',
'daemon.serviceBlockingGate.downloadLatest': 'Télécharger la dernière version',
@@ -4707,6 +4708,9 @@ const messages: TranslationMap = {
"Lorsqu'elle est désactivée, les étiquettes n'apparaissent qu'au survol ou pour l'onglet actif.",
'settings.appearance.chatHeading': 'Chat',
'settings.appearance.assistantTextMode': "Réponses de l'assistant en texte",
+ 'settings.appearance.hideAgentInsights': 'Masquer le raisonnement de l’agent',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Réduit la chronologie en direct des étapes de l’agent dans le chat. Un lien clignotant « Traitement en cours » permet toujours d’ouvrir le déroulé complet.',
'settings.appearance.assistantTextModeDesc':
"Affiche les réponses de l'assistant en texte sans cadre tout en gardant vos messages en bulles.",
'settings.mascot.active': 'Actif',
diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts
index 6702e2589..f986a2ed3 100644
--- a/app/src/lib/i18n/hi.ts
+++ b/app/src/lib/i18n/hi.ts
@@ -3068,6 +3068,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'स्रोत',
'conversations.agentTaskInsights.noSteps': 'कोई चरण दर्ज नहीं किया गया',
'conversations.agentTaskInsights.viewProcessSource': 'पूर्ण एजेंट प्रक्रिया स्रोत देखें',
+ 'conversations.agentTaskInsights.processing': 'प्रोसेसिंग',
'daemon.serviceBlockingGate.body': 'विवरण',
'daemon.serviceBlockingGate.downloadHint': 'डाउनलोड संकेत',
'daemon.serviceBlockingGate.downloadLatest': 'नवीनतम संस्करण डाउनलोड करें',
@@ -4606,6 +4607,9 @@ const messages: TranslationMap = {
'बंद होने पर, लेबल केवल होवर पर या सक्रिय टैब के लिए दिखाई देते हैं।',
'settings.appearance.chatHeading': 'चैट',
'settings.appearance.assistantTextMode': 'असिस्टेंट जवाब टेक्स्ट में',
+ 'settings.appearance.hideAgentInsights': 'एजेंट की सोच छिपाएँ',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'चैट में एजेंट की चरण-दर-चरण लाइव टाइमलाइन को छिपाएँ। एक ब्लिंक करता "प्रोसेसिंग" लिंक फिर भी आपको पूरी प्रक्रिया खोलने देता है।',
'settings.appearance.assistantTextModeDesc':
'असिस्टेंट के जवाबों को बिना फ्रेम वाले टेक्स्ट के रूप में दिखाएं और आपके संदेश बबल में रखें।',
'settings.mascot.active': 'एक्टिव',
diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts
index 0a0ec5946..2bef589bf 100644
--- a/app/src/lib/i18n/id.ts
+++ b/app/src/lib/i18n/id.ts
@@ -3071,6 +3071,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'Sumber',
'conversations.agentTaskInsights.noSteps': 'Tidak ada langkah yang tercatat',
'conversations.agentTaskInsights.viewProcessSource': 'Lihat sumber proses agen lengkap',
+ 'conversations.agentTaskInsights.processing': 'Memproses',
'daemon.serviceBlockingGate.body': 'Isi',
'daemon.serviceBlockingGate.downloadHint': 'Petunjuk unduhan',
'daemon.serviceBlockingGate.downloadLatest': 'Unduh Versi Terbaru',
@@ -4615,6 +4616,9 @@ const messages: TranslationMap = {
'Saat nonaktif, label hanya muncul saat diarahkan atau untuk tab yang aktif.',
'settings.appearance.chatHeading': 'Chat',
'settings.appearance.assistantTextMode': 'Respons asisten sebagai teks',
+ 'settings.appearance.hideAgentInsights': 'Sembunyikan proses berpikir agen',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Ciutkan lini masa langkah demi langkah agen secara langsung di obrolan. Tautan "Memproses" yang berkedip tetap memungkinkan Anda membuka proses lengkapnya.',
'settings.appearance.assistantTextModeDesc':
'Tampilkan balasan asisten sebagai teks tanpa bingkai, sementara pesan Anda tetap dalam gelembung.',
'settings.mascot.active': 'Aktif',
diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts
index 4bda9cf33..985b79647 100644
--- a/app/src/lib/i18n/it.ts
+++ b/app/src/lib/i18n/it.ts
@@ -3113,6 +3113,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.noSteps': 'Nessun passaggio registrato',
'conversations.agentTaskInsights.viewProcessSource':
"Visualizza l'origine completa del processo dell'agente",
+ 'conversations.agentTaskInsights.processing': 'Elaborazione',
'daemon.serviceBlockingGate.body': 'Corpo',
'daemon.serviceBlockingGate.downloadHint': 'Suggerimento di download',
'daemon.serviceBlockingGate.downloadLatest': "Scarica l'ultima versione",
@@ -4678,6 +4679,9 @@ const messages: TranslationMap = {
'Quando disattivata, le etichette vengono visualizzate solo al passaggio del mouse o per la scheda attiva.',
'settings.appearance.chatHeading': 'Chat',
'settings.appearance.assistantTextMode': "Risposte dell'assistente in testo",
+ 'settings.appearance.hideAgentInsights': 'Nascondi il ragionamento dell’agente',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Comprime la cronologia in tempo reale dei passaggi dell’agente nella chat. Un link lampeggiante «Elaborazione» consente comunque di aprire l’intero processo.',
'settings.appearance.assistantTextModeDesc':
"Mostra le risposte dell'assistente come testo senza cornice mantenendo i tuoi messaggi nei fumetti.",
'settings.mascot.active': 'Attivo',
diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts
index a53081415..aa9eb989d 100644
--- a/app/src/lib/i18n/ko.ts
+++ b/app/src/lib/i18n/ko.ts
@@ -3043,6 +3043,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': '소스',
'conversations.agentTaskInsights.noSteps': '기록된 단계 없음',
'conversations.agentTaskInsights.viewProcessSource': '전체 에이전트 프로세스 소스 보기',
+ 'conversations.agentTaskInsights.processing': '처리 중',
'daemon.serviceBlockingGate.body': '본문',
'daemon.serviceBlockingGate.downloadHint': '다운로드 안내',
'daemon.serviceBlockingGate.downloadLatest': '최신 버전 다운로드',
@@ -4558,6 +4559,9 @@ const messages: TranslationMap = {
'끄면 레이블은 마우스를 가져가거나 활성 탭에 대해서만 표시됩니다.',
'settings.appearance.chatHeading': '채팅',
'settings.appearance.assistantTextMode': '어시스턴트 답변을 텍스트로 표시',
+ 'settings.appearance.hideAgentInsights': '에이전트 사고 숨기기',
+ 'settings.appearance.hideAgentInsightsDesc':
+ '채팅에서 에이전트의 단계별 실시간 타임라인을 접습니다. 깜박이는 "처리 중" 링크로 전체 과정을 열 수 있습니다.',
'settings.appearance.assistantTextModeDesc':
'사용자 메시지는 말풍선으로 유지하고 어시스턴트 답변은 프레임 없는 텍스트로 표시합니다.',
'settings.mascot.active': '활성',
diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts
index 1c553c67a..b4b922d61 100644
--- a/app/src/lib/i18n/pl.ts
+++ b/app/src/lib/i18n/pl.ts
@@ -3099,6 +3099,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'Źródła',
'conversations.agentTaskInsights.noSteps': 'Brak zarejestrowanych kroków',
'conversations.agentTaskInsights.viewProcessSource': 'Zobacz pełne źródło procesu agenta',
+ 'conversations.agentTaskInsights.processing': 'Przetwarzanie',
'daemon.serviceBlockingGate.body':
'Rdzeń OpenHuman nie odpowiada. Spróbuj ponownie lub pobierz najnowszą wersję aplikacji.',
'daemon.serviceBlockingGate.downloadHint':
@@ -4672,6 +4673,9 @@ const messages: TranslationMap = {
'Wyłączone — etykiety pojawiają się tylko po najechaniu lub dla aktywnej zakładki.',
'settings.appearance.chatHeading': 'Czat',
'settings.appearance.assistantTextMode': 'Odpowiedzi asystenta jako tekst',
+ 'settings.appearance.hideAgentInsights': 'Ukryj myślenie agenta',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Zwija oś czasu z krokami agenta na żywo w czacie. Migający link „Przetwarzanie” nadal pozwala otworzyć pełny przebieg.',
'settings.appearance.assistantTextModeDesc':
'Wyświetla odpowiedzi asystenta jako tekst bez ramki, a Twoje wiadomości pozostawia w dymkach.',
'settings.mascot.active': 'Aktywny',
diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts
index e9efe16a2..e4c4b801a 100644
--- a/app/src/lib/i18n/pt.ts
+++ b/app/src/lib/i18n/pt.ts
@@ -3117,6 +3117,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'Fontes',
'conversations.agentTaskInsights.noSteps': 'Nenhuma etapa registrada',
'conversations.agentTaskInsights.viewProcessSource': 'Ver a fonte completa do processo do agente',
+ 'conversations.agentTaskInsights.processing': 'Processando',
'daemon.serviceBlockingGate.body': 'Corpo',
'daemon.serviceBlockingGate.downloadHint': 'Dica de download',
'daemon.serviceBlockingGate.downloadLatest': 'Baixar Versão Mais Recente',
@@ -4682,6 +4683,9 @@ const messages: TranslationMap = {
'Quando desativado, os rótulos só aparecem ao passar o mouse ou para a guia ativa.',
'settings.appearance.chatHeading': 'Chat',
'settings.appearance.assistantTextMode': 'Respostas do assistente em texto',
+ 'settings.appearance.hideAgentInsights': 'Ocultar o raciocínio do agente',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Recolhe a linha do tempo passo a passo do agente no chat. Um link “Processando” piscando ainda permite abrir o processo completo.',
'settings.appearance.assistantTextModeDesc':
'Renderiza as respostas do assistente como texto sem moldura e mantém suas mensagens em balões.',
'settings.mascot.active': 'Ativo',
diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts
index 4f966ae2c..223a55822 100644
--- a/app/src/lib/i18n/ru.ts
+++ b/app/src/lib/i18n/ru.ts
@@ -3090,6 +3090,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': 'Источники',
'conversations.agentTaskInsights.noSteps': 'Шаги не записаны',
'conversations.agentTaskInsights.viewProcessSource': 'Показать полный источник процесса агента',
+ 'conversations.agentTaskInsights.processing': 'Обработка',
'daemon.serviceBlockingGate.body': 'Текст',
'daemon.serviceBlockingGate.downloadHint': 'Подсказка по загрузке',
'daemon.serviceBlockingGate.downloadLatest': 'Скачать последнюю версию',
@@ -4645,6 +4646,9 @@ const messages: TranslationMap = {
'Если этот параметр отключен, метки отображаются только при наведении курсора мыши или на активной вкладке.',
'settings.appearance.chatHeading': 'Чат',
'settings.appearance.assistantTextMode': 'Ответы ассистента текстом',
+ 'settings.appearance.hideAgentInsights': 'Скрыть размышления агента',
+ 'settings.appearance.hideAgentInsightsDesc':
+ 'Сворачивает пошаговую ленту действий агента в чате. Мигающая ссылка «Обработка» по-прежнему позволяет открыть весь процесс.',
'settings.appearance.assistantTextModeDesc':
'Показывает ответы ассистента как текст без рамки, оставляя ваши сообщения в пузырьках.',
'settings.mascot.active': 'Активно',
diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts
index 8c4cb48ad..b4c964dcd 100644
--- a/app/src/lib/i18n/zh-CN.ts
+++ b/app/src/lib/i18n/zh-CN.ts
@@ -2920,6 +2920,7 @@ const messages: TranslationMap = {
'conversations.agentTaskInsights.sourcesHeading': '来源',
'conversations.agentTaskInsights.noSteps': '未记录任何步骤',
'conversations.agentTaskInsights.viewProcessSource': '查看完整的智能体处理来源',
+ 'conversations.agentTaskInsights.processing': '处理中',
'daemon.serviceBlockingGate.body': '核心服务不可用,请等待或下载最新版本。',
'daemon.serviceBlockingGate.downloadHint': '下载最新版本',
'daemon.serviceBlockingGate.downloadLatest': '下载最新版本',
@@ -4379,6 +4380,9 @@ const messages: TranslationMap = {
'settings.appearance.tabBarAlwaysShowLabelsDesc': '关闭时,标签仅出现在悬停时或活动选项卡上。',
'settings.appearance.chatHeading': '聊天',
'settings.appearance.assistantTextMode': '助手回复以文本显示',
+ 'settings.appearance.hideAgentInsights': '隐藏智能体思考过程',
+ 'settings.appearance.hideAgentInsightsDesc':
+ '折叠聊天中智能体逐步执行的实时时间线。闪烁的“处理中”链接仍可让你打开完整过程。',
'settings.appearance.assistantTextModeDesc': '将助手回复渲染为无边框文本,同时保留你的消息气泡。',
'settings.mascot.active': '活跃',
'settings.mascot.characterDesc': '选择你的 OpenHuman 角色',
diff --git a/app/src/pages/Conversations.tsx b/app/src/pages/Conversations.tsx
index e29992553..20e30d936 100644
--- a/app/src/pages/Conversations.tsx
+++ b/app/src/pages/Conversations.tsx
@@ -304,6 +304,11 @@ const Conversations = ({
const agentMessageViewMode = useAppSelector(
state => state.theme?.agentMessageViewMode ?? 'bubbles'
);
+ // When ON, the verbose per-agent "Agentic task insights" timeline is hidden
+ // from chat; a compact blinking "Processing" link (and the existing message
+ // bubble loading) stand in for it, with the full run one click away in the
+ // Agent Process Source side panel. See themeSlice.hideAgentInsights.
+ const hideAgentInsights = useAppSelector(state => state.theme?.hideAgentInsights ?? false);
const inferenceTurnLifecycleByThread = useAppSelector(
state => state.chatRuntime.inferenceTurnLifecycleByThread
);
@@ -2233,12 +2238,43 @@ const Conversations = ({
messages the turn produced — both for the settled/inline case
(shouldRenderTimelineBeforeLatestAgentMessage) and the live
in-flight fallback. */}
- {selectedThreadToolTimeline.length > 0 && (
- setOpenSubagentTaskId(sub.taskId)}
- />
- )}
+ {selectedThreadToolTimeline.length > 0 &&
+ (hideAgentInsights ? (
+ // "Hide agent thinking" is ON: suppress the verbose step rows.
+ // While the turn is still in flight, surface a single compact
+ // blinking "Processing" link that opens the full run in the
+ // Agent Process Source side panel. Once settled, the
+ // "View full agent process Source" button below takes over.
+ isSending ? (
+
+ ) : !shouldRenderTimelineBeforeLatestAgentMessage ? (
+ // Settled, but the hoisted "View full agent process Source"
+ // opener below won't render because no agent message exists
+ // for this turn (e.g. a cancelled first turn — onError skips
+ // the agent message for `error_type === 'cancelled'`). Without
+ // this fallback the recorded steps would be unreachable while
+ // hidden, so surface our own opener whenever entries remain.
+
+ ) : null
+ ) : (
+ setOpenSubagentTaskId(sub.taskId)}
+ />
+ ))}
{/* "View full agent process" — only in the settled/inline state
(turn finished, an agent message exists). Hoisted out of the
per-message map alongside the panel above so it renders once
diff --git a/app/src/pages/__tests__/Conversations.render.test.tsx b/app/src/pages/__tests__/Conversations.render.test.tsx
index 1b447656a..eb49556bf 100644
--- a/app/src/pages/__tests__/Conversations.render.test.tsx
+++ b/app/src/pages/__tests__/Conversations.render.test.tsx
@@ -19,6 +19,7 @@ import { chatSend } from '../../services/chatService';
import { CoreRpcError } from '../../services/coreRpcClient';
import agentProfileReducer from '../../store/agentProfileSlice';
import chatRuntimeReducer, {
+ beginInferenceTurn,
setInferenceStatusForThread,
setTaskBoardForThread,
setToolTimelineForThread,
@@ -1778,6 +1779,183 @@ describe('Conversations — agent task insights panel anchoring (#3717 Bug 2)',
fireEvent.click(screen.getByTestId('subagent-view-processing'));
});
});
+
+ it('hides the verbose timeline when "hide agent thinking" is on, but still opens the source panel', async () => {
+ const thread = makeThread({ id: 'hide-insights-thread', title: 'Hide insights' });
+ const messages: ThreadMessage[] = [
+ {
+ id: 'm-user',
+ sender: 'user',
+ type: 'text',
+ content: 'How many posts?',
+ extraMetadata: {},
+ createdAt: '2026-01-01T00:00:00.000Z',
+ },
+ {
+ id: 'm-agent',
+ sender: 'agent',
+ type: 'text',
+ content: 'Zero posts went up.',
+ extraMetadata: {},
+ createdAt: '2026-01-01T00:01:00.000Z',
+ },
+ ];
+ mockGetThreads.mockResolvedValue({ threads: [thread], count: 1 });
+ mockGetThreadMessages.mockResolvedValue({ messages, count: messages.length });
+
+ let store: ReturnType | undefined;
+ await act(async () => {
+ store = await renderConversations({
+ thread: {
+ ...selectedThreadState(thread),
+ messagesByThreadId: { [thread.id]: messages },
+ messages,
+ },
+ socket: socketState('connected'),
+ theme: {
+ mode: 'system',
+ tabBarLabels: 'hover',
+ fontSize: 'medium',
+ hideAgentInsights: true,
+ },
+ });
+ });
+
+ await screen.findByText('Zero posts went up.');
+ await act(async () => {
+ store!.dispatch(
+ setToolTimelineForThread({
+ threadId: thread.id,
+ entries: [{ id: 'tl-1', name: 'web_fetch', round: 1, status: 'success' }],
+ })
+ );
+ });
+
+ // Settled turn + preference ON: the verbose inline timeline is suppressed…
+ expect(screen.queryByTestId('agent-task-insights')).toBeNull();
+ // …but the "View full agent process Source" affordance still works and the
+ // full run is one click away in the side panel (which renders the timeline).
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('view-process-source'));
+ });
+ expect(await screen.findByTestId('agent-task-insights')).toBeInTheDocument();
+ });
+
+ it('shows a blinking "Processing" link instead of the timeline while in flight', async () => {
+ const thread = makeThread({ id: 'processing-thread', title: 'Processing' });
+ const messages: ThreadMessage[] = [
+ {
+ id: 'm-user',
+ sender: 'user',
+ type: 'text',
+ content: 'Go.',
+ extraMetadata: {},
+ createdAt: '2026-01-01T00:00:00.000Z',
+ },
+ ];
+ mockGetThreads.mockResolvedValue({ threads: [thread], count: 1 });
+ mockGetThreadMessages.mockResolvedValue({ messages, count: messages.length });
+
+ let store: ReturnType | undefined;
+ await act(async () => {
+ store = await renderConversations({
+ thread: {
+ ...selectedThreadState(thread),
+ messagesByThreadId: { [thread.id]: messages },
+ messages,
+ },
+ socket: socketState('connected'),
+ theme: {
+ mode: 'system',
+ tabBarLabels: 'hover',
+ fontSize: 'medium',
+ hideAgentInsights: true,
+ },
+ });
+ });
+
+ await screen.findByText('Go.');
+ // Drive the thread into an in-flight turn so `isSending` is true, then seed
+ // a running timeline that the preference should keep hidden behind the link.
+ await act(async () => {
+ store!.dispatch(beginInferenceTurn({ threadId: thread.id }));
+ store!.dispatch(
+ setToolTimelineForThread({
+ threadId: thread.id,
+ entries: [{ id: 'tl-1', name: 'web_fetch', round: 1, status: 'running' }],
+ })
+ );
+ });
+
+ const link = await screen.findByTestId('agent-processing-link');
+ expect(link).toBeInTheDocument();
+ expect(screen.queryByTestId('agent-task-insights')).toBeNull();
+
+ // Clicking the compact link opens the full run in the source panel.
+ await act(async () => {
+ fireEvent.click(link);
+ });
+ expect(await screen.findByTestId('agent-task-insights')).toBeInTheDocument();
+ });
+
+ it('keeps a settled source opener when hidden and no agent message exists (cancelled first turn)', async () => {
+ // A cancelled first turn records timeline steps but never persists an agent
+ // message, so the hoisted "View full agent process Source" button does not
+ // render. With the timeline hidden, the fallback opener must keep those
+ // steps reachable.
+ const thread = makeThread({ id: 'cancelled-first-turn', title: 'Cancelled' });
+ const messages: ThreadMessage[] = [
+ {
+ id: 'm-user',
+ sender: 'user',
+ type: 'text',
+ content: 'Start then stop.',
+ extraMetadata: {},
+ createdAt: '2026-01-01T00:00:00.000Z',
+ },
+ ];
+ mockGetThreads.mockResolvedValue({ threads: [thread], count: 1 });
+ mockGetThreadMessages.mockResolvedValue({ messages, count: messages.length });
+
+ let store: ReturnType | undefined;
+ await act(async () => {
+ store = await renderConversations({
+ thread: {
+ ...selectedThreadState(thread),
+ messagesByThreadId: { [thread.id]: messages },
+ messages,
+ },
+ socket: socketState('connected'),
+ theme: {
+ mode: 'system',
+ tabBarLabels: 'hover',
+ fontSize: 'medium',
+ hideAgentInsights: true,
+ },
+ });
+ });
+
+ await screen.findByText('Start then stop.');
+ // Settled (no in-flight turn) timeline with steps but no agent message.
+ await act(async () => {
+ store!.dispatch(
+ setToolTimelineForThread({
+ threadId: thread.id,
+ entries: [{ id: 'tl-1', name: 'web_fetch', round: 1, status: 'cancelled' }],
+ })
+ );
+ });
+
+ // No verbose timeline, and the hoisted opener is absent (no agent message)…
+ expect(screen.queryByTestId('agent-task-insights')).toBeNull();
+ expect(screen.queryByTestId('view-process-source')).toBeNull();
+ // …so the fallback opener carries access to the recorded steps.
+ const fallback = await screen.findByTestId('agent-process-source-fallback');
+ await act(async () => {
+ fireEvent.click(fallback);
+ });
+ expect(await screen.findByTestId('agent-task-insights')).toBeInTheDocument();
+ });
});
describe('Conversations — open-session resume (View work)', () => {
diff --git a/app/src/store/index.ts b/app/src/store/index.ts
index 3427dbb7f..fbaa59dc9 100644
--- a/app/src/store/index.ts
+++ b/app/src/store/index.ts
@@ -96,7 +96,14 @@ const persistedLocaleReducer = persistReducer(localePersistConfig, localeReducer
const themePersistConfig = {
key: 'theme',
storage: localStorageAdapter,
- whitelist: ['mode', 'tabBarLabels', 'fontSize', 'agentMessageViewMode', 'developerMode'],
+ whitelist: [
+ 'mode',
+ 'tabBarLabels',
+ 'fontSize',
+ 'agentMessageViewMode',
+ 'developerMode',
+ 'hideAgentInsights',
+ ],
};
const persistedThemeReducer = persistReducer(themePersistConfig, themeReducer);
diff --git a/app/src/store/themeSlice.test.ts b/app/src/store/themeSlice.test.ts
index 1965423f0..90e7426df 100644
--- a/app/src/store/themeSlice.test.ts
+++ b/app/src/store/themeSlice.test.ts
@@ -3,8 +3,10 @@ import { describe, expect, it } from 'vitest';
import themeReducer, {
FONT_SIZE_PX,
type FontSize,
+ selectHideAgentInsights,
setAgentMessageViewMode,
setFontSize,
+ setHideAgentInsights,
setTabBarLabels,
setThemeMode,
} from './themeSlice';
@@ -39,6 +41,7 @@ describe('themeSlice', () => {
fontSize: 'xlarge',
agentMessageViewMode: 'text',
developerMode: false,
+ hideAgentInsights: false,
});
});
@@ -48,6 +51,20 @@ describe('themeSlice', () => {
expect(state.agentMessageViewMode).toBe('text');
});
+ it('defaults hideAgentInsights to false and toggles it', () => {
+ let state = themeReducer(undefined, { type: '@@INIT' });
+ expect(state.hideAgentInsights).toBe(false);
+ expect(selectHideAgentInsights({ theme: state })).toBe(false);
+
+ state = themeReducer(state, setHideAgentInsights(true));
+ expect(state.hideAgentInsights).toBe(true);
+ expect(selectHideAgentInsights({ theme: state })).toBe(true);
+ });
+
+ it('falls back to false when hideAgentInsights is absent from persisted state', () => {
+ expect(selectHideAgentInsights({ theme: {} as never })).toBe(false);
+ });
+
it('maps every font size to a concrete px value', () => {
const sizes: FontSize[] = ['small', 'medium', 'large', 'xlarge'];
expect(sizes.map(size => FONT_SIZE_PX[size])).toEqual(['14px', '16px', '18px', '20px']);
diff --git a/app/src/store/themeSlice.ts b/app/src/store/themeSlice.ts
index 3e1455025..f96f8ccad 100644
--- a/app/src/store/themeSlice.ts
+++ b/app/src/store/themeSlice.ts
@@ -36,6 +36,15 @@ interface ThemeState {
* is authoritative and is never relaxed by this toggle.
*/
developerMode: boolean;
+ /**
+ * Hide the live "Agentic task insights" step-by-step timeline in chat
+ * (default OFF). When true, the verbose per-agent step rows are collapsed
+ * away: the chat shows only the existing message-bubble loading plus a
+ * compact blinking "Processing" link while a turn is in flight. The full
+ * timeline is still one click away via that link / the "View full agent
+ * process Source" affordance, which open the existing side panel.
+ */
+ hideAgentInsights: boolean;
}
const initialState: ThemeState = {
@@ -44,6 +53,7 @@ const initialState: ThemeState = {
fontSize: 'medium',
agentMessageViewMode: 'text',
developerMode: false,
+ hideAgentInsights: false,
};
const themeSlice = createSlice({
@@ -65,6 +75,9 @@ const themeSlice = createSlice({
setDeveloperMode(state, action: PayloadAction) {
state.developerMode = action.payload;
},
+ setHideAgentInsights(state, action: PayloadAction) {
+ state.hideAgentInsights = action.payload;
+ },
},
});
@@ -74,9 +87,18 @@ export const {
setFontSize,
setAgentMessageViewMode,
setDeveloperMode,
+ setHideAgentInsights,
} = themeSlice.actions;
export default themeSlice.reducer;
+/**
+ * Selector for the persisted `hideAgentInsights` preference. Falls back to
+ * `false` so existing persisted state (written before this field existed)
+ * keeps the verbose timeline visible until the user opts out.
+ */
+export const selectHideAgentInsights = (state: { theme: ThemeState }): boolean =>
+ state.theme.hideAgentInsights ?? false;
+
/**
* Selector for the persisted `developerMode` preference.
* Use {@link useDeveloperMode} in components — it combines this with `IS_DEV`.