diff --git a/crates/localization/locales/ca.json b/crates/localization/locales/ca.json index 8bb4eaf8e0..bc700165c3 100644 --- a/crates/localization/locales/ca.json +++ b/crates/localization/locales/ca.json @@ -1213,6 +1213,36 @@ "NotificationSubagentCancelled": "Subagent cancel·lat", "NotificationSubagentBudgetExhausted": "Pressupost del subagent esgotat", "FooterWorkedChip": "ha treballat {duration}", + "FooterPermissionKeyHint": "{key} per canviar", + "ApprovalSummaryRunCommand": "Executar `{command}`", + "ApprovalSummaryRunShell": "Executar una ordre de shell", + "ApprovalSummaryShellWait": "Esperar una ordre de shell en execució", + "ApprovalSummaryShellInput": "Enviar entrada a una ordre de shell en execució", + "ApprovalSummaryShellStop": "Aturar una ordre de shell en execució", + "ApprovalSummaryWritePath": "Escriure {path}", + "ApprovalSummaryWriteFile": "Escriure un fitxer", + "ApprovalSummaryEditPath": "Editar {path}", + "ApprovalSummaryEditFile": "Editar un fitxer", + "ApprovalSummaryEditPathMore": "Editar {path} i {count} fitxer(s) més", + "ApprovalSummaryApplyPatch": "Aplicar un pedaç", + "ApprovalSummaryReadPath": "Llegir {path}", + "ApprovalSummaryReadFile": "Llegir un fitxer", + "ApprovalSummaryListPath": "Llistar {path}", + "ApprovalSummaryListWorkspace": "Llistar l'espai de treball", + "ApprovalSummaryFetchUrl": "Obtenir {url}", + "ApprovalSummaryFetchPage": "Obtenir una pàgina web", + "ApprovalSummarySearchQuery": "Cercar '{query}' al web", + "ApprovalSummarySearchWeb": "Cercar al web", + "ApprovalSummaryImageQuery": "Cercar imatges de '{query}' al web", + "ApprovalSummaryOpenTarget": "Obrir {target}", + "ApprovalSummaryMore": " (+{count} més)", + "ApprovalSummaryFollowLink": "Seguir un enllaç d'una pàgina oberta", + "ApprovalSummaryFindOnPage": "Cercar '{pattern}' en una pàgina oberta", + "ApprovalSummaryScreenshot": "Fer una captura d'una pàgina oberta", + "ApprovalSummaryBrowse": "Navegar pel web", + "ApprovalSummaryMcpTool": "Fer servir {tool} de {server}", + "ApprovalSummaryUseName": "Fer servir {name}", + "ApprovalSummaryUseTool": "Fer servir l'eina {name}", "FleetDraftTitle": "Perfil d'equip — esborrany de {model_label} (g desa)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Esborrany de {model_label}, validat i limitat per Codewhale.\n# Els permisos es mantenen al mínim de l'equip: sense shell, sense confiança, aprovació requerida.\n# No es desa res fins que premis g a l'assistent.\n\n", "SetupRemoteOnRampText": "Incorporació al runtime remot\n\n/setup només mostra dades del runtime remot. No genera paquets de desplegament, no escriu credencials, no crida CLI de núvol ni executa `remote-setup`.\n\nDades actuals:\n- Núvols: {clouds_result}\n- Ponts de xat: {bridges_result}\n- Proveïdors: {providers_result}\n- Mode: {mode_result}\n\nPer generar un paquet de desplegament, executa explícitament en un terminal normal:\n\n```sh\n{command}\n```\n\nEl RUNBOOK generat inclou passos de l'amfitrió per a revisió humana. `--apply` continua sense implementar; no ho tractis com un desplegament automàtic.", diff --git a/crates/localization/locales/de.json b/crates/localization/locales/de.json index a4d7c7b56c..28b046d3d0 100644 --- a/crates/localization/locales/de.json +++ b/crates/localization/locales/de.json @@ -1213,6 +1213,36 @@ "NotificationSubagentCancelled": "Subagent abgebrochen", "NotificationSubagentBudgetExhausted": "Subagent-Budget erschöpft", "FooterWorkedChip": "{duration} gearbeitet", + "FooterPermissionKeyHint": "{key} zum Ändern", + "ApprovalSummaryRunCommand": "`{command}` ausführen", + "ApprovalSummaryRunShell": "Einen Shell-Befehl ausführen", + "ApprovalSummaryShellWait": "Auf einen laufenden Shell-Befehl warten", + "ApprovalSummaryShellInput": "Eingabe an einen laufenden Shell-Befehl senden", + "ApprovalSummaryShellStop": "Einen laufenden Shell-Befehl stoppen", + "ApprovalSummaryWritePath": "{path} schreiben", + "ApprovalSummaryWriteFile": "Eine Datei schreiben", + "ApprovalSummaryEditPath": "{path} bearbeiten", + "ApprovalSummaryEditFile": "Eine Datei bearbeiten", + "ApprovalSummaryEditPathMore": "{path} und {count} weitere Datei(en) bearbeiten", + "ApprovalSummaryApplyPatch": "Einen Patch anwenden", + "ApprovalSummaryReadPath": "{path} lesen", + "ApprovalSummaryReadFile": "Eine Datei lesen", + "ApprovalSummaryListPath": "{path} auflisten", + "ApprovalSummaryListWorkspace": "Den Arbeitsbereich auflisten", + "ApprovalSummaryFetchUrl": "{url} abrufen", + "ApprovalSummaryFetchPage": "Eine Webseite abrufen", + "ApprovalSummarySearchQuery": "Im Web nach '{query}' suchen", + "ApprovalSummarySearchWeb": "Im Web suchen", + "ApprovalSummaryImageQuery": "Im Web nach Bildern von '{query}' suchen", + "ApprovalSummaryOpenTarget": "{target} öffnen", + "ApprovalSummaryMore": " (+{count} weitere)", + "ApprovalSummaryFollowLink": "Einem Link auf einer geöffneten Seite folgen", + "ApprovalSummaryFindOnPage": "'{pattern}' auf einer geöffneten Seite suchen", + "ApprovalSummaryScreenshot": "Einen Screenshot einer geöffneten Seite aufnehmen", + "ApprovalSummaryBrowse": "Im Web surfen", + "ApprovalSummaryMcpTool": "{tool} von {server} verwenden", + "ApprovalSummaryUseName": "{name} verwenden", + "ApprovalSummaryUseTool": "Das Werkzeug {name} verwenden", "FleetDraftTitle": "Team-Profil — Entwurf von {model_label} (g speichert)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Entwurf von {model_label}, validiert und begrenzt durch Codewhale.\n# Berechtigungen bleiben auf Team-Minimum: keine Shell, kein Vertrauen, Freigabe erforderlich.\n# Es wird nichts gespeichert, bis Sie im Wizard g drücken.\n\n", "SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup zeigt nur Fakten zur Remote-Runtime. Es erzeugt keine Deploy-Bundles, schreibt keine Zugangsdaten, ruft keine Cloud-CLIs auf und führt `remote-setup` nicht aus.\n\nAktuelle Fakten:\n- Clouds: {clouds_result}\n- Chat-Bridges: {bridges_result}\n- Provider: {providers_result}\n- Modus: {mode_result}\n\nUm ein Deploy-Bundle zu erzeugen, explizit in einem normalen Terminal ausführen:\n\n```sh\n{command}\n```\n\nDas erzeugte RUNBOOK enthält Host-Schritte zur menschlichen Prüfung. `--apply` bleibt unimplementiert; nicht als Auto-Deploy behandeln.", diff --git a/crates/localization/locales/en.json b/crates/localization/locales/en.json index 51db5cc060..419c4f65d1 100644 --- a/crates/localization/locales/en.json +++ b/crates/localization/locales/en.json @@ -1236,6 +1236,36 @@ "NotificationSubagentCancelled": "Agent cancelled", "NotificationSubagentBudgetExhausted": "Agent budget exhausted", "FooterWorkedChip": "worked {duration}", + "FooterPermissionKeyHint": "{key} to change", + "ApprovalSummaryRunCommand": "Run `{command}`", + "ApprovalSummaryRunShell": "Run a shell command", + "ApprovalSummaryShellWait": "Wait for a running shell command", + "ApprovalSummaryShellInput": "Send input to a running shell command", + "ApprovalSummaryShellStop": "Stop a running shell command", + "ApprovalSummaryWritePath": "Write {path}", + "ApprovalSummaryWriteFile": "Write a file", + "ApprovalSummaryEditPath": "Edit {path}", + "ApprovalSummaryEditFile": "Edit a file", + "ApprovalSummaryEditPathMore": "Edit {path} and {count} more file(s)", + "ApprovalSummaryApplyPatch": "Apply a patch", + "ApprovalSummaryReadPath": "Read {path}", + "ApprovalSummaryReadFile": "Read a file", + "ApprovalSummaryListPath": "List {path}", + "ApprovalSummaryListWorkspace": "List the workspace", + "ApprovalSummaryFetchUrl": "Fetch {url}", + "ApprovalSummaryFetchPage": "Fetch a web page", + "ApprovalSummarySearchQuery": "Search the web for '{query}'", + "ApprovalSummarySearchWeb": "Search the web", + "ApprovalSummaryImageQuery": "Search the web for images of '{query}'", + "ApprovalSummaryOpenTarget": "Open {target}", + "ApprovalSummaryMore": " (+{count} more)", + "ApprovalSummaryFollowLink": "Follow a link on an opened page", + "ApprovalSummaryFindOnPage": "Find '{pattern}' on an opened page", + "ApprovalSummaryScreenshot": "Take a screenshot of an opened page", + "ApprovalSummaryBrowse": "Browse the web", + "ApprovalSummaryMcpTool": "Use {tool} from {server}", + "ApprovalSummaryUseName": "Use {name}", + "ApprovalSummaryUseTool": "Use the {name} tool", "FleetDraftTitle": "team profile — draft by {model_label} (g saves)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Drafted by {model_label}, validated and bounded by Codewhale.\n# Permissions stay at the team floor: no shell, no trust, approval required.\n# Nothing is saved until you press g in the wizard.\n\n", "SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup shows remote runtime facts only. It does not generate deploy bundles, write credentials, call cloud CLIs, or run `remote-setup`.\n\nCurrent facts:\n- Clouds: {clouds_result}\n- Chat bridges: {bridges_result}\n- Providers: {providers_result}\n- Mode: {mode_result}\n\nTo generate a deploy bundle, run explicitly in a normal terminal:\n\n```sh\n{command}\n```\n\nThe generated RUNBOOK includes host steps for human review. `--apply` remains unimplemented; do not treat this as auto-deploy.", diff --git a/crates/localization/locales/es-419.json b/crates/localization/locales/es-419.json index 62cf9f860a..bc2619f8b0 100644 --- a/crates/localization/locales/es-419.json +++ b/crates/localization/locales/es-419.json @@ -1234,6 +1234,36 @@ "NotificationSubagentCancelled": "Subagente cancelado", "NotificationSubagentBudgetExhausted": "Presupuesto del subagente agotado", "FooterWorkedChip": "trabajó {duration}", + "FooterPermissionKeyHint": "{key} para cambiar", + "ApprovalSummaryRunCommand": "Ejecutar `{command}`", + "ApprovalSummaryRunShell": "Ejecutar un comando de shell", + "ApprovalSummaryShellWait": "Esperar un comando de shell en ejecución", + "ApprovalSummaryShellInput": "Enviar entrada a un comando de shell en ejecución", + "ApprovalSummaryShellStop": "Detener un comando de shell en ejecución", + "ApprovalSummaryWritePath": "Escribir {path}", + "ApprovalSummaryWriteFile": "Escribir un archivo", + "ApprovalSummaryEditPath": "Editar {path}", + "ApprovalSummaryEditFile": "Editar un archivo", + "ApprovalSummaryEditPathMore": "Editar {path} y {count} archivo(s) más", + "ApprovalSummaryApplyPatch": "Aplicar un parche", + "ApprovalSummaryReadPath": "Leer {path}", + "ApprovalSummaryReadFile": "Leer un archivo", + "ApprovalSummaryListPath": "Listar {path}", + "ApprovalSummaryListWorkspace": "Listar el espacio de trabajo", + "ApprovalSummaryFetchUrl": "Obtener {url}", + "ApprovalSummaryFetchPage": "Obtener una página web", + "ApprovalSummarySearchQuery": "Buscar '{query}' en la web", + "ApprovalSummarySearchWeb": "Buscar en la web", + "ApprovalSummaryImageQuery": "Buscar imágenes de '{query}' en la web", + "ApprovalSummaryOpenTarget": "Abrir {target}", + "ApprovalSummaryMore": " (+{count} más)", + "ApprovalSummaryFollowLink": "Seguir un enlace de una página abierta", + "ApprovalSummaryFindOnPage": "Buscar '{pattern}' en una página abierta", + "ApprovalSummaryScreenshot": "Tomar una captura de una página abierta", + "ApprovalSummaryBrowse": "Navegar por la web", + "ApprovalSummaryMcpTool": "Usar {tool} de {server}", + "ApprovalSummaryUseName": "Usar {name}", + "ApprovalSummaryUseTool": "Usar la herramienta {name}", "FleetDraftTitle": "Perfil de equipo — borrador de {model_label} (g para guardar)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Borrador por {model_label}, validado y limitado por Codewhale.\n# Permisos en el piso del equipo: sin shell, sin confianza, aprobación requerida.\n# Nada se guarda hasta que presione g en el asistente.\n\n", "SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup solo muestra datos del runtime remoto. No genera bundles, no escribe credenciales, no llama CLIs de cloud ni ejecuta `remote-setup`.\n\nDatos actuales:\n- Clouds: {clouds_result}\n- Puentes: {bridges_result}\n- Proveedores: {providers_result}\n- Modo: {mode_result}\n\nPara generar un bundle de deploy, ejecute explícitamente en un terminal normal:\n\n```sh\n{command}\n```\n\nEl RUNBOOK generado contiene los pasos de host para revisión humana. `--apply` sigue sin implementarse; no trate esto como auto-deploy.", diff --git a/crates/localization/locales/fr.json b/crates/localization/locales/fr.json index 1c282a64d5..2faee6c696 100644 --- a/crates/localization/locales/fr.json +++ b/crates/localization/locales/fr.json @@ -1213,6 +1213,36 @@ "NotificationSubagentCancelled": "Sous-agent annulé", "NotificationSubagentBudgetExhausted": "Budget du sous-agent épuisé", "FooterWorkedChip": "a travaillé {duration}", + "FooterPermissionKeyHint": "{key} pour changer", + "ApprovalSummaryRunCommand": "Exécuter `{command}`", + "ApprovalSummaryRunShell": "Exécuter une commande shell", + "ApprovalSummaryShellWait": "Attendre une commande shell en cours", + "ApprovalSummaryShellInput": "Envoyer une entrée à une commande shell en cours", + "ApprovalSummaryShellStop": "Arrêter une commande shell en cours", + "ApprovalSummaryWritePath": "Écrire {path}", + "ApprovalSummaryWriteFile": "Écrire un fichier", + "ApprovalSummaryEditPath": "Modifier {path}", + "ApprovalSummaryEditFile": "Modifier un fichier", + "ApprovalSummaryEditPathMore": "Modifier {path} et {count} autre(s) fichier(s)", + "ApprovalSummaryApplyPatch": "Appliquer un correctif", + "ApprovalSummaryReadPath": "Lire {path}", + "ApprovalSummaryReadFile": "Lire un fichier", + "ApprovalSummaryListPath": "Lister {path}", + "ApprovalSummaryListWorkspace": "Lister l'espace de travail", + "ApprovalSummaryFetchUrl": "Récupérer {url}", + "ApprovalSummaryFetchPage": "Récupérer une page web", + "ApprovalSummarySearchQuery": "Rechercher '{query}' sur le web", + "ApprovalSummarySearchWeb": "Rechercher sur le web", + "ApprovalSummaryImageQuery": "Rechercher des images de '{query}' sur le web", + "ApprovalSummaryOpenTarget": "Ouvrir {target}", + "ApprovalSummaryMore": " (+{count} de plus)", + "ApprovalSummaryFollowLink": "Suivre un lien sur une page ouverte", + "ApprovalSummaryFindOnPage": "Chercher '{pattern}' sur une page ouverte", + "ApprovalSummaryScreenshot": "Faire une capture d'une page ouverte", + "ApprovalSummaryBrowse": "Naviguer sur le web", + "ApprovalSummaryMcpTool": "Utiliser {tool} de {server}", + "ApprovalSummaryUseName": "Utiliser {name}", + "ApprovalSummaryUseTool": "Utiliser l'outil {name}", "FleetDraftTitle": "Profil d'équipe — brouillon par {model_label} (g enregistre)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Rédigé par {model_label}, validé et borné par Codewhale.\n# Les permissions restent au plancher de l'équipe : pas de shell, pas de confiance, approbation requise.\n# Rien n'est enregistré tant que vous n'appuyez pas sur g dans l'assistant.\n\n", "SetupRemoteOnRampText": "Amorçage du runtime distant\n\n/setup n'affiche que les faits du runtime distant. Il ne génère pas de bundle de déploiement, n'écrit pas d'identifiants, n'appelle pas de CLI cloud et n'exécute pas `remote-setup`.\n\nFaits actuels :\n- Clouds : {clouds_result}\n- Ponts de chat : {bridges_result}\n- Fournisseurs : {providers_result}\n- Mode : {mode_result}\n\nPour générer un bundle de déploiement, exécutez explicitement dans un terminal normal :\n\n```sh\n{command}\n```\n\nLe RUNBOOK généré inclut les étapes hôte pour revue humaine. `--apply` reste non implémenté ; ne le considérez pas comme un déploiement automatique.", diff --git a/crates/localization/locales/hi.json b/crates/localization/locales/hi.json index 72674c7f11..e47b375397 100644 --- a/crates/localization/locales/hi.json +++ b/crates/localization/locales/hi.json @@ -1213,6 +1213,36 @@ "NotificationSubagentCancelled": "सब-एजेंट रद्द", "NotificationSubagentBudgetExhausted": "सब-एजेंट बजट समाप्त", "FooterWorkedChip": "{duration} काम किया", + "FooterPermissionKeyHint": "बदलने के लिए {key}", + "ApprovalSummaryRunCommand": "`{command}` चलाएँ", + "ApprovalSummaryRunShell": "एक शेल कमांड चलाएँ", + "ApprovalSummaryShellWait": "चल रहे शेल कमांड की प्रतीक्षा करें", + "ApprovalSummaryShellInput": "चल रहे शेल कमांड को इनपुट भेजें", + "ApprovalSummaryShellStop": "चल रहा शेल कमांड रोकें", + "ApprovalSummaryWritePath": "{path} लिखें", + "ApprovalSummaryWriteFile": "एक फ़ाइल लिखें", + "ApprovalSummaryEditPath": "{path} संपादित करें", + "ApprovalSummaryEditFile": "एक फ़ाइल संपादित करें", + "ApprovalSummaryEditPathMore": "{path} और {count} अन्य फ़ाइलें संपादित करें", + "ApprovalSummaryApplyPatch": "एक पैच लागू करें", + "ApprovalSummaryReadPath": "{path} पढ़ें", + "ApprovalSummaryReadFile": "एक फ़ाइल पढ़ें", + "ApprovalSummaryListPath": "{path} की सूची देखें", + "ApprovalSummaryListWorkspace": "वर्कस्पेस की सूची देखें", + "ApprovalSummaryFetchUrl": "{url} प्राप्त करें", + "ApprovalSummaryFetchPage": "एक वेब पेज प्राप्त करें", + "ApprovalSummarySearchQuery": "वेब पर '{query}' खोजें", + "ApprovalSummarySearchWeb": "वेब पर खोजें", + "ApprovalSummaryImageQuery": "वेब पर '{query}' की तस्वीरें खोजें", + "ApprovalSummaryOpenTarget": "{target} खोलें", + "ApprovalSummaryMore": " (+{count} और)", + "ApprovalSummaryFollowLink": "खुले पेज पर एक लिंक खोलें", + "ApprovalSummaryFindOnPage": "खुले पेज पर '{pattern}' ढूँढें", + "ApprovalSummaryScreenshot": "खुले पेज का स्क्रीनशॉट लें", + "ApprovalSummaryBrowse": "वेब ब्राउज़ करें", + "ApprovalSummaryMcpTool": "{server} से {tool} का उपयोग करें", + "ApprovalSummaryUseName": "{name} का उपयोग करें", + "ApprovalSummaryUseTool": "{name} टूल का उपयोग करें", "FleetDraftTitle": "टीम प्रोफ़ाइल — {model_label} का मसौदा (g से सहेजें)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label} द्वारा मसौदा, Codewhale द्वारा सत्यापित और सीमाबद्ध।\n# अनुमतियाँ टीम न्यूनतम स्तर पर रहती हैं: कोई शेल नहीं, कोई ट्रस्ट नहीं, अनुमति आवश्यक।\n# जब तक आप विज़ार्ड में g नहीं दबाते, कुछ सहेजा नहीं जाता।\n\n", "SetupRemoteOnRampText": "रिमोट रनटाइम ऑन-रैंप\n\n/setup केवल रिमोट रनटाइम तथ्य दिखाता है। यह डिप्लॉय बंडल नहीं बनाता, क्रेडेंशल नहीं लिखता, क्लाउड CLI नहीं बुलाता, और `remote-setup` नहीं चलाता।\n\nवर्तमान तथ्य:\n- क्लाउड: {clouds_result}\n- चैट ब्रिज: {bridges_result}\n- प्रोवाइडर: {providers_result}\n- मोड: {mode_result}\n\nडिप्लॉय बंडल बनाने के लिए, सामान्य टर्मिनल में स्पष्ट रूप से चलाएँ:\n\n```sh\n{command}\n```\n\nबनाया गया RUNBOOK मानव समीक्षा के लिए होस्ट चरण शामिल करता है। `--apply` अभी लागू नहीं है; इसे ऑटो-डिप्लॉय न समझें।", diff --git a/crates/localization/locales/id.json b/crates/localization/locales/id.json index 6db2c5cb67..469f3f8b18 100644 --- a/crates/localization/locales/id.json +++ b/crates/localization/locales/id.json @@ -1213,6 +1213,36 @@ "NotificationSubagentCancelled": "Sub-agent dibatalkan", "NotificationSubagentBudgetExhausted": "Anggaran sub-agent habis", "FooterWorkedChip": "bekerja {duration}", + "FooterPermissionKeyHint": "{key} untuk mengubah", + "ApprovalSummaryRunCommand": "Jalankan `{command}`", + "ApprovalSummaryRunShell": "Jalankan perintah shell", + "ApprovalSummaryShellWait": "Tunggu perintah shell yang sedang berjalan", + "ApprovalSummaryShellInput": "Kirim masukan ke perintah shell yang sedang berjalan", + "ApprovalSummaryShellStop": "Hentikan perintah shell yang sedang berjalan", + "ApprovalSummaryWritePath": "Tulis {path}", + "ApprovalSummaryWriteFile": "Tulis sebuah file", + "ApprovalSummaryEditPath": "Sunting {path}", + "ApprovalSummaryEditFile": "Sunting sebuah file", + "ApprovalSummaryEditPathMore": "Sunting {path} dan {count} file lainnya", + "ApprovalSummaryApplyPatch": "Terapkan patch", + "ApprovalSummaryReadPath": "Baca {path}", + "ApprovalSummaryReadFile": "Baca sebuah file", + "ApprovalSummaryListPath": "Daftar isi {path}", + "ApprovalSummaryListWorkspace": "Daftar isi ruang kerja", + "ApprovalSummaryFetchUrl": "Ambil {url}", + "ApprovalSummaryFetchPage": "Ambil halaman web", + "ApprovalSummarySearchQuery": "Cari '{query}' di web", + "ApprovalSummarySearchWeb": "Cari di web", + "ApprovalSummaryImageQuery": "Cari gambar '{query}' di web", + "ApprovalSummaryOpenTarget": "Buka {target}", + "ApprovalSummaryMore": " (+{count} lainnya)", + "ApprovalSummaryFollowLink": "Ikuti tautan di halaman yang dibuka", + "ApprovalSummaryFindOnPage": "Cari '{pattern}' di halaman yang dibuka", + "ApprovalSummaryScreenshot": "Ambil tangkapan layar halaman yang dibuka", + "ApprovalSummaryBrowse": "Jelajahi web", + "ApprovalSummaryMcpTool": "Gunakan {tool} dari {server}", + "ApprovalSummaryUseName": "Gunakan {name}", + "ApprovalSummaryUseTool": "Gunakan alat {name}", "FleetDraftTitle": "Profil tim — draf oleh {model_label} (g menyimpan)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Draf oleh {model_label}, divalidasi dan dibatasi oleh Codewhale.\n# Izin tetap di batas dasar tim: tanpa shell, tanpa trust, perlu persetujuan.\n# Tidak ada yang disimpan sampai Anda menekan g di wizard.\n\n", "SetupRemoteOnRampText": "Jalur Awal Runtime Remote\n\n/setup hanya menampilkan fakta runtime remote. Tidak membuat bundle deploy, menulis kredensial, memanggil CLI cloud, atau menjalankan `remote-setup`.\n\nFakta saat ini:\n- Cloud: {clouds_result}\n- Bridge chat: {bridges_result}\n- Provider: {providers_result}\n- Mode: {mode_result}\n\nUntuk membuat bundle deploy, jalankan secara eksplisit di terminal normal:\n\n```sh\n{command}\n```\n\nRUNBOOK yang dihasilkan mencakup langkah host untuk ditinjau manusia. `--apply` belum diimplementasikan; jangan anggap ini auto-deploy.", diff --git a/crates/localization/locales/ja.json b/crates/localization/locales/ja.json index cdb40d9ba5..54eea3e74b 100644 --- a/crates/localization/locales/ja.json +++ b/crates/localization/locales/ja.json @@ -1234,6 +1234,36 @@ "NotificationSubagentCancelled": "サブエージェントキャンセル", "NotificationSubagentBudgetExhausted": "サブエージェントの予算上限到達", "FooterWorkedChip": "経過{duration}", + "FooterPermissionKeyHint": "{key}で変更", + "ApprovalSummaryRunCommand": "`{command}` を実行", + "ApprovalSummaryRunShell": "シェルコマンドを実行", + "ApprovalSummaryShellWait": "実行中のシェルコマンドを待機", + "ApprovalSummaryShellInput": "実行中のシェルコマンドに入力を送信", + "ApprovalSummaryShellStop": "実行中のシェルコマンドを停止", + "ApprovalSummaryWritePath": "{path} を書き込み", + "ApprovalSummaryWriteFile": "ファイルを書き込み", + "ApprovalSummaryEditPath": "{path} を編集", + "ApprovalSummaryEditFile": "ファイルを編集", + "ApprovalSummaryEditPathMore": "{path} と他 {count} 件のファイルを編集", + "ApprovalSummaryApplyPatch": "パッチを適用", + "ApprovalSummaryReadPath": "{path} を読み取り", + "ApprovalSummaryReadFile": "ファイルを読み取り", + "ApprovalSummaryListPath": "{path} の一覧を表示", + "ApprovalSummaryListWorkspace": "ワークスペースの一覧を表示", + "ApprovalSummaryFetchUrl": "{url} を取得", + "ApprovalSummaryFetchPage": "Webページを取得", + "ApprovalSummarySearchQuery": "Webで「{query}」を検索", + "ApprovalSummarySearchWeb": "Webを検索", + "ApprovalSummaryImageQuery": "Webで「{query}」の画像を検索", + "ApprovalSummaryOpenTarget": "{target} を開く", + "ApprovalSummaryMore": "(他 {count} 件)", + "ApprovalSummaryFollowLink": "開いたページのリンクをたどる", + "ApprovalSummaryFindOnPage": "開いたページで「{pattern}」を検索", + "ApprovalSummaryScreenshot": "開いたページのスクリーンショットを撮る", + "ApprovalSummaryBrowse": "Webを閲覧", + "ApprovalSummaryMcpTool": "{server} の {tool} を使用", + "ApprovalSummaryUseName": "{name} を使用", + "ApprovalSummaryUseTool": "{name} ツールを使用", "FleetDraftTitle": "チーム 設定 — {model_label} によるドラフト(g で保存)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label} が作成し、Codewhale が検証・制限を適用しました。\n# 権限は チーム の下限に維持されます:シェル不可、信頼不可、承認必須。\n# ウィザードで g を押すまで何も保存されません。\n\n", "SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup はリモートランタイムの事実だけを表示します。デプロイバンドルの生成、認証情報の書き込み、クラウド CLI の呼び出し、`remote-setup` の実行は行いません。\n\n現在の事実:\n- クラウド: {clouds_result}\n- ブリッジ: {bridges_result}\n- プロバイダー: {providers_result}\n- モード: {mode_result}\n\nデプロイバンドルを生成する場合は、通常の端末で明示的に実行してください:\n\n```sh\n{command}\n```\n\n生成された RUNBOOK には人間が確認するホスト手順が含まれます。`--apply` は未実装です。自動デプロイとして扱わないでください。", diff --git a/crates/localization/locales/ko.json b/crates/localization/locales/ko.json index b28f1ffa99..b08ed068d5 100644 --- a/crates/localization/locales/ko.json +++ b/crates/localization/locales/ko.json @@ -1236,6 +1236,36 @@ "NotificationSubagentCancelled": "서브 에이전트 취소", "NotificationSubagentBudgetExhausted": "서브 에이전트 예산 소진", "FooterWorkedChip": "작업 시간 {duration}", + "FooterPermissionKeyHint": "변경: {key}", + "ApprovalSummaryRunCommand": "`{command}` 실행", + "ApprovalSummaryRunShell": "셸 명령 실행", + "ApprovalSummaryShellWait": "실행 중인 셸 명령 대기", + "ApprovalSummaryShellInput": "실행 중인 셸 명령에 입력 보내기", + "ApprovalSummaryShellStop": "실행 중인 셸 명령 중지", + "ApprovalSummaryWritePath": "{path} 쓰기", + "ApprovalSummaryWriteFile": "파일 쓰기", + "ApprovalSummaryEditPath": "{path} 편집", + "ApprovalSummaryEditFile": "파일 편집", + "ApprovalSummaryEditPathMore": "{path} 외 파일 {count}개 편집", + "ApprovalSummaryApplyPatch": "패치 적용", + "ApprovalSummaryReadPath": "{path} 읽기", + "ApprovalSummaryReadFile": "파일 읽기", + "ApprovalSummaryListPath": "{path} 목록 보기", + "ApprovalSummaryListWorkspace": "작업 공간 목록 보기", + "ApprovalSummaryFetchUrl": "{url} 가져오기", + "ApprovalSummaryFetchPage": "웹 페이지 가져오기", + "ApprovalSummarySearchQuery": "웹에서 '{query}' 검색", + "ApprovalSummarySearchWeb": "웹 검색", + "ApprovalSummaryImageQuery": "웹에서 '{query}' 이미지 검색", + "ApprovalSummaryOpenTarget": "{target} 열기", + "ApprovalSummaryMore": " (외 {count}개)", + "ApprovalSummaryFollowLink": "열린 페이지의 링크 따라가기", + "ApprovalSummaryFindOnPage": "열린 페이지에서 '{pattern}' 찾기", + "ApprovalSummaryScreenshot": "열린 페이지 스크린샷 찍기", + "ApprovalSummaryBrowse": "웹 탐색", + "ApprovalSummaryMcpTool": "{server}의 {tool} 사용", + "ApprovalSummaryUseName": "{name} 사용", + "ApprovalSummaryUseTool": "{name} 도구 사용", "FleetDraftTitle": "팀 프로필 — {model_label} 초안 (g로 저장)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label}이(가) 작성한 초안이며, Codewhale이 검증하고 범위를 제한했습니다.\n# 권한은 팀 최저 기준을 유지합니다: 셸 없음, 신뢰 없음, 승인 필수.\n# 마법사에서 g를 누르기 전까지는 아무것도 저장되지 않습니다.\n\n", "SetupRemoteOnRampText": "원격 런타임 온램프\n\n/setup은 원격 런타임 정보만 보여줍니다. 배포 번들을 생성하거나, 자격 증명을 쓰거나, 클라우드 CLI를 호출하거나, `remote-setup`을 실행하지 않습니다.\n\n현재 정보:\n- 클라우드: {clouds_result}\n- 채팅 브리지: {bridges_result}\n- 프로바이더: {providers_result}\n- 모드: {mode_result}\n\n배포 번들을 생성하려면 일반 터미널에서 다음을 직접 실행하세요:\n\n```sh\n{command}\n```\n\n생성된 RUNBOOK에는 사람이 검토할 호스트 단계가 포함되어 있습니다. `--apply`는 아직 구현되지 않았으니 이를 자동 배포로 취급하지 마세요.", diff --git a/crates/localization/locales/pt-BR.json b/crates/localization/locales/pt-BR.json index a4352c0db3..caf74ecfab 100644 --- a/crates/localization/locales/pt-BR.json +++ b/crates/localization/locales/pt-BR.json @@ -1234,6 +1234,36 @@ "NotificationSubagentCancelled": "Subagente cancelado", "NotificationSubagentBudgetExhausted": "Orçamento do subagente esgotado", "FooterWorkedChip": "trabalhou {duration}", + "FooterPermissionKeyHint": "{key} para mudar", + "ApprovalSummaryRunCommand": "Executar `{command}`", + "ApprovalSummaryRunShell": "Executar um comando de shell", + "ApprovalSummaryShellWait": "Aguardar um comando de shell em execução", + "ApprovalSummaryShellInput": "Enviar entrada para um comando de shell em execução", + "ApprovalSummaryShellStop": "Parar um comando de shell em execução", + "ApprovalSummaryWritePath": "Gravar {path}", + "ApprovalSummaryWriteFile": "Gravar um arquivo", + "ApprovalSummaryEditPath": "Editar {path}", + "ApprovalSummaryEditFile": "Editar um arquivo", + "ApprovalSummaryEditPathMore": "Editar {path} e mais {count} arquivo(s)", + "ApprovalSummaryApplyPatch": "Aplicar um patch", + "ApprovalSummaryReadPath": "Ler {path}", + "ApprovalSummaryReadFile": "Ler um arquivo", + "ApprovalSummaryListPath": "Listar {path}", + "ApprovalSummaryListWorkspace": "Listar o espaço de trabalho", + "ApprovalSummaryFetchUrl": "Buscar {url}", + "ApprovalSummaryFetchPage": "Buscar uma página da web", + "ApprovalSummarySearchQuery": "Pesquisar '{query}' na web", + "ApprovalSummarySearchWeb": "Pesquisar na web", + "ApprovalSummaryImageQuery": "Pesquisar imagens de '{query}' na web", + "ApprovalSummaryOpenTarget": "Abrir {target}", + "ApprovalSummaryMore": " (+{count} a mais)", + "ApprovalSummaryFollowLink": "Seguir um link em uma página aberta", + "ApprovalSummaryFindOnPage": "Procurar '{pattern}' em uma página aberta", + "ApprovalSummaryScreenshot": "Capturar a tela de uma página aberta", + "ApprovalSummaryBrowse": "Navegar na web", + "ApprovalSummaryMcpTool": "Usar {tool} de {server}", + "ApprovalSummaryUseName": "Usar {name}", + "ApprovalSummaryUseTool": "Usar a ferramenta {name}", "FleetDraftTitle": "Perfil da equipe — rascunho de {model_label} (g para salvar)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Rascunhado por {model_label}, validado e limitado pela Codewhale.\n# Permissões no piso da equipe: sem shell, sem confiança, aprovação necessária.\n# Nada é salvo até que você pressione g no assistente.\n\n", "SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup apenas mostra fatos do runtime remoto. Ele não gera bundles, grava credenciais, chama CLIs de cloud nem executa `remote-setup`.\n\nFatos atuais:\n- Clouds: {clouds_result}\n- Pontes: {bridges_result}\n- Provedores: {providers_result}\n- Modo: {mode_result}\n\nPara gerar um bundle de deploy, execute explicitamente em um terminal normal:\n\n```sh\n{command}\n```\n\nO RUNBOOK gerado contém os passos de host para revisão humana. `--apply` continua não implementado; não trate isso como auto-deploy.", diff --git a/crates/localization/locales/ru.json b/crates/localization/locales/ru.json index 1b0a30dce6..0bf69174a6 100644 --- a/crates/localization/locales/ru.json +++ b/crates/localization/locales/ru.json @@ -1213,6 +1213,36 @@ "NotificationSubagentCancelled": "Субагент отменён", "NotificationSubagentBudgetExhausted": "Бюджет субагента исчерпан", "FooterWorkedChip": "работал {duration}", + "FooterPermissionKeyHint": "{key} — изменить", + "ApprovalSummaryRunCommand": "Выполнить `{command}`", + "ApprovalSummaryRunShell": "Выполнить команду оболочки", + "ApprovalSummaryShellWait": "Дождаться выполняемой команды оболочки", + "ApprovalSummaryShellInput": "Отправить ввод выполняемой команде оболочки", + "ApprovalSummaryShellStop": "Остановить выполняемую команду оболочки", + "ApprovalSummaryWritePath": "Записать {path}", + "ApprovalSummaryWriteFile": "Записать файл", + "ApprovalSummaryEditPath": "Изменить {path}", + "ApprovalSummaryEditFile": "Изменить файл", + "ApprovalSummaryEditPathMore": "Изменить {path} и ещё файлов: {count}", + "ApprovalSummaryApplyPatch": "Применить патч", + "ApprovalSummaryReadPath": "Прочитать {path}", + "ApprovalSummaryReadFile": "Прочитать файл", + "ApprovalSummaryListPath": "Показать содержимое {path}", + "ApprovalSummaryListWorkspace": "Показать содержимое рабочей папки", + "ApprovalSummaryFetchUrl": "Загрузить {url}", + "ApprovalSummaryFetchPage": "Загрузить веб-страницу", + "ApprovalSummarySearchQuery": "Искать в интернете «{query}»", + "ApprovalSummarySearchWeb": "Искать в интернете", + "ApprovalSummaryImageQuery": "Искать в интернете изображения «{query}»", + "ApprovalSummaryOpenTarget": "Открыть {target}", + "ApprovalSummaryMore": " (ещё {count})", + "ApprovalSummaryFollowLink": "Перейти по ссылке на открытой странице", + "ApprovalSummaryFindOnPage": "Найти «{pattern}» на открытой странице", + "ApprovalSummaryScreenshot": "Сделать снимок открытой страницы", + "ApprovalSummaryBrowse": "Просматривать интернет", + "ApprovalSummaryMcpTool": "Использовать {tool} из {server}", + "ApprovalSummaryUseName": "Использовать {name}", + "ApprovalSummaryUseTool": "Использовать инструмент {name}", "FleetDraftTitle": "Профиль команды — черновик от {model_label} (g сохраняет)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Черновик от {model_label}, проверен и ограничен Codewhale.\n# Права остаются на минимуме команды: без shell, без доверия, требуется одобрение.\n# Ничего не сохраняется, пока вы не нажмёте g в мастере.\n\n", "SetupRemoteOnRampText": "Запуск удалённой среды\n\n/setup только показывает факты об удалённой среде выполнения. Он не генерирует пакеты развёртывания, не записывает учётные данные, не вызывает облачные CLI и не запускает `remote-setup`.\n\nТекущие факты:\n- Облака: {clouds_result}\n- Мосты чатов: {bridges_result}\n- Провайдеры: {providers_result}\n- Режим: {mode_result}\n\nЧтобы сгенерировать пакет развёртывания, выполните явно в обычном терминале:\n\n```sh\n{command}\n```\n\nСгенерированный RUNBOOK включает шаги для проверки человеком. `--apply` не реализован; не воспринимайте это как авторазвёртывание.", diff --git a/crates/localization/locales/uk.json b/crates/localization/locales/uk.json index 8ff043f032..d3678a3ca0 100644 --- a/crates/localization/locales/uk.json +++ b/crates/localization/locales/uk.json @@ -1213,6 +1213,36 @@ "NotificationSubagentCancelled": "Субагента скасовано", "NotificationSubagentBudgetExhausted": "Бюджет субагента вичерпано", "FooterWorkedChip": "працював {duration}", + "FooterPermissionKeyHint": "{key} — змінити", + "ApprovalSummaryRunCommand": "Виконати `{command}`", + "ApprovalSummaryRunShell": "Виконати команду оболонки", + "ApprovalSummaryShellWait": "Дочекатися виконуваної команди оболонки", + "ApprovalSummaryShellInput": "Надіслати введення виконуваній команді оболонки", + "ApprovalSummaryShellStop": "Зупинити виконувану команду оболонки", + "ApprovalSummaryWritePath": "Записати {path}", + "ApprovalSummaryWriteFile": "Записати файл", + "ApprovalSummaryEditPath": "Змінити {path}", + "ApprovalSummaryEditFile": "Змінити файл", + "ApprovalSummaryEditPathMore": "Змінити {path} та ще файлів: {count}", + "ApprovalSummaryApplyPatch": "Застосувати патч", + "ApprovalSummaryReadPath": "Прочитати {path}", + "ApprovalSummaryReadFile": "Прочитати файл", + "ApprovalSummaryListPath": "Показати вміст {path}", + "ApprovalSummaryListWorkspace": "Показати вміст робочої теки", + "ApprovalSummaryFetchUrl": "Завантажити {url}", + "ApprovalSummaryFetchPage": "Завантажити вебсторінку", + "ApprovalSummarySearchQuery": "Шукати в інтернеті «{query}»", + "ApprovalSummarySearchWeb": "Шукати в інтернеті", + "ApprovalSummaryImageQuery": "Шукати в інтернеті зображення «{query}»", + "ApprovalSummaryOpenTarget": "Відкрити {target}", + "ApprovalSummaryMore": " (ще {count})", + "ApprovalSummaryFollowLink": "Перейти за посиланням на відкритій сторінці", + "ApprovalSummaryFindOnPage": "Знайти «{pattern}» на відкритій сторінці", + "ApprovalSummaryScreenshot": "Зробити знімок відкритої сторінки", + "ApprovalSummaryBrowse": "Переглядати інтернет", + "ApprovalSummaryMcpTool": "Використати {tool} з {server}", + "ApprovalSummaryUseName": "Використати {name}", + "ApprovalSummaryUseTool": "Використати інструмент {name}", "FleetDraftTitle": "Профіль команди — чернетка від {model_label} (g зберігає)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Чернетка від {model_label}, перевірена й обмежена Codewhale.\n# Дозволи залишаються на базовому рівні команди: без shell, без довіри, потрібне схвалення.\n# Нічого не буде збережено, доки ви не натиснете g у майстрі.\n\n", "SetupRemoteOnRampText": "Безпечний старт віддаленого середовища виконання\n\n/setup лише показує факти про віддалене середовище виконання. Він не генерує пакети розгортання, не записує облікові дані, не викликає хмарні CLI й не запускає `remote-setup`.\n\nПоточні факти:\n- Хмари: {clouds_result}\n- Мости чатів: {bridges_result}\n- Провайдери: {providers_result}\n- Режим: {mode_result}\n\nЩоб згенерувати пакет розгортання, виконайте явно у звичайному терміналі:\n\n```sh\n{command}\n```\n\nЗгенерований RUNBOOK містить кроки для хоста, призначені для перегляду людиною. `--apply` лишається нереалізованим; не вважайте це авторозгортанням.", diff --git a/crates/localization/locales/vi.json b/crates/localization/locales/vi.json index ca45ae012f..ea1c861ba7 100644 --- a/crates/localization/locales/vi.json +++ b/crates/localization/locales/vi.json @@ -1234,6 +1234,36 @@ "NotificationSubagentCancelled": "Sub-agent đã hủy", "NotificationSubagentBudgetExhausted": "Sub-agent đã hết ngân sách", "FooterWorkedChip": "đã chạy {duration}", + "FooterPermissionKeyHint": "{key} để đổi", + "ApprovalSummaryRunCommand": "Chạy `{command}`", + "ApprovalSummaryRunShell": "Chạy một lệnh shell", + "ApprovalSummaryShellWait": "Chờ một lệnh shell đang chạy", + "ApprovalSummaryShellInput": "Gửi dữ liệu nhập cho một lệnh shell đang chạy", + "ApprovalSummaryShellStop": "Dừng một lệnh shell đang chạy", + "ApprovalSummaryWritePath": "Ghi {path}", + "ApprovalSummaryWriteFile": "Ghi một tệp", + "ApprovalSummaryEditPath": "Sửa {path}", + "ApprovalSummaryEditFile": "Sửa một tệp", + "ApprovalSummaryEditPathMore": "Sửa {path} và {count} tệp khác", + "ApprovalSummaryApplyPatch": "Áp dụng một bản vá", + "ApprovalSummaryReadPath": "Đọc {path}", + "ApprovalSummaryReadFile": "Đọc một tệp", + "ApprovalSummaryListPath": "Liệt kê {path}", + "ApprovalSummaryListWorkspace": "Liệt kê không gian làm việc", + "ApprovalSummaryFetchUrl": "Tải {url}", + "ApprovalSummaryFetchPage": "Tải một trang web", + "ApprovalSummarySearchQuery": "Tìm '{query}' trên web", + "ApprovalSummarySearchWeb": "Tìm trên web", + "ApprovalSummaryImageQuery": "Tìm hình ảnh '{query}' trên web", + "ApprovalSummaryOpenTarget": "Mở {target}", + "ApprovalSummaryMore": " (+{count} mục khác)", + "ApprovalSummaryFollowLink": "Mở một liên kết trên trang đã mở", + "ApprovalSummaryFindOnPage": "Tìm '{pattern}' trên trang đã mở", + "ApprovalSummaryScreenshot": "Chụp màn hình trang đã mở", + "ApprovalSummaryBrowse": "Duyệt web", + "ApprovalSummaryMcpTool": "Dùng {tool} từ {server}", + "ApprovalSummaryUseName": "Dùng {name}", + "ApprovalSummaryUseTool": "Dùng công cụ {name}", "FleetDraftTitle": "Hồ sơ nhóm — bản nháp của {model_label} (nhấn g để lưu)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# Được soạn bởi {model_label}, xác thực và giới hạn bởi Codewhale.\n# Quyền giữ ở mức sàn nhóm: không shell, không tin cậy, cần phê duyệt.\n# Không có gì được lưu cho đến khi bạn nhấn g trong trình hướng dẫn.\n\n", "SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup chỉ hiển thị dữ kiện runtime từ xa. Nó không tạo gói triển khai, ghi thông tin xác thực, gọi CLI đám mây hay chạy `remote-setup`.\n\nDữ kiện hiện tại:\n- Đám mây: {clouds_result}\n- Cầu nối: {bridges_result}\n- Nhà cung cấp: {providers_result}\n- Chế độ: {mode_result}\n\nĐể tạo gói triển khai, hãy chạy rõ ràng trong terminal thông thường:\n\n```sh\n{command}\n```\n\nRUNBOOK được tạo bao gồm các bước máy chủ cần xem xét thủ công. `--apply` vẫn chưa được triển khai; đừng coi đây là tự động triển khai.", diff --git a/crates/localization/locales/zh-Hans.json b/crates/localization/locales/zh-Hans.json index 4f813c17c5..041e9310e8 100644 --- a/crates/localization/locales/zh-Hans.json +++ b/crates/localization/locales/zh-Hans.json @@ -1234,6 +1234,36 @@ "NotificationSubagentCancelled": "子代理已取消", "NotificationSubagentBudgetExhausted": "子代理预算已用尽", "FooterWorkedChip": "已运行{duration}", + "FooterPermissionKeyHint": "{key} 切换", + "ApprovalSummaryRunCommand": "运行 `{command}`", + "ApprovalSummaryRunShell": "运行一条 shell 命令", + "ApprovalSummaryShellWait": "等待正在运行的 shell 命令", + "ApprovalSummaryShellInput": "向正在运行的 shell 命令发送输入", + "ApprovalSummaryShellStop": "停止正在运行的 shell 命令", + "ApprovalSummaryWritePath": "写入 {path}", + "ApprovalSummaryWriteFile": "写入文件", + "ApprovalSummaryEditPath": "编辑 {path}", + "ApprovalSummaryEditFile": "编辑文件", + "ApprovalSummaryEditPathMore": "编辑 {path} 及另外 {count} 个文件", + "ApprovalSummaryApplyPatch": "应用补丁", + "ApprovalSummaryReadPath": "读取 {path}", + "ApprovalSummaryReadFile": "读取文件", + "ApprovalSummaryListPath": "列出 {path}", + "ApprovalSummaryListWorkspace": "列出工作区", + "ApprovalSummaryFetchUrl": "获取 {url}", + "ApprovalSummaryFetchPage": "获取网页", + "ApprovalSummarySearchQuery": "在网上搜索“{query}”", + "ApprovalSummarySearchWeb": "在网上搜索", + "ApprovalSummaryImageQuery": "在网上搜索“{query}”的图片", + "ApprovalSummaryOpenTarget": "打开 {target}", + "ApprovalSummaryMore": "(另 {count} 项)", + "ApprovalSummaryFollowLink": "打开已打开页面上的链接", + "ApprovalSummaryFindOnPage": "在已打开的页面上查找“{pattern}”", + "ApprovalSummaryScreenshot": "截取已打开页面的屏幕截图", + "ApprovalSummaryBrowse": "浏览网页", + "ApprovalSummaryMcpTool": "使用 {server} 的 {tool}", + "ApprovalSummaryUseName": "使用 {name}", + "ApprovalSummaryUseTool": "使用 {name} 工具", "FleetDraftTitle": "团队 配置 — 由 {model_label} 起草(按 g 保存)", "FleetDraftHeader": "# .codewhale/agents/{name}\n# 由 {model_label} 起草,并由 Codewhale 校验与限界。\n# 权限保持在 团队 底线:无 shell、无 trust、需审批。\n# 在向导中按 g 之前不会保存任何内容。\n\n", "SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup 只展示远程运行时事实,不会生成部署包、写入凭据、调用云 CLI 或运行 `remote-setup`。\n\n当前事实:\n- 云目标:{clouds_result}\n- 聊天桥:{bridges_result}\n- 提供商:{providers_result}\n- 模式:{mode_result}\n\n生成部署包时,请在普通终端显式运行:\n\n```sh\n{command}\n```\n\n生成的 RUNBOOK 会包含需要人工复核的主机步骤。`--apply` 仍未实现;不要把它当成自动部署。", diff --git a/crates/localization/locales/zh-Hant.json b/crates/localization/locales/zh-Hant.json index 1b4dba754d..e482c13610 100644 --- a/crates/localization/locales/zh-Hant.json +++ b/crates/localization/locales/zh-Hant.json @@ -960,6 +960,36 @@ "FooterHintOutput": "輸出", "FooterPressCtrlCAgain": "再次按 Ctrl+C 退出", "FooterWorkedChip": "已運行{duration}", + "FooterPermissionKeyHint": "{key} 切換", + "ApprovalSummaryRunCommand": "執行 `{command}`", + "ApprovalSummaryRunShell": "執行一條 shell 命令", + "ApprovalSummaryShellWait": "等待正在執行的 shell 命令", + "ApprovalSummaryShellInput": "向正在執行的 shell 命令傳送輸入", + "ApprovalSummaryShellStop": "停止正在執行的 shell 命令", + "ApprovalSummaryWritePath": "寫入 {path}", + "ApprovalSummaryWriteFile": "寫入檔案", + "ApprovalSummaryEditPath": "編輯 {path}", + "ApprovalSummaryEditFile": "編輯檔案", + "ApprovalSummaryEditPathMore": "編輯 {path} 及另外 {count} 個檔案", + "ApprovalSummaryApplyPatch": "套用修補", + "ApprovalSummaryReadPath": "讀取 {path}", + "ApprovalSummaryReadFile": "讀取檔案", + "ApprovalSummaryListPath": "列出 {path}", + "ApprovalSummaryListWorkspace": "列出工作區", + "ApprovalSummaryFetchUrl": "擷取 {url}", + "ApprovalSummaryFetchPage": "擷取網頁", + "ApprovalSummarySearchQuery": "在網路上搜尋「{query}」", + "ApprovalSummarySearchWeb": "在網路上搜尋", + "ApprovalSummaryImageQuery": "在網路上搜尋「{query}」的圖片", + "ApprovalSummaryOpenTarget": "開啟 {target}", + "ApprovalSummaryMore": "(另 {count} 項)", + "ApprovalSummaryFollowLink": "開啟已開啟頁面上的連結", + "ApprovalSummaryFindOnPage": "在已開啟的頁面上尋找「{pattern}」", + "ApprovalSummaryScreenshot": "擷取已開啟頁面的螢幕截圖", + "ApprovalSummaryBrowse": "瀏覽網頁", + "ApprovalSummaryMcpTool": "使用 {server} 的 {tool}", + "ApprovalSummaryUseName": "使用 {name}", + "ApprovalSummaryUseTool": "使用 {name} 工具", "FooterWorking": "工作中", "HeaderAgentsChip": "子代理 {count}", "HelpGroupCommonCommands": "常用指令", diff --git a/crates/localization/src/lib.rs b/crates/localization/src/lib.rs index 49cdf158fa..519702f35b 100644 --- a/crates/localization/src/lib.rs +++ b/crates/localization/src/lib.rs @@ -1499,6 +1499,36 @@ pub enum MessageId { NotificationSubagentBudgetExhausted, // Footer chips. FooterWorkedChip, + FooterPermissionKeyHint, + ApprovalSummaryRunCommand, + ApprovalSummaryRunShell, + ApprovalSummaryShellWait, + ApprovalSummaryShellInput, + ApprovalSummaryShellStop, + ApprovalSummaryWritePath, + ApprovalSummaryWriteFile, + ApprovalSummaryEditPath, + ApprovalSummaryEditFile, + ApprovalSummaryEditPathMore, + ApprovalSummaryApplyPatch, + ApprovalSummaryReadPath, + ApprovalSummaryReadFile, + ApprovalSummaryListPath, + ApprovalSummaryListWorkspace, + ApprovalSummaryFetchUrl, + ApprovalSummaryFetchPage, + ApprovalSummarySearchQuery, + ApprovalSummarySearchWeb, + ApprovalSummaryImageQuery, + ApprovalSummaryOpenTarget, + ApprovalSummaryMore, + ApprovalSummaryFollowLink, + ApprovalSummaryFindOnPage, + ApprovalSummaryScreenshot, + ApprovalSummaryBrowse, + ApprovalSummaryMcpTool, + ApprovalSummaryUseName, + ApprovalSummaryUseTool, // Fleet setup wizard. FleetDraftTitle, FleetDraftHeader, @@ -3822,6 +3852,36 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[ MessageId::NotificationSubagentCancelled, MessageId::NotificationSubagentBudgetExhausted, MessageId::FooterWorkedChip, + MessageId::FooterPermissionKeyHint, + MessageId::ApprovalSummaryRunCommand, + MessageId::ApprovalSummaryRunShell, + MessageId::ApprovalSummaryShellWait, + MessageId::ApprovalSummaryShellInput, + MessageId::ApprovalSummaryShellStop, + MessageId::ApprovalSummaryWritePath, + MessageId::ApprovalSummaryWriteFile, + MessageId::ApprovalSummaryEditPath, + MessageId::ApprovalSummaryEditFile, + MessageId::ApprovalSummaryEditPathMore, + MessageId::ApprovalSummaryApplyPatch, + MessageId::ApprovalSummaryReadPath, + MessageId::ApprovalSummaryReadFile, + MessageId::ApprovalSummaryListPath, + MessageId::ApprovalSummaryListWorkspace, + MessageId::ApprovalSummaryFetchUrl, + MessageId::ApprovalSummaryFetchPage, + MessageId::ApprovalSummarySearchQuery, + MessageId::ApprovalSummarySearchWeb, + MessageId::ApprovalSummaryImageQuery, + MessageId::ApprovalSummaryOpenTarget, + MessageId::ApprovalSummaryMore, + MessageId::ApprovalSummaryFollowLink, + MessageId::ApprovalSummaryFindOnPage, + MessageId::ApprovalSummaryScreenshot, + MessageId::ApprovalSummaryBrowse, + MessageId::ApprovalSummaryMcpTool, + MessageId::ApprovalSummaryUseName, + MessageId::ApprovalSummaryUseTool, MessageId::FleetDraftTitle, MessageId::FleetDraftHeader, MessageId::SetupRemoteOnRampText, diff --git a/crates/tui/src/tools/approval_summary.rs b/crates/tui/src/tools/approval_summary.rs index 66c9a6c6c0..5d1b7896f6 100644 --- a/crates/tui/src/tools/approval_summary.rs +++ b/crates/tui/src/tools/approval_summary.rs @@ -9,14 +9,30 @@ use std::path::Path; +use codewhale_localization::{Locale, MessageId, tr}; use serde_json::Value; /// Longest quoted argument a summary carries before it is cut with `…`. const MAX_QUOTED_CHARS: usize = 80; -/// Summarize a gated tool call for an approval prompt. +/// Summarize a gated tool call for an approval prompt, in English — the +/// wire form every runtime client receives. #[must_use] pub fn approval_summary(tool_name: &str, input: &Value, workspace: Option<&Path>) -> String { + approval_summary_in(Locale::En, tool_name, input, workspace) +} + +/// Summarize a gated tool call in `locale`, so a translated approval card +/// leads with the same plain sentence the English one does. Commands, paths, +/// URLs and queries are carried verbatim; only the sentence around them is +/// translated. +#[must_use] +pub fn approval_summary_in( + locale: Locale, + tool_name: &str, + input: &Value, + workspace: Option<&Path>, +) -> String { let name = crate::tools::canonical_action::canonical_action_alias(tool_name, input); let text = |key: &str| { input @@ -26,49 +42,55 @@ pub fn approval_summary(tool_name: &str, input: &Value, workspace: Option<&Path> .filter(|value| !value.is_empty()) }; let path = |key: &str| text(key).map(|raw| relative_path(raw, workspace)); + let msg = |id: MessageId| tr(locale, id).into_owned(); + let with = |id: MessageId, slot: &str, value: &str| { + tr(locale, id).replace(&format!("{{{slot}}}"), value) + }; match name { "exec_shell" | "task_shell_start" => match text("command") { - Some(command) => format!("Run `{}`", clip(command)), - None => "Run a shell command".to_string(), + Some(command) => with( + MessageId::ApprovalSummaryRunCommand, + "command", + &clip(command), + ), + None => msg(MessageId::ApprovalSummaryRunShell), }, - "exec_shell_wait" | "exec_wait" => "Wait for a running shell command".to_string(), - "exec_shell_interact" | "exec_interact" => { - "Send input to a running shell command".to_string() - } - "exec_shell_cancel" => "Stop a running shell command".to_string(), + "exec_shell_wait" | "exec_wait" => msg(MessageId::ApprovalSummaryShellWait), + "exec_shell_interact" | "exec_interact" => msg(MessageId::ApprovalSummaryShellInput), + "exec_shell_cancel" => msg(MessageId::ApprovalSummaryShellStop), "write_file" => match path("path") { - Some(path) => format!("Write {path}"), - None => "Write a file".to_string(), + Some(path) => with(MessageId::ApprovalSummaryWritePath, "path", &path), + None => msg(MessageId::ApprovalSummaryWriteFile), }, "edit_file" | "fim_edit" => match path("path") { - Some(path) => format!("Edit {path}"), - None => "Edit a file".to_string(), + Some(path) => with(MessageId::ApprovalSummaryEditPath, "path", &path), + None => msg(MessageId::ApprovalSummaryEditFile), }, - "apply_patch" => patch_summary(input, workspace), + "apply_patch" => patch_summary(locale, input, workspace), "read_file" => match path("path") { - Some(path) => format!("Read {path}"), - None => "Read a file".to_string(), + Some(path) => with(MessageId::ApprovalSummaryReadPath, "path", &path), + None => msg(MessageId::ApprovalSummaryReadFile), }, "list_dir" => match path("path") { - Some(path) => format!("List {path}"), - None => "List the workspace".to_string(), + Some(path) => with(MessageId::ApprovalSummaryListPath, "path", &path), + None => msg(MessageId::ApprovalSummaryListWorkspace), }, "fetch_url" | "web.fetch" | "web_fetch" => match text("url") { - Some(url) => format!("Fetch {}", clip(url)), - None => "Fetch a web page".to_string(), + Some(url) => with(MessageId::ApprovalSummaryFetchUrl, "url", &clip(url)), + None => msg(MessageId::ApprovalSummaryFetchPage), }, "web_search" => match text("query").or_else(|| text("q")) { - Some(query) => format!("Search the web for '{}'", clip(query)), - None => "Search the web".to_string(), + Some(query) => with(MessageId::ApprovalSummarySearchQuery, "query", &clip(query)), + None => msg(MessageId::ApprovalSummarySearchWeb), }, - "web.run" => web_run_summary(input), - name if name.starts_with("mcp_") => mcp_summary(name), - name => format!("Use the {name} tool"), + "web.run" => web_run_summary(locale, input), + name if name.starts_with("mcp_") => mcp_summary(locale, name), + name => with(MessageId::ApprovalSummaryUseTool, "name", name), } } -fn web_run_summary(input: &Value) -> String { +fn web_run_summary(locale: Locale, input: &Value) -> String { let first = |key: &str, field: &str| { input .get(key) @@ -83,40 +105,37 @@ fn web_run_summary(input: &Value) -> String { let count = |key: &str| input.get(key).and_then(Value::as_array).map_or(0, Vec::len); let more = |key: &str| match count(key) { 0 | 1 => String::new(), - n => format!(" (+{} more)", n - 1), + n => tr(locale, MessageId::ApprovalSummaryMore).replace("{count}", &(n - 1).to_string()), + }; + let with = |id: MessageId, slot: &str, value: &str| { + tr(locale, id).replace(&format!("{{{slot}}}"), &clip(value)) }; if let Some(query) = first("search_query", "q") { - return format!( - "Search the web for '{}'{}", - clip(&query), - more("search_query") - ); + return with(MessageId::ApprovalSummarySearchQuery, "query", &query) + + &more("search_query"); } if let Some(query) = first("image_query", "q") { - return format!( - "Search the web for images of '{}'{}", - clip(&query), - more("image_query") - ); + return with(MessageId::ApprovalSummaryImageQuery, "query", &query) + &more("image_query"); } if let Some(target) = first("open", "ref_id") { - return format!("Open {}{}", clip(&target), more("open")); + return with(MessageId::ApprovalSummaryOpenTarget, "target", &target) + &more("open"); } if count("click") > 0 { - return "Follow a link on an opened page".to_string(); + return tr(locale, MessageId::ApprovalSummaryFollowLink).into_owned(); } if let Some(pattern) = first("find", "pattern") { - return format!("Find '{}' on an opened page", clip(&pattern)); + return with(MessageId::ApprovalSummaryFindOnPage, "pattern", &pattern); } if count("screenshot") > 0 { - return "Take a screenshot of an opened page".to_string(); + return tr(locale, MessageId::ApprovalSummaryScreenshot).into_owned(); } - "Browse the web".to_string() + tr(locale, MessageId::ApprovalSummaryBrowse).into_owned() } -fn patch_summary(input: &Value, workspace: Option<&Path>) -> String { +fn patch_summary(locale: Locale, input: &Value, workspace: Option<&Path>) -> String { + let apply_patch = || tr(locale, MessageId::ApprovalSummaryApplyPatch).into_owned(); let Ok(preflight) = crate::tools::apply_patch::preflight_apply_patch(input) else { - return "Apply a patch".to_string(); + return apply_patch(); }; let mut paths: Vec = preflight .touched_files @@ -126,21 +145,25 @@ fn patch_summary(input: &Value, workspace: Option<&Path>) -> String { paths.sort_unstable(); paths.dedup(); match paths.as_slice() { - [] => "Apply a patch".to_string(), - [one] => format!("Edit {one}"), - [first, rest @ ..] => format!("Edit {first} and {} more file(s)", rest.len()), + [] => apply_patch(), + [one] => tr(locale, MessageId::ApprovalSummaryEditPath).replace("{path}", one), + [first, rest @ ..] => tr(locale, MessageId::ApprovalSummaryEditPathMore) + .replace("{path}", first) + .replace("{count}", &rest.len().to_string()), } } -fn mcp_summary(name: &str) -> String { +fn mcp_summary(locale: Locale, name: &str) -> String { // `mcp__`; server names may themselves hold `_`, so this is // presentation only and never a policy decision. let rest = name.trim_start_matches("mcp_"); match rest.split_once('_') { Some((server, tool)) if !server.is_empty() && !tool.is_empty() => { - format!("Use {tool} from {server}") + tr(locale, MessageId::ApprovalSummaryMcpTool) + .replace("{tool}", tool) + .replace("{server}", server) } - _ => format!("Use {rest}"), + _ => tr(locale, MessageId::ApprovalSummaryUseName).replace("{name}", rest), } } @@ -261,4 +284,68 @@ mod tests { "Use the some_tool tool" ); } + + /// A translated card leads with the same sentence, translated around the + /// verbatim command, path and query (experience mark 4). + #[test] + fn summaries_translate_the_sentence_and_keep_the_arguments_verbatim() { + let workspace = Path::new("/work/repo"); + assert_eq!( + approval_summary_in( + Locale::De, + "exec_shell", + &json!({"command": "cargo test"}), + None + ), + "`cargo test` ausführen" + ); + assert_eq!( + approval_summary_in( + Locale::Ja, + "write_file", + &json!({"path": "/work/repo/notes/espresso.md"}), + Some(workspace), + ), + "notes/espresso.md を書き込み" + ); + assert_eq!( + approval_summary_in( + Locale::ZhHans, + "web.run", + &json!({"search_query": [{"q": "espresso"}, {"q": "grinder"}]}), + None, + ), + "在网上搜索“espresso”(另 1 项)" + ); + assert_eq!( + approval_summary_in(Locale::Fr, "mcp_github_create_issue", &json!({}), None), + "Utiliser create_issue de github" + ); + // Every summary a non-English pack produces is its own sentence, never + // the English one leaking through the fallback. + for (tool, input) in [ + ("exec_shell", json!({})), + ("write_file", json!({})), + ("edit_file", json!({"path": "a.rs"})), + ("read_file", json!({})), + ("list_dir", json!({})), + ("fetch_url", json!({})), + ("web_search", json!({"query": "x"})), + ("web.run", json!({})), + ("some_tool", json!({})), + ] { + let english = approval_summary(tool, &input, None); + for locale in [ + Locale::De, + Locale::Ja, + Locale::Ru, + Locale::Hi, + Locale::ZhHant, + ] { + let translated = approval_summary_in(locale, tool, &input, None); + assert_ne!(translated, english, "{locale:?} {tool}"); + assert!(!translated.contains('{'), "{locale:?} {tool}: {translated}"); + } + } + } } diff --git a/crates/tui/src/tui/approval.rs b/crates/tui/src/tui/approval.rs index 18d72ac4b5..5b66cc6be9 100644 --- a/crates/tui/src/tui/approval.rs +++ b/crates/tui/src/tui/approval.rs @@ -234,6 +234,22 @@ impl ApprovalRequest { serde_json::to_string(&truncated).unwrap_or_else(|_| truncated.to_string()) } + /// The plain summary in `locale` (E6, experience mark 4): the same + /// sentence the English card leads with, translated around the verbatim + /// command, path or query. + #[must_use] + pub fn summary_for_locale(&self, locale: Locale) -> String { + if locale == Locale::En { + return self.summary.clone(); + } + crate::tools::approval_summary::approval_summary_in( + locale, + &self.tool_name, + &self.params, + Some(&self.workspace), + ) + } + pub fn description_for_locale(&self, locale: Locale) -> String { match locale { Locale::ZhHans => localized_description_zh_hans(self.category), diff --git a/crates/tui/src/tui/goldens/config_panel_120x32.txt b/crates/tui/src/tui/goldens/config_panel_120x32.txt index bc82f67d58..419383ba0c 100644 --- a/crates/tui/src/tui/goldens/config_panel_120x32.txt +++ b/crates/tui/src/tui/goldens/config_panel_120x32.txt @@ -26,7 +26,7 @@ │ │ │ │ system | terminal | shoreline | shoreline-light | underwater | underwater-retro | dark | light | grayscale |… - Preview: ask agent + Preview: ● ask agent type=filter, Up/Down=select, Enter=edit, Esc=close ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── diff --git a/crates/tui/src/tui/goldens/config_panel_80x24.txt b/crates/tui/src/tui/goldens/config_panel_80x24.txt index 55a7c4697b..3679534f47 100644 --- a/crates/tui/src/tui/goldens/config_panel_80x24.txt +++ b/crates/tui/src/tui/goldens/config_panel_80x24.txt @@ -18,7 +18,7 @@ system | terminal | shoreline | shoreline-light | underwater |… Enter or click again: Enter opens choices · Theme: current terminal · saved terminal · applies on save - Preview: ask agent + Preview: ● ask agent type=filter, Up/Down=select, Enter=edit, PgUp/PgDn=scroll, Esc=close ────────────────────────────────────────────────────────────────────────────── diff --git a/crates/tui/src/tui/goldens/footer_100x30.txt b/crates/tui/src/tui/goldens/footer_100x30.txt index e5b2b2da82..d0327593a3 100644 --- a/crates/tui/src/tui/goldens/footer_100x30.txt +++ b/crates/tui/src/tui/goldens/footer_100x30.txt @@ -27,4 +27,4 @@ - ask (Shift+Tab) work (Tab) working 1m 15s 2 agents worked 41m 12s Esc to interrupt + ● ask Shift+Tab to change work (Tab) 2 agents worked 41m 12s Esc to interrupt diff --git a/crates/tui/src/tui/goldens/footer_120x32.txt b/crates/tui/src/tui/goldens/footer_120x32.txt index c11ed93af2..f9db4486f7 100644 --- a/crates/tui/src/tui/goldens/footer_120x32.txt +++ b/crates/tui/src/tui/goldens/footer_120x32.txt @@ -29,4 +29,4 @@ - ask (Shift+Tab) work (Tab) working 1m 15s 2 agents worked 41m 12s Esc to interrupt + ● ask Shift+Tab to change work (Tab) working 1m 15s 2 agents worked 41m 12s Esc to interrupt diff --git a/crates/tui/src/tui/goldens/footer_160x40.txt b/crates/tui/src/tui/goldens/footer_160x40.txt index a53fdffa32..e790ecdfe3 100644 --- a/crates/tui/src/tui/goldens/footer_160x40.txt +++ b/crates/tui/src/tui/goldens/footer_160x40.txt @@ -37,4 +37,4 @@ - ask (Shift+Tab) work (Tab) working 1m 15s 2 agents worked 41m 12s Esc to interrupt + ● ask Shift+Tab to change work (Tab) working 1m 15s 2 agents worked 41m 12s Esc to interrupt diff --git a/crates/tui/src/tui/goldens/footer_80x24.txt b/crates/tui/src/tui/goldens/footer_80x24.txt index f61bd1cd57..22fffeacfa 100644 --- a/crates/tui/src/tui/goldens/footer_80x24.txt +++ b/crates/tui/src/tui/goldens/footer_80x24.txt @@ -21,4 +21,4 @@ - ask (Shift+Tab) work (Tab) 2 agents worked 41m 12s Esc to interrupt + ● ask Shift+Tab to change work (Tab) 2 agents Esc to interrupt diff --git a/crates/tui/src/tui/phase_strip.rs b/crates/tui/src/tui/phase_strip.rs index 638e8c2892..a8916e9f43 100644 --- a/crates/tui/src/tui/phase_strip.rs +++ b/crates/tui/src/tui/phase_strip.rs @@ -220,7 +220,17 @@ fn fit_notice(text: &str, budget: usize) -> Option { return Some(fitted); } let first = sentences.first().copied().unwrap_or(text); - join_while_fitting(¬ice_clauses(first, &CLAUSE_MARKS), budget) + let clauses = notice_clauses(first, &CLAUSE_MARKS); + let fitted = join_while_fitting(&clauses, budget)?; + // A one-word label whose value was shed is not a notice, it is a false + // one: `Thinking: high → max · model qwen` cut to `Thinking` sat in an + // idle footer reading as if a turn were thinking (experience mark 8). + // A phrase before the colon (`Auto-denied exec_shell`) still stands. + let bare_label = clauses + .first() + .is_some_and(|label| label.ends_with([':', ':'])) + && !fitted.contains(char::is_whitespace); + (!bare_label).then_some(fitted) } /// Map the boot surface's typed severity through the same semantic palette as @@ -383,6 +393,26 @@ mod tests { assert_eq!(fit_notice(" ", 40), None); } + /// Experience mark 8: a `/model` thinking change on a narrow rail used to + /// shed to a bare `Thinking`, which read as a live indicator with nothing + /// running. A label whose value cannot fit says nothing instead. + #[test] + fn a_label_never_sheds_to_a_bare_word_that_reads_as_activity() { + let notice = "Thinking: high → max · model deepseek-v4-flash"; + assert_eq!(fit_notice(notice, 40), None); + assert_eq!( + fit_notice("思考:高 → 最高 · 模型 deepseek-v4-flash", 12), + None + ); + // Room for the whole reading keeps it whole. + assert_eq!(fit_notice(notice, 60).as_deref(), Some(notice)); + // A phrase before the colon is still a notice on its own. + assert_eq!( + fit_notice("Auto-denied exec_shell: denied earlier this turn", 30).as_deref(), + Some("Auto-denied exec_shell") + ); + } + /// The failure this caught: a one-sentence warning longer than the row /// used to have no sentence joint to shed at, so the rail dropped the /// whole warning. Inner joints are the fallback, and the phrase that @@ -549,9 +579,11 @@ mod tests { // direction 2026-09-02): the first row under the composer, in Claude Code's // grammar — // -// full access (Shift+Tab) work (Tab) 2 agents, 1 task Esc to interrupt rc connected +// ● full access Shift+Tab to change work (Tab) 2 agents, 1 task Esc to interrupt rc connected // -// permission chip first (never sheds, #5796), the mode, the turn clock, the +// permission chip first (never sheds, #5796), marked `●` so the current +// permission reads without color (experience mark 8) and followed by what +// its key does, then the mode, the turn clock, the // live counts, the session clock, then the one hint that applies right now; // the remote-control state or a live notice pinned right. No cost: the // roster owns per-agent elapsed and the metrics line owns the price. The @@ -581,8 +613,9 @@ pub struct TidelineFooter<'a> { /// Permission chip (`ask` / `auto` / `full access`, plus the filesystem /// scope notice when it deviates) in its Permission ink. Never sheds. pub permission_chip: (&'a str, codewhale_palette::ChromeInk), - /// The chord that cycles the permission posture, when the binding is - /// live for the current focus (`Shift+Tab`). + /// What the permission key does, when the binding is live for the + /// current focus and not yet learned (`Shift+Tab to change`). Painted + /// after the chip in hint ink. pub permission_key: Option<&'a str>, /// Mode chip (`work` / `plan` / `operate`) in its Policy ink. pub mode_chip: Option<(&'a str, codewhale_palette::ChromeInk)>, @@ -762,6 +795,9 @@ fn tput(buf: &mut Buffer, x: u16, y: u16, text: &str, style: Style) { /// (bold) or a hint. struct PostureItem { text: String, + /// The taught key painted after `text` in hint ink (` Shift+Tab to + /// change`, ` (Tab)`), separator included. Sheds before the chip does. + key: Option, ink: ChromeInk, bold: bool, /// Painted after `, ` rather than ` · `: the counts are one group. @@ -791,16 +827,22 @@ struct PostureItem { /// The hint and counts still outrank it (#5914). Above them the /// context-cap warning, which is not a hint but the reason the next turn /// will not start at all. +/// +/// The permission key ("Shift+Tab to change", mark 8) is a reminder of a +/// binding, not live state, and is the widest optional item on the row, so +/// it sheds right after the clocks: a live hint ("Enter again to send +/// now") or the agent count must never be dropped to keep it. The +/// permission chip itself, marked `●`, never sheds. const SHED_TURN_CLOCK: u8 = 1; const SHED_SESSION_CLOCK: u8 = 2; -const SHED_HINT: u8 = 3; -const SHED_COUNTS: u8 = 4; -const SHED_CAP_WARNING: u8 = 5; -const SHED_MODE_KEY: u8 = 6; -const SHED_MODE: u8 = 7; -const SHED_PERMISSION_KEY: u8 = 8; +const SHED_PERMISSION_KEY: u8 = 3; +const SHED_HINT: u8 = 4; +const SHED_COUNTS: u8 = 5; +const SHED_CAP_WARNING: u8 = 6; +const SHED_MODE_KEY: u8 = 7; +const SHED_MODE: u8 = 8; /// The most-shed rung: everything gone but the permission chip. -const MAX_SHED: u8 = SHED_PERMISSION_KEY; +const MAX_SHED: u8 = SHED_MODE; /// Where a compact posture bar (`tui.posture_bar = "compact"`, #5950) /// starts on the ladder: the clocks, the hint and the counts are gone /// before width is consulted; the cap warning, the mode chip and the @@ -808,17 +850,20 @@ const MAX_SHED: u8 = SHED_PERMISSION_KEY; const COMPACT_SHED: u8 = SHED_COUNTS; fn posture_items(footer: &TidelineFooter<'_>, shed: u8) -> Vec { - let chip = |text: &str, key: Option<&str>| -> String { - match key { - Some(key) => format!("{text} ({key})"), - None => text.to_string(), - } - }; + // Experience mark 8: the current permission is marked, not only + // colored, so a monochrome terminal still tells it apart from the mode + // chip beside it; its key says what it does rather than sitting in + // parentheses like one more option. let mut items = vec![PostureItem { - text: chip( - &footer.sym(footer.permission_chip.0), - footer.permission_key.filter(|_| shed < SHED_PERMISSION_KEY), + text: format!( + "{} {}", + footer.sym(crate::tui::glyphs::CURRENT), + footer.sym(footer.permission_chip.0) ), + key: footer + .permission_key + .filter(|_| shed < SHED_PERMISSION_KEY) + .map(|key| format!(" {}", footer.sym(key))), ink: footer.permission_chip.1, bold: true, joined: false, @@ -826,10 +871,11 @@ fn posture_items(footer: &TidelineFooter<'_>, shed: u8) -> Vec { }]; if let Some((mode, ink)) = footer.mode_chip.filter(|_| shed < SHED_MODE) { items.push(PostureItem { - text: chip( - &footer.sym(mode), - footer.mode_key.filter(|_| shed < SHED_MODE_KEY), - ), + text: footer.sym(mode), + key: footer + .mode_key + .filter(|_| shed < SHED_MODE_KEY) + .map(|key| format!(" ({key})")), ink, bold: false, joined: false, @@ -849,6 +895,7 @@ fn posture_items(footer: &TidelineFooter<'_>, shed: u8) -> Vec { if let Some((clock, ink)) = footer.turn_clock.filter(|_| shed < turn_shed) { items.push(PostureItem { text: footer.sym(clock), + key: None, ink, bold: false, joined: false, @@ -857,8 +904,16 @@ fn posture_items(footer: &TidelineFooter<'_>, shed: u8) -> Vec { } if shed < SHED_COUNTS { for (index, (count, ink)) in footer.counts.iter().enumerate() { + // The idle dock affordance carries its chord as `(Ctrl+])`; keep + // the word legible while the chord recedes into hint ink. + let count = footer.sym(count); + let (text, key) = match count.strip_suffix(" (Ctrl+])") { + Some(label) => (label.to_string(), Some(" (Ctrl+])".to_string())), + None => (count, None), + }; items.push(PostureItem { - text: footer.sym(count), + text, + key, ink: *ink, bold: false, joined: index > 0, @@ -869,6 +924,7 @@ fn posture_items(footer: &TidelineFooter<'_>, shed: u8) -> Vec { if let Some((clock, ink)) = footer.session_clock.filter(|_| shed < SHED_SESSION_CLOCK) { items.push(PostureItem { text: footer.sym(clock), + key: None, ink, bold: false, joined: false, @@ -888,6 +944,7 @@ fn posture_items(footer: &TidelineFooter<'_>, shed: u8) -> Vec { { items.push(PostureItem { text, + key: None, ink, bold: false, joined: false, @@ -907,8 +964,14 @@ fn separator_before(item: &PostureItem) -> &'static str { } } +impl PostureItem { + fn width(&self) -> usize { + self.text.width() + self.key.as_deref().map_or(0, UnicodeWidthStr::width) + } +} + fn left_run_width(items: &[PostureItem]) -> usize { - items.iter().map(|item| item.text.width()).sum::() + items.iter().map(PostureItem::width).sum::() + items .iter() .skip(1) @@ -985,41 +1048,34 @@ pub fn render_tideline_footer( if item.bold { style = style.add_modifier(Modifier::BOLD); } + // Keep the state legible while its taught keyboard hint recedes: + // the chip keeps its semantic ink, the key paints in hint ink. let text = clip(x, &item.text); - // Keep the state legible while its taught keyboard hint recedes. - // Only known shortcut suffixes qualify; parenthetical scope/warnings - // retain their semantic ink. - let key_start = [footer.permission_key, footer.mode_key, Some("Ctrl+]")] - .into_iter() - .flatten() - .find_map(|key| { - let suffix = format!(" ({key})"); - item.text - .ends_with(&suffix) - .then(|| item.text.len() - suffix.len()) - }); - if let Some(start) = key_start.filter(|start| *start < text.len()) { - let (label, key) = text.split_at(start); - tput(buf, x as u16, area.y, label, style); + tput(buf, x as u16, area.y, &text, style); + let key_x = x + item.text.width(); + let key = item + .key + .as_deref() + .map(|key| clip(key_x, key)) + .unwrap_or_default(); + if !key.is_empty() { tput( buf, - (x + label.width()) as u16, + key_x as u16, area.y, - key, + &key, tchrome(theme, ChromeInk::MetadataHint), ); - } else { - tput(buf, x as u16, area.y, &text, style); } if let Some(count_index) = item.count_index && !text.is_empty() { count_rects.push(( count_index, - Rect::new(x as u16, area.y, text.width() as u16, 1), + Rect::new(x as u16, area.y, (text.width() + key.width()) as u16, 1), )); } - x += item.text.width(); + x += item.width(); } if let Some((text, ink)) = right @@ -1035,7 +1091,9 @@ pub fn render_tideline_footer( /// to [`TidelineFooter`] for painting. pub(crate) struct TidelineFooterFacts { pub permission_chip: (String, codewhale_palette::ChromeInk), - pub permission_key: Option<&'static str>, + /// `Shift+Tab to change`, localized, while the binding is live and not + /// yet learned. + pub permission_key: Option, pub mode_chip: Option<(String, codewhale_palette::ChromeInk)>, pub mode_key: Option<&'static str>, pub turn_clock: ClockReading, @@ -1063,7 +1121,7 @@ impl TidelineFooterFacts { theme, (self.permission_chip.0.as_str(), self.permission_chip.1), ) - .permission_key(self.permission_key) + .permission_key(self.permission_key.as_deref()) .mode_chip(borrow(&self.mode_chip)) .mode_key(self.mode_key) .turn_clock(borrow(&self.turn_clock)) @@ -1357,12 +1415,16 @@ pub(crate) fn tideline_footer_from_app(app: &mut App, width: u16) -> TidelineFoo let (counts, count_actions) = live_counts(app, tier); TidelineFooterFacts { permission_chip, - permission_key: live_chord(ShellBindingId::PermissionCycle).filter(|_| { - !crate::tui::footer_hints::retired( - &app.footer_hint_uses, - crate::tui::footer_hints::PERMISSION_CYCLE, - ) - }), + permission_key: live_chord(ShellBindingId::PermissionCycle) + .filter(|_| { + !crate::tui::footer_hints::retired( + &app.footer_hint_uses, + crate::tui::footer_hints::PERMISSION_CYCLE, + ) + }) + .map(|chord| { + tr(app.ui_locale, MessageId::FooterPermissionKeyHint).replace("{key}", chord) + }), mode_chip, mode_key: live_chord(ShellBindingId::ModeCycle).filter(|_| { !crate::tui::footer_hints::retired( diff --git a/crates/tui/src/tui/phase_strip/tideline_tests.rs b/crates/tui/src/tui/phase_strip/tideline_tests.rs index 0f052ad9af..b5ce51b268 100644 --- a/crates/tui/src/tui/phase_strip/tideline_tests.rs +++ b/crates/tui/src/tui/phase_strip/tideline_tests.rs @@ -27,7 +27,7 @@ struct Fixture { fn working() -> Fixture { Fixture { permission: ("ask", ChromeInk::PermissionAsk), - permission_key: Some("Shift+Tab"), + permission_key: Some("Shift+Tab to change"), mode: Some(("work", ChromeInk::PolicyAct)), mode_key: Some("Tab"), turn_clock: Some(("working 1m 15s", ChromeInk::Active)), @@ -83,10 +83,26 @@ fn posture_bar_reads_permission_mode_clock_counts_hint() { let band = text.lines().last().unwrap_or_default().trim_end(); assert_eq!( band, - " ask (Shift+Tab) work (Tab) working 1m 15s 2 agents worked 41m 12s Esc to interrupt" + " ● ask Shift+Tab to change work (Tab) working 1m 15s 2 agents worked 41m 12s Esc to interrupt" ); } +/// Experience mark 8: the current permission reads without color. With every +/// ink stripped the row still marks which chip is the permission in force +/// (`●`) and says what its key does, instead of printing two look-alike +/// `word (key)` chips that read as a menu of options. +#[test] +fn current_permission_is_identifiable_without_color() { + for (w, h) in BLOCKER_SIZES { + let text = draw(w, h, &working().widget(&UI_THEME)); + let band = text.lines().last().unwrap_or_default(); + assert!(band.starts_with(" ● ask"), "{w}x{h}: {band}"); + assert_eq!(band.matches('●').count(), 1, "{w}x{h}: {band}"); + assert!(band.contains("Shift+Tab to change"), "{w}x{h}: {band}"); + assert!(!band.contains("(Shift+Tab)"), "{w}x{h}: {band}"); + } +} + /// The bar carries no cost and no context reading: the metrics line owns /// both. The elapsed reading is this bar's again (#5914) — a fixed row is /// the only place a glancing user can find it during a multi-hour session — @@ -148,7 +164,7 @@ fn posture_bar_pins_notice_or_remote_control_right() { fixture.right = Some(("/rc connected", ChromeInk::Info)); let text = draw(100, 30, &fixture.widget(&UI_THEME)); assert!(text.trim_end().ends_with("/rc connected"), "{text}"); - assert!(text.contains(" ask (Shift+Tab)"), "{text}"); + assert!(text.contains(" ● ask Shift+Tab to change"), "{text}"); fixture.right = Some(("Auto-denied exec_shell", ChromeInk::Attention)); let text = draw(100, 30, &fixture.widget(&UI_THEME)); @@ -164,7 +180,8 @@ fn posture_bar_pins_notice_or_remote_control_right() { } /// Shed ladder, most expendable first: the turn clock, the session clock, -/// the hint, the counts, mode key, mode, permission key. The permission chip +/// the permission key (a binding reminder, and the widest optional item), +/// the hint, the counts, mode key, mode. The permission chip /// never sheds (#5796); the clock is what a glance wants and the hint and /// counts are what a keystroke wants, so on a row too narrow for both the /// clock goes (#5914). @@ -185,14 +202,14 @@ fn posture_bar_sheds_the_clocks_then_the_hint_counts_and_posture_chips() { // a needle only the mode paints. let mode_key = narrowest_showing("work (Tab)"); let mode = narrowest_showing(" work "); - let permission_key = narrowest_showing("(Shift+Tab)"); + let permission_key = narrowest_showing("Shift+Tab to change"); assert!( turn_clock > session_clock - && session_clock > hint + && session_clock > permission_key + && permission_key > hint && hint > counts && counts > mode_key - && mode_key > mode - && mode > permission_key, + && mode_key > mode, "turn_clock@{turn_clock} session_clock@{session_clock} hint@{hint} counts@{counts} mode_key@{mode_key} mode@{mode} permission_key@{permission_key}" ); for w in 8..=160u16 { @@ -219,12 +236,13 @@ fn compact_posture_bar_states_posture_and_nothing_live() { let mut fixture = working(); fixture.right = Some(("/rc connected", ChromeInk::Info)); let wide = draw(160, 3, &fixture.widget(&UI_THEME).compact(true)); - for kept in [" ask (Shift+Tab)", " work (Tab)", "/rc connected"] { + for kept in [" ● ask", " work (Tab)", "/rc connected"] { assert!(wide.contains(kept), "compact keeps {kept}: {wide}"); } for gone in [ "working 1m 15s", "worked 41m 12s", + "Shift+Tab to change", "2 agents", "Esc to interrupt", ] { @@ -285,7 +303,7 @@ fn posture_bar_prints_cycle_keys_only_when_live() { fixture.permission_key = None; let text = draw(120, 30, &fixture.widget(&UI_THEME)); assert!( - text.contains(" ask work working 1m 15s 2 agents worked 41m 12s"), + text.contains(" ● ask work working 1m 15s 2 agents worked 41m 12s"), "{text}" ); assert!(!text.contains('('), "{text}"); @@ -297,7 +315,7 @@ fn posture_bar_ascii_safe_projects_glyphs() { fixture.context_percent = 90; let text = draw(100, 30, &fixture.widget(&UI_THEME).ascii_safe(true)); let band = text.lines().last().unwrap_or_default(); - assert!(band.starts_with(" ask"), "inset preserved: {band}"); + assert!(band.starts_with(" . ask"), "inset preserved: {band}"); assert!(text.contains("^ surface soon"), "{text}"); for ch in text.chars() { if ch != '\n' { @@ -350,14 +368,14 @@ fn set_uses(app: &mut App, key: &str, uses: u8) { fn cycle_keys_show_at_zero_and_one_use_and_go_bare_at_two() { let mut app = session_app(); let facts = tideline_footer_from_app(&mut app, 120); - assert_eq!(facts.permission_key, Some("Shift+Tab")); + assert_eq!(facts.permission_key.as_deref(), Some("Shift+Tab to change")); assert_eq!(facts.mode_key, Some("Tab")); for key in [PERMISSION_CYCLE, MODE_CYCLE] { set_uses(&mut app, key, 1); } let facts = tideline_footer_from_app(&mut app, 120); - assert_eq!(facts.permission_key, Some("Shift+Tab")); + assert_eq!(facts.permission_key.as_deref(), Some("Shift+Tab to change")); assert_eq!(facts.mode_key, Some("Tab")); set_uses(&mut app, PERMISSION_CYCLE, 2); @@ -717,8 +735,9 @@ fn posture_shortcuts_recede_without_changing_count_click_targets() { let mut buf = Buffer::empty(Rect::new(0, 0, 120, 1)); let targets = render_tideline_footer(buf.area, &mut buf, &fixture.widget(&UI_THEME)); let text: String = buf.content().iter().map(|cell| cell.symbol()).collect(); - let label = text.find("ask").unwrap() as u16; - let key = text.find("(Shift+Tab)").unwrap() as u16; + let column = |needle: &str| text[..text.find(needle).unwrap()].chars().count() as u16; + let label = column("ask"); + let key = column("Shift+Tab to change"); assert!(buf[(label, 0)].modifier.contains(Modifier::BOLD)); assert!(!buf[(key, 0)].modifier.contains(Modifier::BOLD)); assert_eq!( diff --git a/crates/tui/src/tui/ui/frame/one_owner_tests.rs b/crates/tui/src/tui/ui/frame/one_owner_tests.rs index b4961b1216..5d530568b1 100644 --- a/crates/tui/src/tui/ui/frame/one_owner_tests.rs +++ b/crates/tui/src/tui/ui/frame/one_owner_tests.rs @@ -161,7 +161,9 @@ fn composed_frame_paints_each_fact_in_exactly_one_row() { // (#5950 — it used to go silent below 50%). let mut facts = vec![ ("mode chip", format!(" {mode} (")), - ("permission chip", format!(" {permission} (")), + // Mark 8: the permission in force is marked `●` and followed by + // what its key does, not a parenthesised chord. + ("permission chip", format!("● {permission}")), ("model", model), ("cost", super::session_cost_label(&app)), ("agent count", "2 agents".to_string()), @@ -190,7 +192,7 @@ fn composed_frame_paints_each_fact_in_exactly_one_row() { // roster — never the other way round. let posture = rows .iter() - .position(|row| row.contains("(Shift+Tab)")) + .position(|row| row.contains("● ")) .expect("posture bar"); let metrics = rows .iter() @@ -284,7 +286,7 @@ fn idle_frame_keeps_two_chrome_rows_and_last_turn_metrics() { // count (shed priority 7, ahead of the help hint) is shed by design. let rows = draw(&mut app, 120, 32); let composer = app.viewport.last_composer_area.unwrap().bottom() as usize; - assert!(rows[composer].contains("(Shift+Tab)"), "{}", rows[composer]); + assert!(rows[composer].contains("● "), "{}", rows[composer]); // The idle fixture sits at 0% context and says so: the reading is on // the row at every fullness (#5950), not only once it is a problem. assert!( @@ -373,7 +375,7 @@ fn row_presets_reclaim_rows_and_quiet_them_in_the_composed_frame() { // halves only both fit beside the pinned unenforced-scope permission // chip from that width up, and this test asserts the full row's clocks. let (width, height) = (160u16, 32u16); - let posture_row = |rows: &[String]| rows.iter().position(|row| row.contains("(Shift+Tab)")); + let posture_row = |rows: &[String]| rows.iter().position(|row| row.contains("● ")); let metrics_row = |rows: &[String]| rows.iter().position(|row| row.contains("context ")); let mut app = working_app(); diff --git a/crates/tui/src/tui/widgets/mod.rs b/crates/tui/src/tui/widgets/mod.rs index a9ddb3dc65..a35c3b1eb1 100644 --- a/crates/tui/src/tui/widgets/mod.rs +++ b/crates/tui/src/tui/widgets/mod.rs @@ -2645,14 +2645,18 @@ fn approval_option_style(is_selected: bool, color: Color) -> Style { } } -/// The approval card's heading. English leads with the plain summary of the -/// call (E6); the summary is not localized yet, so other packs keep the tool -/// name rather than mixing an English sentence into translated chrome. +/// The approval card's heading: the plain summary of the call (E6), in the +/// card's language, falling back to the tool name only when no summary was +/// derived. fn approval_heading(request: &ApprovalRequest, locale: Locale) -> String { - if matches!(locale, Locale::En) && !request.summary.trim().is_empty() { - request.summary.clone() - } else { + if request.summary.trim().is_empty() { + return request.tool_name.clone(); + } + let summary = request.summary_for_locale(locale); + if summary.trim().is_empty() { request.tool_name.clone() + } else { + summary } }