feat(chat): render tool-call failure explanations in View-processing timeline (#4254, frontend) (#4413)

This commit is contained in:
Mega Mind
2026-07-03 11:19:15 -07:00
committed by GitHub
parent a93c6c9bbf
commit 28331c93a6
21 changed files with 696 additions and 4 deletions
+28
View File
@@ -35,6 +35,34 @@ const messages: TranslationMap = {
'crossHostVault.title': 'الخزنة موجودة على مضيف النواة.',
'crossHostVault.message':
'يتم تخزين خزنة الذاكرة هذه على مضيف openhuman-core ({os}). لا يمكن فتحها أو عرضها إلا على ذلك الجهاز، وليس من هذا الجهاز.',
'conversations.toolFailure.whyLabel': 'لماذا',
'conversations.toolFailure.nextLabel': 'ما الذي يجب فعله بعد ذلك',
'conversations.toolFailure.missingPermission.cause': 'لا يملك OpenHuman الإذن للقيام بهذا بعد.',
'conversations.toolFailure.missingPermission.next': 'امنح الإذن المطلوب ثم حاول مرة أخرى.',
'conversations.toolFailure.missingApp.cause':
'التطبيق أو البرنامج اللازم لهذا الإجراء غير متوفر.',
'conversations.toolFailure.missingApp.next': 'ثبّت التطبيق أو افتحه ثم حاول مرة أخرى.',
'conversations.toolFailure.serviceUnavailable.cause':
'إحدى الخدمات التي يحتاجها OpenHuman غير متوفرة مؤقتًا.',
'conversations.toolFailure.serviceUnavailable.next':
'سيحاول OpenHuman مرة أخرى قريبًا — لا حاجة لأي إجراء.',
'conversations.toolFailure.badCredentials.cause':
'بيانات تسجيل الدخول المحفوظة مفقودة أو لم تعد صالحة.',
'conversations.toolFailure.badCredentials.next':
'سجّل الدخول مجددًا أو حدّث بيانات الاعتماد ثم حاول مرة أخرى.',
'conversations.toolFailure.blockedByPolicy.cause':
'هذا الإجراء محظور بواسطة إعدادات الأمان الخاصة بك.',
'conversations.toolFailure.blockedByPolicy.next':
'اسمح به من الإعدادات → وصول الوكيل إذا أردت تشغيله.',
'conversations.toolFailure.modelConnection.cause':
'تعذّر على OpenHuman الوصول إلى نموذج الذكاء الاصطناعي.',
'conversations.toolFailure.modelConnection.next':
'تحقّق من اتصالك أو إعدادات النموذج؛ سيعيد OpenHuman المحاولة.',
'conversations.toolFailure.timeout.cause': 'استغرق الإجراء وقتًا طويلاً وتم إيقافه.',
'conversations.toolFailure.timeout.next': 'سيعيد OpenHuman المحاولة، أو يمكنك إعادتها يدويًا.',
'conversations.toolFailure.unknown.cause': 'حدث خطأ ما في هذا الإجراء.',
'conversations.toolFailure.unknown.next':
'حاول مرة أخرى؛ وإذا استمر الفشل، شغّل التشخيص من الإعدادات.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'شارك ملاحظاتك',
'feedback.board': 'لوحة الملاحظات',
+30
View File
@@ -37,6 +37,36 @@ const messages: TranslationMap = {
'crossHostVault.title': 'ভল্টটি কোর হোস্টে রয়েছে।',
'crossHostVault.message':
'এই মেমরি ভল্টটি openhuman-core হোস্টে ({os}) সংরক্ষিত আছে। এটি কেবল সেই মেশিনেই খোলা বা দেখানো যায়, এই ডিভাইস থেকে নয়।',
'conversations.toolFailure.whyLabel': 'কেন',
'conversations.toolFailure.nextLabel': 'এরপর কী করবেন',
'conversations.toolFailure.missingPermission.cause': 'OpenHuman-এর এখনও এটি করার অনুমতি নেই।',
'conversations.toolFailure.missingPermission.next':
'প্রয়োজনীয় অনুমতি দিন, তারপর আবার চেষ্টা করুন।',
'conversations.toolFailure.missingApp.cause':
'এই কাজের জন্য প্রয়োজনীয় অ্যাপ বা প্রোগ্রামটি উপলব্ধ নেই।',
'conversations.toolFailure.missingApp.next':
'অ্যাপটি ইনস্টল করুন বা খুলুন, তারপর আবার চেষ্টা করুন।',
'conversations.toolFailure.serviceUnavailable.cause':
'OpenHuman-এর প্রয়োজনীয় একটি পরিষেবা সাময়িকভাবে অনুপলব্ধ।',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman শীঘ্রই আবার চেষ্টা করবে — কোনো পদক্ষেপের প্রয়োজন নেই।',
'conversations.toolFailure.badCredentials.cause':
'সংরক্ষিত সাইন-ইন তথ্য অনুপস্থিত বা আর বৈধ নয়।',
'conversations.toolFailure.badCredentials.next':
'আবার সাইন ইন করুন বা শংসাপত্র আপডেট করুন, তারপর আবার চেষ্টা করুন।',
'conversations.toolFailure.blockedByPolicy.cause':
'এই কাজটি আপনার নিরাপত্তা সেটিংস দ্বারা অবরুদ্ধ।',
'conversations.toolFailure.blockedByPolicy.next':
'আপনি চালাতে চাইলে সেটিংস → এজেন্ট অ্যাক্সেস-এ এটি অনুমতি দিন।',
'conversations.toolFailure.modelConnection.cause': 'OpenHuman AI মডেলে পৌঁছাতে পারেনি।',
'conversations.toolFailure.modelConnection.next':
'আপনার সংযোগ বা মডেল সেটিংস পরীক্ষা করুন; OpenHuman আবার চেষ্টা করবে।',
'conversations.toolFailure.timeout.cause': 'কাজটি অনেক বেশি সময় নেওয়ায় থামিয়ে দেওয়া হয়েছে।',
'conversations.toolFailure.timeout.next':
'OpenHuman আবার চেষ্টা করবে, অথবা আপনি নিজে আবার চেষ্টা করতে পারেন।',
'conversations.toolFailure.unknown.cause': 'এই কাজটিতে কিছু ভুল হয়েছে।',
'conversations.toolFailure.unknown.next':
'আবার চেষ্টা করুন; বারবার ব্যর্থ হলে সেটিংস থেকে ডায়াগনস্টিকস চালান।',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'মতামত দিন',
'feedback.board': 'মতামত বোর্ড',
+32
View File
@@ -39,6 +39,38 @@ const messages: TranslationMap = {
'crossHostVault.title': 'Der Vault liegt auf dem Core-Host.',
'crossHostVault.message':
'Dieser Memory-Vault wird auf dem openhuman-core-Host ({os}) gespeichert. Er kann nur auf diesem Rechner geöffnet oder angezeigt werden, nicht von diesem Gerät.',
'conversations.toolFailure.whyLabel': 'Warum',
'conversations.toolFailure.nextLabel': 'Nächste Schritte',
'conversations.toolFailure.missingPermission.cause':
'OpenHuman hat noch keine Berechtigung, dies zu tun.',
'conversations.toolFailure.missingPermission.next':
'Erteile die benötigte Berechtigung und versuche es erneut.',
'conversations.toolFailure.missingApp.cause':
'Die für diese Aktion benötigte App oder das Programm ist nicht verfügbar.',
'conversations.toolFailure.missingApp.next':
'Installiere oder öffne die App und versuche es erneut.',
'conversations.toolFailure.serviceUnavailable.cause':
'Ein von OpenHuman benötigter Dienst ist vorübergehend nicht verfügbar.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman versucht es in Kürze erneut nichts weiter nötig.',
'conversations.toolFailure.badCredentials.cause':
'Die gespeicherten Anmeldedaten fehlen oder sind nicht mehr gültig.',
'conversations.toolFailure.badCredentials.next':
'Melde dich erneut an oder aktualisiere die Anmeldedaten und versuche es erneut.',
'conversations.toolFailure.blockedByPolicy.cause':
'Diese Aktion wird durch deine Sicherheitseinstellungen blockiert.',
'conversations.toolFailure.blockedByPolicy.next':
'Erlaube sie unter Einstellungen → Agentenzugriff, wenn sie ausgeführt werden soll.',
'conversations.toolFailure.modelConnection.cause':
'OpenHuman konnte das KI-Modell nicht erreichen.',
'conversations.toolFailure.modelConnection.next':
'Prüfe deine Verbindung oder Modelleinstellungen; OpenHuman versucht es erneut.',
'conversations.toolFailure.timeout.cause': 'Die Aktion hat zu lange gedauert und wurde gestoppt.',
'conversations.toolFailure.timeout.next':
'OpenHuman versucht es erneut, oder du kannst es manuell wiederholen.',
'conversations.toolFailure.unknown.cause': 'Bei dieser Aktion ist etwas schiefgelaufen.',
'conversations.toolFailure.unknown.next':
'Versuche es erneut; wenn es weiterhin fehlschlägt, führe die Diagnose in den Einstellungen aus.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Feedback geben',
'feedback.board': 'Feedback-Board',
+31
View File
@@ -3813,6 +3813,37 @@ const en: TranslationMap = {
'conversations.subagent.cancel': 'Cancel task',
'conversations.subagent.cancelling': 'Cancelling…',
'conversations.subagent.cancelFailed': "Couldn't cancel the task. Try again.",
// Tool-failure explanation surfaced under a failed step in "View processing" (#4254).
'conversations.toolFailure.whyLabel': 'Why',
'conversations.toolFailure.nextLabel': 'What to do next',
'conversations.toolFailure.missingPermission.cause':
"OpenHuman doesn't have permission to do this yet.",
'conversations.toolFailure.missingPermission.next':
'Grant the permission it needs, then try again.',
'conversations.toolFailure.missingApp.cause':
"The app or program needed for this action isn't available.",
'conversations.toolFailure.missingApp.next': 'Install or open the app, then try again.',
'conversations.toolFailure.serviceUnavailable.cause':
'A service OpenHuman needs is temporarily unavailable.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman will try again shortly — no action needed.',
'conversations.toolFailure.badCredentials.cause':
'The saved sign-in details are missing or no longer valid.',
'conversations.toolFailure.badCredentials.next':
'Sign in again or update the credentials, then try again.',
'conversations.toolFailure.blockedByPolicy.cause':
'This action is blocked by your safety settings.',
'conversations.toolFailure.blockedByPolicy.next':
'Allow it in Settings → Agent access if you want it to run.',
'conversations.toolFailure.modelConnection.cause': "OpenHuman couldn't reach the AI model.",
'conversations.toolFailure.modelConnection.next':
'Check your connection or model settings; OpenHuman will retry.',
'conversations.toolFailure.timeout.cause': 'The action took too long and was stopped.',
'conversations.toolFailure.timeout.next':
'OpenHuman will try again, or you can retry it manually.',
'conversations.toolFailure.unknown.cause': 'Something went wrong with this action.',
'conversations.toolFailure.unknown.next':
'Try again; if it keeps failing, run diagnostics from Settings.',
'conversations.backgroundTasks.title': 'Background tasks',
'conversations.backgroundTasks.titleWithCount': 'Background tasks ({count})',
'conversations.backgroundTasks.running': '{count} running',
+31
View File
@@ -38,6 +38,37 @@ const messages: TranslationMap = {
'crossHostVault.title': 'El vault está en el host del core.',
'crossHostVault.message':
'Este vault de memoria se almacena en el host de openhuman-core ({os}). Solo se puede abrir o mostrar en esa máquina, no desde este dispositivo.',
'conversations.toolFailure.whyLabel': 'Por qué',
'conversations.toolFailure.nextLabel': 'Qué hacer a continuación',
'conversations.toolFailure.missingPermission.cause':
'OpenHuman aún no tiene permiso para hacer esto.',
'conversations.toolFailure.missingPermission.next':
'Concede el permiso que necesita y vuelve a intentarlo.',
'conversations.toolFailure.missingApp.cause':
'La app o el programa necesario para esta acción no está disponible.',
'conversations.toolFailure.missingApp.next': 'Instala o abre la app y vuelve a intentarlo.',
'conversations.toolFailure.serviceUnavailable.cause':
'Un servicio que OpenHuman necesita no está disponible temporalmente.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman lo intentará de nuevo en breve; no hace falta hacer nada.',
'conversations.toolFailure.badCredentials.cause':
'Los datos de inicio de sesión guardados faltan o ya no son válidos.',
'conversations.toolFailure.badCredentials.next':
'Inicia sesión de nuevo o actualiza las credenciales y vuelve a intentarlo.',
'conversations.toolFailure.blockedByPolicy.cause':
'Esta acción está bloqueada por tu configuración de seguridad.',
'conversations.toolFailure.blockedByPolicy.next':
'Permítela en Configuración → Acceso del agente si quieres que se ejecute.',
'conversations.toolFailure.modelConnection.cause':
'OpenHuman no pudo conectarse con el modelo de IA.',
'conversations.toolFailure.modelConnection.next':
'Comprueba tu conexión o la configuración del modelo; OpenHuman lo reintentará.',
'conversations.toolFailure.timeout.cause': 'La acción tardó demasiado y se detuvo.',
'conversations.toolFailure.timeout.next':
'OpenHuman lo intentará de nuevo, o puedes reintentarlo manualmente.',
'conversations.toolFailure.unknown.cause': 'Algo salió mal con esta acción.',
'conversations.toolFailure.unknown.next':
'Inténtalo de nuevo; si sigue fallando, ejecuta el diagnóstico desde Configuración.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Compartir opiniones',
'feedback.board': 'Tablero de opiniones',
+30
View File
@@ -39,6 +39,36 @@ const messages: TranslationMap = {
'crossHostVault.title': "Le coffre se trouve sur l'hôte du cœur.",
'crossHostVault.message':
"Ce coffre de mémoire est stocké sur l'hôte openhuman-core ({os}). Il ne peut être ouvert ou affiché que sur cette machine, pas depuis cet appareil.",
'conversations.toolFailure.whyLabel': 'Pourquoi',
'conversations.toolFailure.nextLabel': 'Que faire ensuite',
'conversations.toolFailure.missingPermission.cause':
"OpenHuman n'a pas encore l'autorisation de faire cela.",
'conversations.toolFailure.missingPermission.next':
"Accordez l'autorisation nécessaire, puis réessayez.",
'conversations.toolFailure.missingApp.cause':
"L'application ou le programme requis pour cette action n'est pas disponible.",
'conversations.toolFailure.missingApp.next': "Installez ou ouvrez l'application, puis réessayez.",
'conversations.toolFailure.serviceUnavailable.cause':
'Un service dont OpenHuman a besoin est temporairement indisponible.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman réessaiera bientôt — aucune action nécessaire.',
'conversations.toolFailure.badCredentials.cause':
'Les identifiants enregistrés sont manquants ou ne sont plus valides.',
'conversations.toolFailure.badCredentials.next':
'Reconnectez-vous ou mettez à jour les identifiants, puis réessayez.',
'conversations.toolFailure.blockedByPolicy.cause':
'Cette action est bloquée par vos paramètres de sécurité.',
'conversations.toolFailure.blockedByPolicy.next':
"Autorisez-la dans Paramètres → Accès de l'agent si vous voulez qu'elle s'exécute.",
'conversations.toolFailure.modelConnection.cause': "OpenHuman n'a pas pu joindre le modèle d'IA.",
'conversations.toolFailure.modelConnection.next':
'Vérifiez votre connexion ou les paramètres du modèle ; OpenHuman réessaiera.',
'conversations.toolFailure.timeout.cause': "L'action a pris trop de temps et a été arrêtée.",
'conversations.toolFailure.timeout.next':
"OpenHuman réessaiera, ou vous pouvez relancer l'action manuellement.",
'conversations.toolFailure.unknown.cause': 'Un problème est survenu avec cette action.',
'conversations.toolFailure.unknown.next':
"Réessayez ; si l'échec persiste, lancez le diagnostic depuis les Paramètres.",
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Donner mon avis',
'feedback.board': 'Tableau des suggestions',
+29
View File
@@ -37,6 +37,35 @@ const messages: TranslationMap = {
'crossHostVault.title': 'वॉल्ट कोर होस्ट पर है।',
'crossHostVault.message':
'यह मेमोरी वॉल्ट openhuman-core होस्ट ({os}) पर संग्रहीत है। इसे केवल उसी मशीन पर खोला या दिखाया जा सकता है, इस डिवाइस से नहीं।',
'conversations.toolFailure.whyLabel': 'क्यों',
'conversations.toolFailure.nextLabel': 'आगे क्या करें',
'conversations.toolFailure.missingPermission.cause':
'OpenHuman के पास अभी यह करने की अनुमति नहीं है।',
'conversations.toolFailure.missingPermission.next': 'आवश्यक अनुमति दें, फिर दोबारा प्रयास करें।',
'conversations.toolFailure.missingApp.cause':
'इस कार्य के लिए आवश्यक ऐप या प्रोग्राम उपलब्ध नहीं है।',
'conversations.toolFailure.missingApp.next': 'ऐप इंस्टॉल करें या खोलें, फिर दोबारा प्रयास करें।',
'conversations.toolFailure.serviceUnavailable.cause':
'OpenHuman को जिस सेवा की आवश्यकता है वह अस्थायी रूप से अनुपलब्ध है।',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman शीघ्र ही फिर से प्रयास करेगा — कोई कार्रवाई आवश्यक नहीं।',
'conversations.toolFailure.badCredentials.cause':
'सहेजी गई साइन-इन जानकारी गायब है या अब मान्य नहीं है।',
'conversations.toolFailure.badCredentials.next':
'फिर से साइन इन करें या क्रेडेंशियल अपडेट करें, फिर दोबारा प्रयास करें।',
'conversations.toolFailure.blockedByPolicy.cause':
'यह कार्य आपकी सुरक्षा सेटिंग्स द्वारा अवरुद्ध है।',
'conversations.toolFailure.blockedByPolicy.next':
'यदि आप इसे चलाना चाहते हैं तो इसे सेटिंग्स → एजेंट एक्सेस में अनुमति दें।',
'conversations.toolFailure.modelConnection.cause': 'OpenHuman AI मॉडल तक नहीं पहुँच सका।',
'conversations.toolFailure.modelConnection.next':
'अपना कनेक्शन या मॉडल सेटिंग्स जाँचें; OpenHuman फिर से प्रयास करेगा।',
'conversations.toolFailure.timeout.cause': 'कार्य में बहुत अधिक समय लगा और इसे रोक दिया गया।',
'conversations.toolFailure.timeout.next':
'OpenHuman फिर से प्रयास करेगा, या आप इसे मैन्युअल रूप से दोबारा कर सकते हैं।',
'conversations.toolFailure.unknown.cause': 'इस कार्य में कुछ गड़बड़ हो गई।',
'conversations.toolFailure.unknown.next':
'दोबारा प्रयास करें; यदि यह बार-बार विफल हो, तो सेटिंग्स से डायग्नोस्टिक्स चलाएँ।',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'फ़ीडबैक साझा करें',
'feedback.board': 'फ़ीडबैक बोर्ड',
+30
View File
@@ -38,6 +38,36 @@ const messages: TranslationMap = {
'crossHostVault.title': 'Vault berada di host core.',
'crossHostVault.message':
'Vault memori ini disimpan di host openhuman-core ({os}). Hanya dapat dibuka atau ditampilkan di mesin tersebut, bukan dari perangkat ini.',
'conversations.toolFailure.whyLabel': 'Mengapa',
'conversations.toolFailure.nextLabel': 'Yang harus dilakukan',
'conversations.toolFailure.missingPermission.cause':
'OpenHuman belum memiliki izin untuk melakukan ini.',
'conversations.toolFailure.missingPermission.next':
'Berikan izin yang diperlukan, lalu coba lagi.',
'conversations.toolFailure.missingApp.cause':
'Aplikasi atau program yang diperlukan untuk tindakan ini tidak tersedia.',
'conversations.toolFailure.missingApp.next': 'Instal atau buka aplikasinya, lalu coba lagi.',
'conversations.toolFailure.serviceUnavailable.cause':
'Layanan yang dibutuhkan OpenHuman sedang tidak tersedia untuk sementara.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman akan mencoba lagi sebentar lagi — tidak perlu tindakan apa pun.',
'conversations.toolFailure.badCredentials.cause':
'Detail masuk yang tersimpan tidak ada atau tidak lagi valid.',
'conversations.toolFailure.badCredentials.next':
'Masuk kembali atau perbarui kredensial, lalu coba lagi.',
'conversations.toolFailure.blockedByPolicy.cause':
'Tindakan ini diblokir oleh pengaturan keamanan Anda.',
'conversations.toolFailure.blockedByPolicy.next':
'Izinkan di Pengaturan → Akses agen jika Anda ingin menjalankannya.',
'conversations.toolFailure.modelConnection.cause': 'OpenHuman tidak dapat menjangkau model AI.',
'conversations.toolFailure.modelConnection.next':
'Periksa koneksi atau pengaturan model Anda; OpenHuman akan mencoba lagi.',
'conversations.toolFailure.timeout.cause': 'Tindakan memakan waktu terlalu lama dan dihentikan.',
'conversations.toolFailure.timeout.next':
'OpenHuman akan mencoba lagi, atau Anda dapat mengulanginya secara manual.',
'conversations.toolFailure.unknown.cause': 'Terjadi kesalahan pada tindakan ini.',
'conversations.toolFailure.unknown.next':
'Coba lagi; jika terus gagal, jalankan diagnostik dari Pengaturan.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Bagikan masukan',
'feedback.board': 'Papan masukan',
+32
View File
@@ -37,6 +37,38 @@ const messages: TranslationMap = {
'crossHostVault.title': "Il vault è sull'host del core.",
'crossHostVault.message':
"Questo vault di memoria è archiviato sull'host openhuman-core ({os}). Può essere aperto o mostrato solo su quella macchina, non da questo dispositivo.",
'conversations.toolFailure.whyLabel': 'Perché',
'conversations.toolFailure.nextLabel': 'Cosa fare ora',
'conversations.toolFailure.missingPermission.cause':
'OpenHuman non ha ancora il permesso di farlo.',
'conversations.toolFailure.missingPermission.next':
'Concedi il permesso necessario, poi riprova.',
'conversations.toolFailure.missingApp.cause':
"L'app o il programma necessario per questa azione non è disponibile.",
'conversations.toolFailure.missingApp.next': "Installa o apri l'app, poi riprova.",
'conversations.toolFailure.serviceUnavailable.cause':
'Un servizio necessario a OpenHuman è temporaneamente non disponibile.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman riproverà a breve — non serve fare nulla.',
'conversations.toolFailure.badCredentials.cause':
'I dati di accesso salvati mancano o non sono più validi.',
'conversations.toolFailure.badCredentials.next':
'Accedi di nuovo o aggiorna le credenziali, poi riprova.',
'conversations.toolFailure.blockedByPolicy.cause':
'Questa azione è bloccata dalle tue impostazioni di sicurezza.',
'conversations.toolFailure.blockedByPolicy.next':
"Consentila in Impostazioni → Accesso dell'agente se vuoi che venga eseguita.",
'conversations.toolFailure.modelConnection.cause':
'OpenHuman non è riuscito a raggiungere il modello di IA.',
'conversations.toolFailure.modelConnection.next':
'Controlla la connessione o le impostazioni del modello; OpenHuman riproverà.',
'conversations.toolFailure.timeout.cause':
"L'azione ha richiesto troppo tempo ed è stata interrotta.",
'conversations.toolFailure.timeout.next':
'OpenHuman riproverà, oppure puoi riprovare manualmente.',
'conversations.toolFailure.unknown.cause': 'Qualcosa è andato storto con questa azione.',
'conversations.toolFailure.unknown.next':
'Riprova; se continua a fallire, esegui la diagnostica dalle Impostazioni.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Condividi feedback',
'feedback.board': 'Bacheca dei feedback',
+28
View File
@@ -35,6 +35,34 @@ const messages: TranslationMap = {
'crossHostVault.title': '보관소가 코어 호스트에 있습니다.',
'crossHostVault.message':
'이 메모리 보관소는 openhuman-core 호스트({os})에 저장되어 있습니다. 해당 컴퓨터에서만 열거나 표시할 수 있으며 이 기기에서는 불가능합니다.',
'conversations.toolFailure.whyLabel': '이유',
'conversations.toolFailure.nextLabel': '다음 할 일',
'conversations.toolFailure.missingPermission.cause':
'OpenHuman에 아직 이 작업을 수행할 권한이 없습니다.',
'conversations.toolFailure.missingPermission.next': '필요한 권한을 부여한 후 다시 시도하세요.',
'conversations.toolFailure.missingApp.cause':
'이 작업에 필요한 앱 또는 프로그램을 사용할 수 없습니다.',
'conversations.toolFailure.missingApp.next': '앱을 설치하거나 연 후 다시 시도하세요.',
'conversations.toolFailure.serviceUnavailable.cause':
'OpenHuman에 필요한 서비스를 일시적으로 사용할 수 없습니다.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman이 곧 다시 시도합니다 — 별도의 조치가 필요하지 않습니다.',
'conversations.toolFailure.badCredentials.cause':
'저장된 로그인 정보가 없거나 더 이상 유효하지 않습니다.',
'conversations.toolFailure.badCredentials.next':
'다시 로그인하거나 자격 증명을 업데이트한 후 다시 시도하세요.',
'conversations.toolFailure.blockedByPolicy.cause': '이 작업은 보안 설정에 의해 차단되었습니다.',
'conversations.toolFailure.blockedByPolicy.next':
'실행하려면 설정 → 에이전트 액세스에서 허용하세요.',
'conversations.toolFailure.modelConnection.cause': 'OpenHuman이 AI 모델에 연결하지 못했습니다.',
'conversations.toolFailure.modelConnection.next':
'연결 또는 모델 설정을 확인하세요. OpenHuman이 다시 시도합니다.',
'conversations.toolFailure.timeout.cause': '작업이 너무 오래 걸려 중지되었습니다.',
'conversations.toolFailure.timeout.next':
'OpenHuman이 다시 시도하거나 수동으로 다시 실행할 수 있습니다.',
'conversations.toolFailure.unknown.cause': '이 작업에서 문제가 발생했습니다.',
'conversations.toolFailure.unknown.next':
'다시 시도하세요. 계속 실패하면 설정에서 진단을 실행하세요.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': '피드백 보내기',
'feedback.board': '피드백 보드',
+32
View File
@@ -39,6 +39,38 @@ const messages: TranslationMap = {
'crossHostVault.title': 'Skarbiec znajduje się na hoście rdzenia.',
'crossHostVault.message':
'Ten skarbiec pamięci jest przechowywany na hoście openhuman-core ({os}). Można go otworzyć lub pokazać tylko na tym komputerze, a nie z tego urządzenia.',
'conversations.toolFailure.whyLabel': 'Dlaczego',
'conversations.toolFailure.nextLabel': 'Co dalej',
'conversations.toolFailure.missingPermission.cause':
'OpenHuman nie ma jeszcze uprawnień, aby to zrobić.',
'conversations.toolFailure.missingPermission.next':
'Nadaj wymagane uprawnienie i spróbuj ponownie.',
'conversations.toolFailure.missingApp.cause':
'Aplikacja lub program potrzebny do tej czynności jest niedostępny.',
'conversations.toolFailure.missingApp.next':
'Zainstaluj lub otwórz aplikację i spróbuj ponownie.',
'conversations.toolFailure.serviceUnavailable.cause':
'Usługa, której potrzebuje OpenHuman, jest chwilowo niedostępna.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman spróbuje ponownie za chwilę — nie musisz nic robić.',
'conversations.toolFailure.badCredentials.cause':
'Zapisane dane logowania są niedostępne lub już nieważne.',
'conversations.toolFailure.badCredentials.next':
'Zaloguj się ponownie lub zaktualizuj dane logowania, a potem spróbuj jeszcze raz.',
'conversations.toolFailure.blockedByPolicy.cause':
'Ta czynność jest zablokowana przez Twoje ustawienia bezpieczeństwa.',
'conversations.toolFailure.blockedByPolicy.next':
'Zezwól na nią w Ustawienia → Dostęp agenta, jeśli ma zostać wykonana.',
'conversations.toolFailure.modelConnection.cause':
'OpenHuman nie mógł połączyć się z modelem AI.',
'conversations.toolFailure.modelConnection.next':
'Sprawdź połączenie lub ustawienia modelu; OpenHuman spróbuje ponownie.',
'conversations.toolFailure.timeout.cause': 'Czynność trwała zbyt długo i została zatrzymana.',
'conversations.toolFailure.timeout.next':
'OpenHuman spróbuje ponownie lub możesz powtórzyć ją ręcznie.',
'conversations.toolFailure.unknown.cause': 'Coś poszło nie tak z tą czynnością.',
'conversations.toolFailure.unknown.next':
'Spróbuj ponownie; jeśli nadal się nie udaje, uruchom diagnostykę w Ustawieniach.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Podziel się opinią',
'feedback.board': 'Tablica opinii',
+31
View File
@@ -37,6 +37,37 @@ const messages: TranslationMap = {
'crossHostVault.title': 'O vault está no host do core.',
'crossHostVault.message':
'Este vault de memória fica armazenado no host openhuman-core ({os}). Só pode ser aberto ou exibido nessa máquina, não a partir deste dispositivo.',
'conversations.toolFailure.whyLabel': 'Por quê',
'conversations.toolFailure.nextLabel': 'O que fazer a seguir',
'conversations.toolFailure.missingPermission.cause':
'O OpenHuman ainda não tem permissão para fazer isso.',
'conversations.toolFailure.missingPermission.next':
'Conceda a permissão necessária e tente novamente.',
'conversations.toolFailure.missingApp.cause':
'O app ou programa necessário para esta ação não está disponível.',
'conversations.toolFailure.missingApp.next': 'Instale ou abra o app e tente novamente.',
'conversations.toolFailure.serviceUnavailable.cause':
'Um serviço de que o OpenHuman precisa está temporariamente indisponível.',
'conversations.toolFailure.serviceUnavailable.next':
'O OpenHuman tentará de novo em breve — nenhuma ação necessária.',
'conversations.toolFailure.badCredentials.cause':
'Os dados de login salvos estão ausentes ou não são mais válidos.',
'conversations.toolFailure.badCredentials.next':
'Faça login novamente ou atualize as credenciais e tente de novo.',
'conversations.toolFailure.blockedByPolicy.cause':
'Esta ação está bloqueada pelas suas configurações de segurança.',
'conversations.toolFailure.blockedByPolicy.next':
'Permita-a em Configurações → Acesso do agente se quiser que ela seja executada.',
'conversations.toolFailure.modelConnection.cause':
'O OpenHuman não conseguiu acessar o modelo de IA.',
'conversations.toolFailure.modelConnection.next':
'Verifique sua conexão ou as configurações do modelo; o OpenHuman tentará novamente.',
'conversations.toolFailure.timeout.cause': 'A ação demorou demais e foi interrompida.',
'conversations.toolFailure.timeout.next':
'O OpenHuman tentará novamente, ou você pode tentar manualmente.',
'conversations.toolFailure.unknown.cause': 'Algo deu errado com esta ação.',
'conversations.toolFailure.unknown.next':
'Tente novamente; se continuar falhando, execute o diagnóstico nas Configurações.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Enviar feedback',
'feedback.board': 'Quadro de feedback',
+32
View File
@@ -39,6 +39,38 @@ const messages: TranslationMap = {
'crossHostVault.title': 'Хранилище находится на хосте ядра.',
'crossHostVault.message':
'Это хранилище памяти размещено на хосте openhuman-core ({os}). Его можно открыть или показать только на той машине, но не с этого устройства.',
'conversations.toolFailure.whyLabel': 'Почему',
'conversations.toolFailure.nextLabel': 'Что делать дальше',
'conversations.toolFailure.missingPermission.cause':
'У OpenHuman пока нет разрешения на это действие.',
'conversations.toolFailure.missingPermission.next':
'Предоставьте нужное разрешение и повторите попытку.',
'conversations.toolFailure.missingApp.cause':
'Приложение или программа, необходимые для этого действия, недоступны.',
'conversations.toolFailure.missingApp.next':
'Установите или откройте приложение и повторите попытку.',
'conversations.toolFailure.serviceUnavailable.cause':
'Служба, необходимая OpenHuman, временно недоступна.',
'conversations.toolFailure.serviceUnavailable.next':
'OpenHuman скоро повторит попытку — ничего делать не нужно.',
'conversations.toolFailure.badCredentials.cause':
'Сохранённые данные для входа отсутствуют или больше недействительны.',
'conversations.toolFailure.badCredentials.next':
'Войдите заново или обновите учётные данные, затем повторите попытку.',
'conversations.toolFailure.blockedByPolicy.cause':
'Это действие заблокировано вашими настройками безопасности.',
'conversations.toolFailure.blockedByPolicy.next':
'Разрешите его в разделе Настройки → Доступ агента, если хотите, чтобы оно выполнилось.',
'conversations.toolFailure.modelConnection.cause': 'OpenHuman не удалось связаться с ИИ-моделью.',
'conversations.toolFailure.modelConnection.next':
'Проверьте подключение или настройки модели; OpenHuman повторит попытку.',
'conversations.toolFailure.timeout.cause':
'Действие заняло слишком много времени и было остановлено.',
'conversations.toolFailure.timeout.next':
'OpenHuman повторит попытку, или вы можете повторить её вручную.',
'conversations.toolFailure.unknown.cause': 'С этим действием что-то пошло не так.',
'conversations.toolFailure.unknown.next':
'Повторите попытку; если ошибка повторяется, запустите диагностику в Настройках.',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': 'Поделиться отзывом',
'feedback.board': 'Доска отзывов',
+19
View File
@@ -33,6 +33,25 @@ const messages: TranslationMap = {
'crossHostVault.title': '记忆库位于核心主机上。',
'crossHostVault.message':
'此记忆库存储在 openhuman-core 主机({os})上。只能在该机器上打开或显示,无法从本设备访问。',
'conversations.toolFailure.whyLabel': '原因',
'conversations.toolFailure.nextLabel': '接下来该怎么做',
'conversations.toolFailure.missingPermission.cause': 'OpenHuman 目前还没有执行此操作的权限。',
'conversations.toolFailure.missingPermission.next': '授予所需权限,然后重试。',
'conversations.toolFailure.missingApp.cause': '此操作所需的应用或程序不可用。',
'conversations.toolFailure.missingApp.next': '安装或打开该应用,然后重试。',
'conversations.toolFailure.serviceUnavailable.cause': 'OpenHuman 需要的某项服务暂时不可用。',
'conversations.toolFailure.serviceUnavailable.next': 'OpenHuman 稍后会重试——无需任何操作。',
'conversations.toolFailure.badCredentials.cause': '保存的登录信息缺失或已失效。',
'conversations.toolFailure.badCredentials.next': '请重新登录或更新凭据,然后重试。',
'conversations.toolFailure.blockedByPolicy.cause': '此操作已被你的安全设置阻止。',
'conversations.toolFailure.blockedByPolicy.next': '如果希望执行,请在“设置 → 代理访问”中允许它。',
'conversations.toolFailure.modelConnection.cause': 'OpenHuman 无法连接到 AI 模型。',
'conversations.toolFailure.modelConnection.next':
'请检查你的网络连接或模型设置;OpenHuman 将会重试。',
'conversations.toolFailure.timeout.cause': '操作耗时过长,已被停止。',
'conversations.toolFailure.timeout.next': 'OpenHuman 会重试,你也可以手动重试。',
'conversations.toolFailure.unknown.cause': '此操作出现了问题。',
'conversations.toolFailure.unknown.next': '请重试;如果持续失败,请在设置中运行诊断。',
'conversations.backgroundTasks.title': 'Background tasks',
'nav.feedback': '分享反馈',
'feedback.board': '反馈板',
@@ -0,0 +1,74 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import type { ToolTimelineEntry } from '../../../store/chatRuntimeSlice';
import { ProcessingTranscriptView } from './ProcessingTranscriptView';
// Echo i18n: return the fallback when one is provided (so localized copy keys
// resolve to the English source we pass), otherwise the key itself. This lets
// us assert on the human strings carried on the failure payload.
vi.mock('../../../lib/i18n/I18nContext', () => ({
useT: () => ({ t: (key: string, fallback?: string) => fallback ?? key, locale: 'en' }),
}));
function failedEntry(overrides: Partial<ToolTimelineEntry> = {}): ToolTimelineEntry {
return {
id: 'call-1',
name: 'read_file',
round: 1,
status: 'error',
failure: {
class: 'MissingPermission',
category: 'NeedsUserConfirmation',
recoverable: false,
causePlain: 'No permission yet.',
nextAction: 'Grant it, then retry.',
},
...overrides,
};
}
describe('ProcessingTranscriptView tool failure explanation', () => {
it('renders the cause + next-action under a failed tool row', () => {
// Empty transcript → single tool group over all entries (legacy path).
render(<ProcessingTranscriptView transcript={[]} entries={[failedEntry()]} />);
const failure = screen.getByTestId('processing-tool-failure');
expect(failure).toBeTruthy();
expect(failure.textContent).toContain('No permission yet.');
expect(failure.textContent).toContain('Grant it, then retry.');
});
it('falls back to the plain English copy for an unrecognized class', () => {
render(
<ProcessingTranscriptView
transcript={[]}
entries={[
failedEntry({
failure: {
class: 'SomethingBrandNew',
category: 'Recoverable',
recoverable: true,
causePlain: 'Mystery cause.',
nextAction: 'Mystery next.',
},
}),
]}
/>
);
const failure = screen.getByTestId('processing-tool-failure');
expect(failure.textContent).toContain('Mystery cause.');
expect(failure.textContent).toContain('Mystery next.');
});
it('does not render the failure block for a successful entry', () => {
render(
<ProcessingTranscriptView
transcript={[]}
entries={[failedEntry({ status: 'success', failure: undefined })]}
/>
);
expect(screen.queryByTestId('processing-tool-failure')).toBeNull();
});
});
@@ -1,6 +1,7 @@
import { useT } from '../../../lib/i18n/I18nContext';
import type {
ProcessingTranscriptItem,
ToolFailureExplanation,
ToolTimelineEntry,
ToolTimelineEntryStatus,
} from '../../../store/chatRuntimeSlice';
@@ -112,6 +113,27 @@ function ToolGroupBlock({ summary, entries }: { summary: string; entries: ToolTi
);
}
/**
* The 8 failure classes the UI has localized copy for (#4254), keyed by the
* camelCase form of the wire's PascalCase `class`. Any class not in this set
* falls back to the English `causePlain` / `nextAction` carried on the payload.
*/
const LOCALIZED_FAILURE_CLASSES: ReadonlySet<string> = new Set([
'missingPermission',
'missingApp',
'serviceUnavailable',
'badCredentials',
'blockedByPolicy',
'modelConnection',
'timeout',
'unknown',
]);
/** Lowercase the first character: `MissingPermission` → `missingPermission`. */
function toCamelClass(cls: string): string {
return cls.length > 0 ? cls[0].toLowerCase() + cls.slice(1) : cls;
}
/** One tool step: type icon + human sentence + contextual detail chip. */
function ToolRow({ entry }: { entry: ToolTimelineEntry }) {
const { title, detail } = formatTimelineEntry(entry);
@@ -127,11 +149,44 @@ function ToolRow({ entry }: { entry: ToolTimelineEntry }) {
{detail}
</span>
) : null}
{entry.status === 'error' && entry.failure ? (
<ToolFailureLines failure={entry.failure} />
) : null}
</span>
</li>
);
}
/**
* The "why + what to do next" pair rendered under a failed tool row (#4254).
* Copy is resolved by failure class from i18n, falling back to the English
* `causePlain` / `nextAction` carried on the wire when the class is one the UI
* hasn't localized. The failure line is coral to match the error glyph.
*/
function ToolFailureLines({ failure }: { failure: ToolFailureExplanation }) {
const { t } = useT();
const camel = toCamelClass(failure.class);
const known = LOCALIZED_FAILURE_CLASSES.has(camel);
const cause = known
? t(`conversations.toolFailure.${camel}.cause`, failure.causePlain)
: failure.causePlain;
const next = known
? t(`conversations.toolFailure.${camel}.next`, failure.nextAction)
: failure.nextAction;
return (
<span
data-testid="processing-tool-failure"
className="mt-1 flex flex-col gap-0.5 text-[11px] leading-snug">
<span className="text-coral-600 dark:text-coral-300">
<span className="font-semibold">{t('conversations.toolFailure.whyLabel')}:</span> {cause}
</span>
<span className="text-content-muted">
<span className="font-semibold">{t('conversations.toolFailure.nextLabel')}:</span> {next}
</span>
</span>
);
}
/** Compact terminal status glyph for the group's "Done" line. */
function StatusGlyph({ status }: { status: ToolTimelineEntryStatus }) {
if (status === 'error') {
+8 -1
View File
@@ -39,6 +39,7 @@ import {
clearStreamingAssistantForThread,
endInferenceTurn,
markInferenceTurnStreaming,
parseToolFailure,
recordChatTurnUsage,
recordProcessingTool,
recordSubagentTranscriptTool,
@@ -586,6 +587,11 @@ const ChatRuntimeProvider = ({ children }: { children: React.ReactNode }) => {
)
return;
// On failure, parse the optional structured explanation (#4254) once so
// both the id-match and name/round-fallback paths can attach it. A
// successful result clears any stale failure carried on the row.
const failure = event.success ? undefined : parseToolFailure(event.failure);
const existing = store.getState().chatRuntime.toolTimelineByThread[event.thread_id] ?? [];
if (existing.length > 0) {
const nextEntries = [...existing];
@@ -597,6 +603,7 @@ const ChatRuntimeProvider = ({ children }: { children: React.ReactNode }) => {
nextEntries[idx] = {
...nextEntries[idx],
status: event.success ? 'success' : 'error',
failure,
};
changed = true;
}
@@ -610,7 +617,7 @@ const ChatRuntimeProvider = ({ children }: { children: React.ReactNode }) => {
entry.name === event.tool_name &&
entry.round === event.round
) {
nextEntries[i] = { ...entry, status: event.success ? 'success' : 'error' };
nextEntries[i] = { ...entry, status: event.success ? 'success' : 'error', failure };
changed = true;
break;
}
+9 -3
View File
@@ -49,6 +49,13 @@ export interface ChatToolResultEvent {
round: number;
/** Matches the id on the corresponding {@link ChatToolCallEvent}. */
tool_call_id?: string;
/**
* Optional structured failure explanation, present only when `success` is
* false (#4254). Raw snake_case wire object (`class`, `category`,
* `recoverable`, `cause_plain`, `next_action`); parsed defensively via
* `parseToolFailure` before it reaches the store.
*/
failure?: unknown;
}
/** One sub-agent's token/cost contribution within a turn (hover breakdown). */
@@ -1168,9 +1175,8 @@ export interface ChatSendParams {
/**
* Queue mode for concurrent messages. When a turn is already in
* flight: `steer` injects at the next iteration boundary, `followup`
* queues for after the turn, `collect` adds as context, and `parallel`
* starts a forked same-thread turn. `interrupt` (default) aborts the
* running turn.
* queues for after the turn, `collect` adds as context. `interrupt`
* (default) aborts the running turn.
*/
queueMode?: QueueMode | null;
}
@@ -0,0 +1,57 @@
import { describe, expect, it } from 'vitest';
import { parseToolFailure } from './chatRuntimeSlice';
describe('parseToolFailure', () => {
it('parses a well-formed snake_case wire object into camelCase', () => {
const parsed = parseToolFailure({
class: 'MissingPermission',
category: 'NeedsUserConfirmation',
recoverable: false,
cause_plain: "OpenHuman doesn't have permission to do this yet.",
next_action: 'Grant the permission it needs, then try again.',
});
expect(parsed).toEqual({
class: 'MissingPermission',
category: 'NeedsUserConfirmation',
recoverable: false,
causePlain: "OpenHuman doesn't have permission to do this yet.",
nextAction: 'Grant the permission it needs, then try again.',
});
});
it('accepts camelCase keys (persisted round-trip)', () => {
const parsed = parseToolFailure({
class: 'Timeout',
category: 'Recoverable',
recoverable: true,
causePlain: 'Took too long.',
nextAction: 'Retry.',
});
expect(parsed?.causePlain).toBe('Took too long.');
expect(parsed?.nextAction).toBe('Retry.');
expect(parsed?.recoverable).toBe(true);
});
it('defaults recoverable to false when absent or non-boolean', () => {
const parsed = parseToolFailure({
class: 'Unknown',
category: 'Recoverable',
cause_plain: 'Oops.',
next_action: 'Try again.',
});
expect(parsed?.recoverable).toBe(false);
});
it.each([
['null', null],
['undefined', undefined],
['a string', 'MissingPermission'],
['a number', 42],
['an empty object', {}],
['missing next_action', { class: 'X', category: 'Y', cause_plain: 'c' }],
['non-string class', { class: 1, category: 'Y', cause_plain: 'c', next_action: 'n' }],
])('returns undefined for garbage input (%s)', (_label, input) => {
expect(parseToolFailure(input)).toBeUndefined();
});
});
+63
View File
@@ -176,6 +176,59 @@ export interface SubagentToolCallEntry {
detail?: string;
}
/**
* Human-readable explanation for a FAILED tool call (#4254). Carried on the
* tool-completion socket event (optional `failure` object, snake_case on the
* wire) and surfaced in the "View processing" timeline as a "why" + "what to
* do next" pair. `class`/`category` come from the core's failure taxonomy;
* `causePlain`/`nextAction` are English fallbacks used when the class is not
* one the UI has localized copy for.
*/
export interface ToolFailureExplanation {
/** PascalCase failure class, e.g. `MissingPermission`, `Timeout`, `Unknown`. */
class: string;
/** `Recoverable` | `BlockedByPolicy` | `NeedsUserConfirmation`. */
category: string;
/** Whether the core considers the failure automatically recoverable. */
recoverable: boolean;
/** English fallback cause copy (used when `class` is unrecognized). */
causePlain: string;
/** English fallback next-action copy (used when `class` is unrecognized). */
nextAction: string;
}
/**
* Defensively parse an incoming `failure` object from a tool-completion socket
* payload (snake_case wire) or a persisted entry (camelCase) into a
* {@link ToolFailureExplanation}. Returns `undefined` for anything that is not
* a well-formed failure object so a malformed/partial payload never corrupts a
* timeline entry.
*/
export function parseToolFailure(raw: unknown): ToolFailureExplanation | undefined {
if (!raw || typeof raw !== 'object') return undefined;
const obj = raw as Record<string, unknown>;
const cls = obj.class;
const category = obj.category;
// Accept both wire (snake_case) and persisted (camelCase) key spellings.
const causePlain = obj.cause_plain ?? obj.causePlain;
const nextAction = obj.next_action ?? obj.nextAction;
if (
typeof cls !== 'string' ||
typeof category !== 'string' ||
typeof causePlain !== 'string' ||
typeof nextAction !== 'string'
) {
return undefined;
}
return {
class: cls,
category,
recoverable: typeof obj.recoverable === 'boolean' ? obj.recoverable : false,
causePlain,
nextAction,
};
}
export interface ToolTimelineEntry {
id: string;
name: string;
@@ -192,6 +245,13 @@ export interface ToolTimelineEntry {
* rows and for legacy snapshots emitted by older cores.
*/
subagent?: SubagentActivity;
/**
* Human-readable failure explanation for an `error` row (#4254). Parsed from
* the tool-completion socket event's optional `failure` object; absent on
* successful/running rows and on legacy snapshots. Preserved through the
* persisted round-trip so a reloaded failed turn keeps its explanation.
*/
failure?: ToolFailureExplanation;
}
export interface StreamingAssistantState {
@@ -669,6 +729,9 @@ function toolTimelineFromPersisted(entry: PersistedToolTimelineEntry): ToolTimel
detail: entry.detail,
sourceToolName: entry.sourceToolName,
subagent: entry.subagent ? subagentActivityFromPersisted(entry.subagent) : undefined,
// Carry a persisted failure explanation across the round-trip (#4254). The
// shared parser tolerates both camelCase (persisted) and snake_case (wire).
failure: parseToolFailure(entry.failure),
};
}
+15
View File
@@ -118,6 +118,20 @@ export interface PersistedSubagentActivity {
transcript?: PersistedSubagentTranscriptItem[];
}
/**
* Human-readable failure explanation persisted alongside a FAILED tool row
* (#4254). Mirrors the socket `failure` object; carried in the snapshot so a
* settled/reloaded turn keeps its "why + what to do next" explanation. Absent
* on successful rows and on snapshots written before this field.
*/
export interface PersistedToolFailure {
class: string;
category: string;
recoverable: boolean;
causePlain: string;
nextAction: string;
}
export interface PersistedToolTimelineEntry {
id: string;
name: string;
@@ -128,6 +142,7 @@ export interface PersistedToolTimelineEntry {
detail?: string;
sourceToolName?: string;
subagent?: PersistedSubagentActivity;
failure?: PersistedToolFailure;
}
export interface PersistedTurnState {