feat(telemetry): count models, tokens and tools; honor DO_NOT_TRACK (schema 4, notice 6)

Usage reporting adds two aggregate events per session: model_usage (provider,
catalog model or "other", turns by outcome, input/output/cache-read/cache-write
tokens, turn-time buckets) and tool_usage (first-party tool name, or "mcp"/"other",
with calls and errors). Models are named only when in the bundled public
models.dev catalog; tool names come from a closed list. No content, arguments or
paths. CI and GitHub Action runs report the "ci" session source. DO_NOT_TRACK=1
turns reporting off like CODEWHALE_TELEMETRY=0. The notice bump re-shows the
disclosure once in all 15 locales. The ingest accepts v4 and still accepts
v3/notice 5; docs/TELEMETRY.md describes both events.

Re-applied from recovered commit a20702df43 (built on the S57 split) onto main
78580d3d4d without conflicts. Founder approved for 0.10.3 (D14).

DEPLOY ORDER: the live ingest rejects schema 4. Deploy the ingest before any
Engine release that sends v4. Nothing deployed here.

Verified on the frozen tree (artifacts/claude-0103-build-20261029-0418-telemetry/):
codewhale-telemetry 53/53, codewhale-localization 54/54, codewhale-config
telemetry 17/17, codewhale-tui lib telemetry 40/40, setup_state 11/11,
telemetry-ingest vitest 174/174 and tsc --noEmit clean. Not re-run on this tree:
the real-turn exec --auto into wrangler dev and the DO_NOT_TRACK zero-request run
reported on the original commit.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Sonnet 5.5 committed 2026-10-29 05:01:26 -07:00
1 parent 78580d3d4d
commit e908f3f736
31 files changed
+810 -84

No files matched your search

+1 -1
View File
@@ -45,7 +45,7 @@ pub const SETUP_STATE_FILE_NAME: &str = "setup_state.json";
/// when the collection policy, schema, or disclosure materially changes. Prior
/// declines remain off. Keying it to the app version would re-prompt every
/// release, which is nagging with extra steps.
pub const TELEMETRY_NOTICE_VERSION: &str = "5";
pub const TELEMETRY_NOTICE_VERSION: &str = "6";
/// Canonical setup step ids. The ordering matches the first-run spine so a
/// `BTreeMap<SetupStep, _>` renders in wizard order.
+10
View File
@@ -47,6 +47,9 @@ pub const TELEMETRY_FLOOR_ENV: &str = "CODEWHALE_TELEMETRY_FLOOR";
/// is read instead, where an explicit "off" or an unreadable value is a floor.
#[must_use]
pub fn telemetry_floor_in_force() -> bool {
if do_not_track() {
return true;
}
if let Ok(raw) = std::env::var(TELEMETRY_FLOOR_ENV)
&& let Ok(declared) = parse_bool(&raw)
{
@@ -60,6 +63,10 @@ pub fn telemetry_floor_in_force() -> bool {
!matches!(parse_bool(&raw), Ok(true))
}
fn do_not_track() -> bool {
std::env::var("DO_NOT_TRACK").is_ok_and(|raw| matches!(parse_bool(&raw), Ok(true)))
}
/// Where resolved telemetry consent came from (#5441).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TelemetrySource {
@@ -95,6 +102,9 @@ impl TelemetrySource {
/// simply `(None, false)`. Shared by the runtime resolver and the
/// provenance surfaces so they cannot drift.
pub(crate) fn read_telemetry_env() -> (Option<bool>, bool) {
if do_not_track() {
return (Some(false), false);
}
let Some(raw) = std::env::var("CODEWHALE_TELEMETRY")
.or_else(|_| std::env::var("DEEPSEEK_TELEMETRY"))
.ok()
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "amplia",
"TranscriptThought": "Ha acabat de pensar",
"TranscriptThoughtFor": "Ha pensat durant {duration}",
"TelemetryNoticeDefaultOn": "Les estadístiques d’ús estan activades per defecte. Compartir totals de versió, plataforma, sessions, funcions i errors amb Codewhale i l’encarregat del tractament PostHog. Sense contingut ni IP. docs/TELEMETRY.md Desactiva: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Les estadístiques d’ús estan activades per defecte. Compartir totals de versió, plataforma, sessions, funcions i errors amb Codewhale i l’encarregat del tractament PostHog. També compta proveïdors, models del catàleg públic, totals de tokens i l’ús de les eines integrades. Sense contingut ni IP. docs/TELEMETRY.md Desactiva: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Estadístiques d’ús de Codewhale",
"TelemetryNoticeBody": "Codewhale compta: la versió que executes, el sistema operatiu i la família\nde CPU, la durada i el resultat de la sessió, i recomptes agregats de funcions\ni errors.\n\nNo recopila mai les teves converses, codi, prompts, fitxers, noms de\nfitxers, repositoris o branques, contingut del model ni credencials — i mai no\nenvia una cronologia de l'activitat de l'agent per torn o eina.\n\nNomés t'identifica un identificador aleatori desat en aquesta màquina, que se\nsubstitueix cada 90 dies. Pots canviar d'opinió en qualsevol moment:\n codewhale config set telemetry false\n\nEsquema complet, camp per camp: docs/TELEMETRY.md\n\nLes estadístiques d’ús estan activades per defecte. Codewhale i PostHog tracten aquests totals quan l’enviament està configurat. No es recopila cap IP.",
"TelemetryNoticeCompactBody": "Compta: versió, SO/CPU, temps/resultat, funcions/errors.\nID local rota/90d.\nMai: xats/codi/prompts/fitxers/noms; contingut del model/credencials; torns/eines.\nVegeu: docs/TELEMETRY.md\nDesact.: codewhale config set telemetry false\nLes estadístiques d’ús estan activades per defecte. Codewhale i PostHog tracten aquests totals quan l’enviament està configurat. No es recopila cap IP.",
"TelemetryNoticeBody": "Codewhale compta: la versió que executes, el sistema operatiu i la família\nde CPU, la durada i el resultat de la sessió, i recomptes agregats de funcions\ni errors.\n\nTambé compta els proveïdors i els models del catàleg públic que fas servir, els seus totals de tokens i quantes vegades s’executa cada eina integrada.\n\nNo recopila mai les teves converses, codi, prompts, fitxers, noms de\nfitxers, repositoris o branques, contingut del model ni credencials — i mai no\nenvia una cronologia de l'activitat de l'agent per torn o eina.\n\nNomés t'identifica un identificador aleatori desat en aquesta màquina, que se\nsubstitueix cada 90 dies. Pots canviar d'opinió en qualsevol moment:\n codewhale config set telemetry false\n\nEsquema complet, camp per camp: docs/TELEMETRY.md\n\nLes estadístiques d’ús estan activades per defecte. Codewhale i PostHog tracten aquests totals quan l’enviament està configurat. No es recopila cap IP.",
"TelemetryNoticeCompactBody": "Compta: versió, SO/CPU, temps/resultat, funcions/errors.\nTambé: proveïdor/model, tokens, eines.\nID local rota/90d.\nMai: xats/codi/prompts/fitxers/noms; contingut del model/credencials; torns/eines.\nVegeu: docs/TELEMETRY.md\nDesact.: codewhale config set telemetry false\nLes estadístiques d’ús estan activades per defecte. Codewhale i PostHog tracten aquests totals quan l’enviament està configurat. No es recopila cap IP.",
"TelemetryNoticeChoiceKeep": "Sí, mantenir el recompte anònim",
"TelemetryNoticeChoiceDisable": "No, desactivar el seguiment",
"TelemetryNoticeActionChoose": "triar",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "erweitern",
"TranscriptThought": "Nachgedacht",
"TranscriptThoughtFor": "{duration} nachgedacht",
"TelemetryNoticeDefaultOn": "Nutzungsstatistiken sind standardmäßig aktiviert. Aggregierte Versions-, Plattform-, Sitzungs-, Funktions- und Fehlerzahlen mit Codewhale und dem Auftragsverarbeiter PostHog teilen. Keine Inhalte oder IPs. docs/TELEMETRY.md Ausschalten: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Nutzungsstatistiken sind standardmäßig aktiviert. Aggregierte Versions-, Plattform-, Sitzungs-, Funktions- und Fehlerzahlen mit Codewhale und dem Auftragsverarbeiter PostHog teilen. Zählt außerdem Anbieter, öffentliche Katalogmodelle, Token-Summen und die Nutzung eingebauter Werkzeuge. Keine Inhalte oder IPs. docs/TELEMETRY.md Ausschalten: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale-Nutzungsstatistiken",
"TelemetryNoticeBody": "Codewhale zählt: die ausgeführte Version, Betriebssystem und Prozessorfamilie,\nSitzungsdauer und -ergebnis sowie zusammengefasste Funktions- und\nFehlerzähler.\n\nEs erfasst nie Unterhaltungen, Code, Prompts, Dateien, Datei-, Repository-\noder Branch-Namen, Modellinhalte oder Zugangsdaten — und sendet nie eine\nAktivitätschronik des Agenten pro Runde oder Werkzeug.\n\nZur Identifikation dient nur eine zufällige, auf diesem Gerät gespeicherte\nKennung, die alle 90 Tage ersetzt wird. Sie können Ihre Meinung jederzeit\nändern:\n codewhale config set telemetry false\n\nVollständiges Schema, Feld für Feld: docs/TELEMETRY.md\n\nNutzungsstatistiken sind standardmäßig aktiviert. Codewhale und PostHog verarbeiten diese Zahlen, wenn die Übermittlung konfiguriert ist. IPs werden nicht erfasst.",
"TelemetryNoticeCompactBody": "Zählt: Version, OS/CPU, Dauer/Ergebnis, Funktionen/Fehler.\nLokale Zufalls-ID wechselt/90T.\nNie: Chat/Code/Prompts/Dateien/Namen; Modellinhalt/Zugangsdaten; Runden/Werkzeuge.\nSchema: docs/TELEMETRY.md\nAus: codewhale config set telemetry false\nNutzungsstatistiken sind standardmäßig aktiviert. Codewhale und PostHog verarbeiten diese Zahlen, wenn die Übermittlung konfiguriert ist. IPs werden nicht erfasst.",
"TelemetryNoticeBody": "Codewhale zählt: die ausgeführte Version, Betriebssystem und Prozessorfamilie,\nSitzungsdauer und -ergebnis sowie zusammengefasste Funktions- und\nFehlerzähler.\n\nAußerdem zählt es die Anbieter und öffentlichen Katalogmodelle, die du nutzt, ihre Token-Summen und wie oft jedes eingebaute Werkzeug läuft.\n\nEs erfasst nie Unterhaltungen, Code, Prompts, Dateien, Datei-, Repository-\noder Branch-Namen, Modellinhalte oder Zugangsdaten — und sendet nie eine\nAktivitätschronik des Agenten pro Runde oder Werkzeug.\n\nZur Identifikation dient nur eine zufällige, auf diesem Gerät gespeicherte\nKennung, die alle 90 Tage ersetzt wird. Sie können Ihre Meinung jederzeit\nändern:\n codewhale config set telemetry false\n\nVollständiges Schema, Feld für Feld: docs/TELEMETRY.md\n\nNutzungsstatistiken sind standardmäßig aktiviert. Codewhale und PostHog verarbeiten diese Zahlen, wenn die Übermittlung konfiguriert ist. IPs werden nicht erfasst.",
"TelemetryNoticeCompactBody": "Zählt: Version, OS/CPU, Dauer/Ergebnis, Funktionen/Fehler.\nAußerdem: Anbieter/Modell, Token, Werkzeuge.\nLokale Zufalls-ID wechselt/90T.\nNie: Chat/Code/Prompts/Dateien/Namen; Modellinhalt/Zugangsdaten; Runden/Werkzeuge.\nSchema: docs/TELEMETRY.md\nAus: codewhale config set telemetry false\nNutzungsstatistiken sind standardmäßig aktiviert. Codewhale und PostHog verarbeiten diese Zahlen, wenn die Übermittlung konfiguriert ist. IPs werden nicht erfasst.",
"TelemetryNoticeChoiceKeep": "Ja, anonyme Zählung beibehalten",
"TelemetryNoticeChoiceDisable": "Nein, Nachverfolgung abschalten",
"TelemetryNoticeActionChoose": "wählen",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "expand",
"TranscriptThought": "Thought",
"TranscriptThoughtFor": "Thought for {duration}",
"TelemetryNoticeDefaultOn": "Usage reporting is on by default. Share aggregate version/platform, session, feature and error counts with Codewhale and PostHog (processor). No content or IP. docs/TELEMETRY.md Turn off: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Usage reporting is on by default. Share aggregate version/platform, session, feature and error counts with Codewhale and PostHog (processor). Also counts providers, public catalog models, token totals and built-in tool use. No content or IP. docs/TELEMETRY.md Turn off: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale usage reporting",
"TelemetryNoticeBody": "Codewhale counts: which version you run, OS and CPU family, session\nduration and outcome, and aggregate feature and error counters.\n\nIt never collects your conversations, code, prompts, files, repo or\nbranch names, model content, or credentials — and it never sends a\nper-turn or per-tool timeline of agent activity.\n\nYou are identified only by a random ID stored on this machine, replaced\nevery 90 days. Change your mind any time:\n codewhale config set telemetry false\n\nFull schema, field by field: docs/TELEMETRY.md\n\nUsage reporting is on by default. Codewhale and PostHog process these counts when delivery is configured. No IP is collected.",
"TelemetryNoticeCompactBody": "Counts: version, OS/CPU, session\ntime/result, feature/error totals.\nRandom local ID rotates every 90d.\nNever: chat/code/prompts/files/names;\nmodel content/credentials; turn/tools.\nSchema: docs/TELEMETRY.md\nOff: codewhale config set telemetry\nfalse\nUsage reporting is on by default. Codewhale and PostHog process these counts when delivery is configured. No IP is collected.",
"TelemetryNoticeBody": "Codewhale counts: which version you run, OS and CPU family, session\nduration and outcome, and aggregate feature and error counters.\n\nIt also counts the providers and public catalog models you use, their\ntoken totals, and how often each built-in tool runs.\n\nIt never collects your conversations, code, prompts, files, repo or\nbranch names, model content, or credentials — and it never sends a\nper-turn or per-tool timeline of agent activity.\n\nYou are identified only by a random ID stored on this machine, replaced\nevery 90 days. Change your mind any time:\n codewhale config set telemetry false\n\nFull schema, field by field: docs/TELEMETRY.md\n\nUsage reporting is on by default. Codewhale and PostHog process these counts when delivery is configured. No IP is collected.",
"TelemetryNoticeCompactBody": "Counts: version, OS/CPU, session\ntime/result, feature/error totals.\nAlso: provider/model, tokens, tools.\nRandom local ID rotates every 90d.\nNever: chat/code/prompts/files/names;\nmodel content/credentials; turn/tools.\nSchema: docs/TELEMETRY.md\nOff: codewhale config set telemetry\nfalse\nUsage reporting is on by default. Codewhale and PostHog process these counts when delivery is configured. No IP is collected.",
"TelemetryNoticeChoiceKeep": "Yes, keep anonymous counts",
"TelemetryNoticeChoiceDisable": "No, turn off tracking",
"TelemetryNoticeActionChoose": "choose",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "expandir",
"TranscriptThought": "Pensamiento terminado",
"TranscriptThoughtFor": "Pensó durante {duration}",
"TelemetryNoticeDefaultOn": "Las estadísticas de uso están activadas de forma predeterminada. Compartir totales de versión, plataforma, sesiones, funciones y errores con Codewhale y su procesador PostHog. Sin contenido ni IP. docs/TELEMETRY.md Desactivar: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Las estadísticas de uso están activadas de forma predeterminada. Compartir totales de versión, plataforma, sesiones, funciones y errores con Codewhale y su procesador PostHog. También cuenta proveedores, modelos del catálogo público, totales de tokens y el uso de herramientas integradas. Sin contenido ni IP. docs/TELEMETRY.md Desactivar: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Estadísticas de uso de Codewhale",
"TelemetryNoticeBody": "Codewhale cuenta: la versión que ejecutas, el sistema operativo y la familia\nde CPU, la duración y el resultado de la sesión, y conteos agregados de\nfunciones y errores.\n\nNunca recopila tus conversaciones, código, prompts, archivos, nombres de\narchivos, repositorios o ramas, contenido del modelo ni credenciales — y nunca\nenvía una cronología de la actividad del agente por turno o herramienta.\n\nSolo te identifica un ID aleatorio almacenado en esta máquina, que se\nreemplaza cada 90 días. Cambia de opinión cuando quieras:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md\n\nLas estadísticas de uso están activadas de forma predeterminada. Codewhale y PostHog procesan estos totales cuando el envío está configurado. No se recopilan IP.",
"TelemetryNoticeCompactBody": "Cuenta: versión, SO/CPU, tiempo/resultado, funciones/errores.\nID local rota/90d.\nNunca: chats/código/prompts/archivos/nombres; datos del modelo/credenciales; turnos/herramientas.\nVer: docs/TELEMETRY.md\nApag.: codewhale config set telemetry false\nLas estadísticas de uso están activadas de forma predeterminada. Codewhale y PostHog procesan estos totales cuando el envío está configurado. No se recopilan IP.",
"TelemetryNoticeBody": "Codewhale cuenta: la versión que ejecutas, el sistema operativo y la familia\nde CPU, la duración y el resultado de la sesión, y conteos agregados de\nfunciones y errores.\n\nTambién cuenta los proveedores y modelos del catálogo público que usas, sus totales de tokens y cuántas veces se ejecuta cada herramienta integrada.\n\nNunca recopila tus conversaciones, código, prompts, archivos, nombres de\narchivos, repositorios o ramas, contenido del modelo ni credenciales — y nunca\nenvía una cronología de la actividad del agente por turno o herramienta.\n\nSolo te identifica un ID aleatorio almacenado en esta máquina, que se\nreemplaza cada 90 días. Cambia de opinión cuando quieras:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md\n\nLas estadísticas de uso están activadas de forma predeterminada. Codewhale y PostHog procesan estos totales cuando el envío está configurado. No se recopilan IP.",
"TelemetryNoticeCompactBody": "Cuenta: versión, SO/CPU, tiempo/resultado, funciones/errores.\nTambién: proveedor/modelo, tokens, herramientas.\nID local rota/90d.\nNunca: chats/código/prompts/archivos/nombres; datos del modelo/credenciales; turnos/herramientas.\nVer: docs/TELEMETRY.md\nApag.: codewhale config set telemetry false\nLas estadísticas de uso están activadas de forma predeterminada. Codewhale y PostHog procesan estos totales cuando el envío está configurado. No se recopilan IP.",
"TelemetryNoticeChoiceKeep": "Sí, mantener el conteo anónimo",
"TelemetryNoticeChoiceDisable": "No, desactivar el seguimiento",
"TelemetryNoticeActionChoose": "elegir",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "développer",
"TranscriptThought": "Réflexion terminée",
"TranscriptThoughtFor": "Réflexion pendant {duration}",
"TelemetryNoticeDefaultOn": "Les statistiques d’utilisation sont activées par défaut. Partager les totaux de versions, plateformes, sessions, fonctions et erreurs avec Codewhale et son sous-traitant PostHog. Aucun contenu ni IP. docs/TELEMETRY.md Désactiver : codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Les statistiques d’utilisation sont activées par défaut. Partager les totaux de versions, plateformes, sessions, fonctions et erreurs avec Codewhale et son sous-traitant PostHog. Compte aussi les fournisseurs, les modèles du catalogue public, les totaux de jetons et l’usage des outils intégrés. Aucun contenu ni IP. docs/TELEMETRY.md Désactiver : codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Statistiques d’utilisation de Codewhale",
"TelemetryNoticeBody": "Codewhale compte : la version exécutée, le système d'exploitation et la\nfamille du processeur, la durée et le résultat de la session, ainsi que les\ntotaux de fonctions et d'erreurs.\n\nIl ne collecte jamais vos conversations, votre code, vos prompts, fichiers,\nnoms de fichiers, de dépôts ou de branches, contenus du modèle ou identifiants\n— et n'envoie jamais de chronologie de l'activité par tour ou par outil.\n\nVous êtes identifié uniquement par un identifiant aléatoire stocké sur cette\nmachine, remplacé tous les 90 jours. Changez d'avis à tout moment :\n codewhale config set telemetry false\n\nSchéma complet, champ par champ : docs/TELEMETRY.md\n\nLes statistiques d’utilisation sont activées par défaut. Codewhale et PostHog traitent ces statistiques lorsque l’envoi est configuré. Aucune IP n’est collectée.",
"TelemetryNoticeCompactBody": "Compte: version, OS/CPU, durée/résultat, fonctions/erreurs.\nID local aléatoire change/90j.\nJamais: chats/code/prompts/fichiers/noms; contenu modèle/identifiants; tours/outils.\nVoir: docs/TELEMETRY.md\nArrêt: codewhale config set telemetry false\nLes statistiques d’utilisation sont activées par défaut. Codewhale et PostHog traitent ces statistiques lorsque l’envoi est configuré. Aucune IP n’est collectée.",
"TelemetryNoticeBody": "Codewhale compte : la version exécutée, le système d'exploitation et la\nfamille du processeur, la durée et le résultat de la session, ainsi que les\ntotaux de fonctions et d'erreurs.\n\nIl compte aussi les fournisseurs et modèles du catalogue public que vous utilisez, leurs totaux de jetons et la fréquence d’exécution de chaque outil intégré.\n\nIl ne collecte jamais vos conversations, votre code, vos prompts, fichiers,\nnoms de fichiers, de dépôts ou de branches, contenus du modèle ou identifiants\n— et n'envoie jamais de chronologie de l'activité par tour ou par outil.\n\nVous êtes identifié uniquement par un identifiant aléatoire stocké sur cette\nmachine, remplacé tous les 90 jours. Changez d'avis à tout moment :\n codewhale config set telemetry false\n\nSchéma complet, champ par champ : docs/TELEMETRY.md\n\nLes statistiques d’utilisation sont activées par défaut. Codewhale et PostHog traitent ces statistiques lorsque l’envoi est configuré. Aucune IP n’est collectée.",
"TelemetryNoticeCompactBody": "Compte: version, OS/CPU, durée/résultat, fonctions/erreurs.\nAussi : fournisseur/modèle, jetons, outils.\nID local aléatoire change/90j.\nJamais: chats/code/prompts/fichiers/noms; contenu modèle/identifiants; tours/outils.\nVoir: docs/TELEMETRY.md\nArrêt: codewhale config set telemetry false\nLes statistiques d’utilisation sont activées par défaut. Codewhale et PostHog traitent ces statistiques lorsque l’envoi est configuré. Aucune IP n’est collectée.",
"TelemetryNoticeChoiceKeep": "Oui, conserver le comptage anonyme",
"TelemetryNoticeChoiceDisable": "Non, désactiver le suivi",
"TelemetryNoticeActionChoose": "choisir",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "विस्तार",
"TranscriptThought": "सोच पूरी हुई",
"TranscriptThoughtFor": "{duration} तक सोचा",
"TelemetryNoticeDefaultOn": "उपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। संस्करण, प्लेटफ़ॉर्म, सत्र, सुविधा और त्रुटि के कुल आँकड़े Codewhale और प्रोसेसर PostHog से साझा करें। सामग्री या IP नहीं। docs/TELEMETRY.md बंद करें: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "उपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। संस्करण, प्लेटफ़ॉर्म, सत्र, सुविधा और त्रुटि के कुल आँकड़े Codewhale और प्रोसेसर PostHog से साझा करें। यह प्रदाता, सार्वजनिक कैटलॉग मॉडल, टोकन योग और अंतर्निहित औज़ारों का उपयोग भी गिनता है। सामग्री या IP नहीं। docs/TELEMETRY.md बंद करें: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale उपयोग आँकड़े",
"TelemetryNoticeBody": "Codewhale गणना करता है: चल रहा संस्करण, ऑपरेटिंग सिस्टम व प्रोसेसर परिवार,\nसत्र की अवधि व परिणाम, और सुविधाओं व त्रुटियों की कुल गणना।\n\nयह आपकी बातचीत, कोड, प्रॉम्प्ट, फ़ाइलें, फ़ाइल, भंडार या शाखा के नाम,\nमॉडल की सामग्री या परिचय प्रमाण कभी एकत्र नहीं करता, और प्रत्येक चरण या\nऔज़ार की एजेंट गतिविधि का क्रम भी कभी नहीं भेजता।\n\nपहचान के लिए केवल इस मशीन पर रखी एक आकस्मिक पहचान संख्या उपयोग होती है,\nजो हर 90 दिन में बदल दी जाती है। कभी भी अपना निर्णय बदलें:\n codewhale config set telemetry false\n\nहर क्षेत्र सहित पूरा प्रारूप: docs/TELEMETRY.md\n\nउपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। भेजने की व्यवस्था कॉन्फ़िगर होने पर Codewhale और PostHog इन आँकड़ों को संसाधित करते हैं। IP एकत्र नहीं किया जाता।",
"TelemetryNoticeCompactBody": "गणना: संस्करण, सिस्टम/प्रोसेसर, सत्र अवधि/परिणाम और सुविधा/त्रुटि योग।\nआकस्मिक स्थानीय पहचान हर 90 दिन में बदलती है।\nकभी नहीं: बातचीत/कोड/प्रॉम्प्ट/फ़ाइल/नाम, मॉडल सामग्री/परिचय प्रमाण, चरण/औज़ार।\nप्रारूप: docs/TELEMETRY.md\nबंद: codewhale config set telemetry false\nउपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। भेजने की व्यवस्था कॉन्फ़िगर होने पर Codewhale और PostHog इन आँकड़ों को संसाधित करते हैं। IP एकत्र नहीं किया जाता।",
"TelemetryNoticeBody": "Codewhale गणना करता है: चल रहा संस्करण, ऑपरेटिंग सिस्टम व प्रोसेसर परिवार,\nसत्र की अवधि व परिणाम, और सुविधाओं व त्रुटियों की कुल गणना।\n\nयह आपके इस्तेमाल किए गए प्रदाताओं और सार्वजनिक कैटलॉग मॉडलों, उनके टोकन योग, और हर अंतर्निहित औज़ार कितनी बार चलता है, यह भी गिनता है।\n\nयह आपकी बातचीत, कोड, प्रॉम्प्ट, फ़ाइलें, फ़ाइल, भंडार या शाखा के नाम,\nमॉडल की सामग्री या परिचय प्रमाण कभी एकत्र नहीं करता, और प्रत्येक चरण या\nऔज़ार की एजेंट गतिविधि का क्रम भी कभी नहीं भेजता।\n\nपहचान के लिए केवल इस मशीन पर रखी एक आकस्मिक पहचान संख्या उपयोग होती है,\nजो हर 90 दिन में बदल दी जाती है। कभी भी अपना निर्णय बदलें:\n codewhale config set telemetry false\n\nहर क्षेत्र सहित पूरा प्रारूप: docs/TELEMETRY.md\n\nउपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। भेजने की व्यवस्था कॉन्फ़िगर होने पर Codewhale और PostHog इन आँकड़ों को संसाधित करते हैं। IP एकत्र नहीं किया जाता।",
"TelemetryNoticeCompactBody": "गणना: संस्करण, सिस्टम/प्रोसेसर, सत्र अवधि/परिणाम और सुविधा/त्रुटि योग।\nसाथ ही: प्रदाता/मॉडल, टोकन, औज़ार उपयोग।\nआकस्मिक स्थानीय पहचान हर 90 दिन में बदलती है।\nकभी नहीं: बातचीत/कोड/प्रॉम्प्ट/फ़ाइल/नाम, मॉडल सामग्री/परिचय प्रमाण, चरण/औज़ार।\nप्रारूप: docs/TELEMETRY.md\nबंद: codewhale config set telemetry false\nउपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। भेजने की व्यवस्था कॉन्फ़िगर होने पर Codewhale और PostHog इन आँकड़ों को संसाधित करते हैं। IP एकत्र नहीं किया जाता।",
"TelemetryNoticeChoiceKeep": "हाँ, अनाम गणना जारी रखें",
"TelemetryNoticeChoiceDisable": "नहीं, ट्रैकिंग बंद करें",
"TelemetryNoticeActionChoose": "चुनें",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "perluas",
"TranscriptThought": "Selesai berpikir",
"TranscriptThoughtFor": "Berpikir selama {duration}",
"TelemetryNoticeDefaultOn": "Pelaporan penggunaan aktif secara bawaan. Bagikan total versi, platform, sesi, fitur, dan galat dengan Codewhale dan pemroses PostHog. Tanpa konten atau IP. docs/TELEMETRY.md Nonaktifkan: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Pelaporan penggunaan aktif secara bawaan. Bagikan total versi, platform, sesi, fitur, dan galat dengan Codewhale dan pemroses PostHog. Juga menghitung penyedia, model katalog publik, total token, dan penggunaan alat bawaan. Tanpa konten atau IP. docs/TELEMETRY.md Nonaktifkan: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Pelaporan penggunaan Codewhale",
"TelemetryNoticeBody": "Codewhale menghitung: versi yang dijalankan, sistem operasi dan keluarga CPU,\ndurasi dan hasil sesi, serta hitungan agregat fitur dan galat.\n\nIa tidak pernah mengumpulkan percakapan, kode, prompt, berkas, nama berkas,\nrepositori atau cabang, konten model, maupun kredensial Anda — dan tidak pernah\nmengirim linimasa aktivitas agen per giliran atau per alat.\n\nAnda hanya dikenali melalui ID acak yang disimpan di mesin ini, diganti setiap\n90 hari. Ubah keputusan kapan saja:\n codewhale config set telemetry false\n\nSkema lengkap, per bidang: docs/TELEMETRY.md\n\nPelaporan penggunaan aktif secara bawaan. Codewhale dan PostHog memproses hitungan ini jika pengiriman dikonfigurasi. IP tidak dikumpulkan.",
"TelemetryNoticeCompactBody": "Hitungan: versi, sistem/CPU, durasi/hasil, serta total fitur/galat.\nID lokal acak diganti setiap 90 hari.\nTidak pernah: percakapan/kode/prompt/berkas/nama, konten model/kredensial, giliran/alat.\nSkema: docs/TELEMETRY.md\nNonaktifkan: codewhale config set telemetry false\nPelaporan penggunaan aktif secara bawaan. Codewhale dan PostHog memproses hitungan ini jika pengiriman dikonfigurasi. IP tidak dikumpulkan.",
"TelemetryNoticeBody": "Codewhale menghitung: versi yang dijalankan, sistem operasi dan keluarga CPU,\ndurasi dan hasil sesi, serta hitungan agregat fitur dan galat.\n\nJuga menghitung penyedia dan model katalog publik yang Anda gunakan, total tokennya, dan seberapa sering setiap alat bawaan dijalankan.\n\nIa tidak pernah mengumpulkan percakapan, kode, prompt, berkas, nama berkas,\nrepositori atau cabang, konten model, maupun kredensial Anda — dan tidak pernah\nmengirim linimasa aktivitas agen per giliran atau per alat.\n\nAnda hanya dikenali melalui ID acak yang disimpan di mesin ini, diganti setiap\n90 hari. Ubah keputusan kapan saja:\n codewhale config set telemetry false\n\nSkema lengkap, per bidang: docs/TELEMETRY.md\n\nPelaporan penggunaan aktif secara bawaan. Codewhale dan PostHog memproses hitungan ini jika pengiriman dikonfigurasi. IP tidak dikumpulkan.",
"TelemetryNoticeCompactBody": "Hitungan: versi, sistem/CPU, durasi/hasil, serta total fitur/galat.\nJuga: penyedia/model, token, alat.\nID lokal acak diganti setiap 90 hari.\nTidak pernah: percakapan/kode/prompt/berkas/nama, konten model/kredensial, giliran/alat.\nSkema: docs/TELEMETRY.md\nNonaktifkan: codewhale config set telemetry false\nPelaporan penggunaan aktif secara bawaan. Codewhale dan PostHog memproses hitungan ini jika pengiriman dikonfigurasi. IP tidak dikumpulkan.",
"TelemetryNoticeChoiceKeep": "Ya, pertahankan penghitungan anonim",
"TelemetryNoticeChoiceDisable": "Tidak, matikan pelacakan",
"TelemetryNoticeActionChoose": "pilih",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "展開",
"TranscriptThought": "思考しました",
"TranscriptThoughtFor": "{duration}思考しました",
"TelemetryNoticeDefaultOn": "使用統計の送信は既定でオンです。 バージョン・環境・セッション・機能・エラーの集計値を Codewhale と処理委託先 PostHog に共有。内容や IP は収集しません。docs/TELEMETRY.md 無効にする: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "使用統計の送信は既定でオンです。 バージョン・環境・セッション・機能・エラーの集計値を Codewhale と処理委託先 PostHog に共有。プロバイダー、公開カタログのモデル、トークン合計、組み込みツールの使用回数も集計します。内容や IP は収集しません。docs/TELEMETRY.md 無効にする: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale の使用統計",
"TelemetryNoticeBody": "Codewhale が集計するのは次の情報です。実行中のバージョン、OS と CPU の種類、\nセッション時間と結果、機能およびエラーの集計カウンター。\n\n会話、コード、プロンプト、ファイル、リポジトリ名やブランチ名、モデルの内容、\n認証情報は収集しません。また、ターンごと、ツールごとのエージェント活動の\n履歴も送信しません。\n\n識別には、このマシンに保存されたランダム ID のみを使用します。この ID は\n90 日ごとに置き換えられます。あとからいつでも変更できます:\n codewhale config set telemetry false\n\n全フィールドのスキーマ: docs/TELEMETRY.md\n\n使用統計の送信は既定でオンです。 送信先が設定されている場合、Codewhale と PostHog が集計値を処理します。IP は収集しません。",
"TelemetryNoticeCompactBody": "集計: 版、OS/CPU、時間/結果、機能/エラー。\nローカルのランダムIDは90日ごとに更新。\n収集なし: 会話/コード/プロンプト/ファイル/名前、モデル内容/認証情報、ターン/ツール。\nスキーマ: docs/TELEMETRY.md\n無効: codewhale config set telemetry false\n使用統計の送信は既定でオンです。 送信先が設定されている場合、Codewhale と PostHog が集計値を処理します。IP は収集しません。",
"TelemetryNoticeBody": "Codewhale が集計するのは次の情報です。実行中のバージョン、OS と CPU の種類、\nセッション時間と結果、機能およびエラーの集計カウンター。\n\n使用するプロバイダーと公開カタログのモデル、そのトークン合計、各組み込みツールの実行回数も集計します。\n\n会話、コード、プロンプト、ファイル、リポジトリ名やブランチ名、モデルの内容、\n認証情報は収集しません。また、ターンごと、ツールごとのエージェント活動の\n履歴も送信しません。\n\n識別には、このマシンに保存されたランダム ID のみを使用します。この ID は\n90 日ごとに置き換えられます。あとからいつでも変更できます:\n codewhale config set telemetry false\n\n全フィールドのスキーマ: docs/TELEMETRY.md\n\n使用統計の送信は既定でオンです。 送信先が設定されている場合、Codewhale と PostHog が集計値を処理します。IP は収集しません。",
"TelemetryNoticeCompactBody": "集計: 版、OS/CPU、時間/結果、機能/エラー。\n他: プロバイダー/モデル、トークン、ツール。\nローカルのランダムIDは90日ごとに更新。\n収集なし: 会話/コード/プロンプト/ファイル/名前、モデル内容/認証情報、ターン/ツール。\nスキーマ: docs/TELEMETRY.md\n無効: codewhale config set telemetry false\n使用統計の送信は既定でオンです。 送信先が設定されている場合、Codewhale と PostHog が集計値を処理します。IP は収集しません。",
"TelemetryNoticeChoiceKeep": "はい、匿名の利用状況集計を続けます",
"TelemetryNoticeChoiceDisable": "いいえ、利用状況集計をオフにします",
"TelemetryNoticeActionChoose": "選択",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "펼치기",
"TranscriptThought": "생각 완료",
"TranscriptThoughtFor": "{duration} 동안 생각함",
"TelemetryNoticeDefaultOn": "사용 통계 보고는 기본적으로 켜져 있습니다. 버전·플랫폼·세션·기능·오류의 집계 수치를 Codewhale 및 처리업체 PostHog와 공유합니다. 콘텐츠나 IP는 수집하지 않습니다. docs/TELEMETRY.md 끄기: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "사용 통계 보고는 기본적으로 켜져 있습니다. 버전·플랫폼·세션·기능·오류의 집계 수치를 Codewhale 및 처리업체 PostHog와 공유합니다. 제공업체, 공개 카탈로그 모델, 토큰 합계, 기본 제공 도구 사용 횟수도 집계합니다. 콘텐츠나 IP는 수집하지 않습니다. docs/TELEMETRY.md 끄기: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale 사용 통계",
"TelemetryNoticeBody": "Codewhale이 집계하는 항목: 실행 중인 버전, 운영 체제와 CPU 계열, 세션\n시간과 결과, 기능 및 오류의 집계 수치입니다.\n\n대화, 코드, 프롬프트, 파일, 파일 이름, 저장소 또는 브랜치 이름, 모델\n내용이나 자격 증명은 절대 수집하지 않으며, 턴별 또는 도구별 에이전트 활동\n기록도 전송하지 않습니다.\n\n이 컴퓨터에 저장된 임의의 ID로만 식별되며, 90일마다 새 ID로 교체됩니다.\n언제든지 변경할 수 있습니다:\n codewhale config set telemetry false\n\n필드별 전체 스키마: docs/TELEMETRY.md\n\n사용 통계 보고는 기본적으로 켜져 있습니다. 전송이 설정된 경우 Codewhale과 PostHog가 이 수치를 처리합니다. IP는 수집하지 않습니다.",
"TelemetryNoticeCompactBody": "집계: 버전, 시스템/CPU, 세션 시간/결과, 기능/오류 합계.\n임의의 로컬 식별자는 90일마다 교체.\n수집 안 함: 대화/코드/프롬프트/파일/이름, 모델 내용/자격 증명, 턴/도구 활동.\n스키마: docs/TELEMETRY.md\n끄기: codewhale config set telemetry false\n사용 통계 보고는 기본적으로 켜져 있습니다. 전송이 설정된 경우 Codewhale과 PostHog가 이 수치를 처리합니다. IP는 수집하지 않습니다.",
"TelemetryNoticeBody": "Codewhale이 집계하는 항목: 실행 중인 버전, 운영 체제와 CPU 계열, 세션\n시간과 결과, 기능 및 오류의 집계 수치입니다.\n\n사용하는 제공업체와 공개 카탈로그 모델, 토큰 합계, 각 기본 제공 도구의 실행 횟수도 집계합니다.\n\n대화, 코드, 프롬프트, 파일, 파일 이름, 저장소 또는 브랜치 이름, 모델\n내용이나 자격 증명은 절대 수집하지 않으며, 턴별 또는 도구별 에이전트 활동\n기록도 전송하지 않습니다.\n\n이 컴퓨터에 저장된 임의의 ID로만 식별되며, 90일마다 새 ID로 교체됩니다.\n언제든지 변경할 수 있습니다:\n codewhale config set telemetry false\n\n필드별 전체 스키마: docs/TELEMETRY.md\n\n사용 통계 보고는 기본적으로 켜져 있습니다. 전송이 설정된 경우 Codewhale과 PostHog가 이 수치를 처리합니다. IP는 수집하지 않습니다.",
"TelemetryNoticeCompactBody": "집계: 버전, 시스템/CPU, 세션 시간/결과, 기능/오류 합계.\n추가: 제공업체/모델, 토큰, 도구 사용.\n임의의 로컬 식별자는 90일마다 교체.\n수집 안 함: 대화/코드/프롬프트/파일/이름, 모델 내용/자격 증명, 턴/도구 활동.\n스키마: docs/TELEMETRY.md\n끄기: codewhale config set telemetry false\n사용 통계 보고는 기본적으로 켜져 있습니다. 전송이 설정된 경우 Codewhale과 PostHog가 이 수치를 처리합니다. IP는 수집하지 않습니다.",
"TelemetryNoticeChoiceKeep": "네, 익명 집계를 유지합니다",
"TelemetryNoticeChoiceDisable": "아니요, 추적을 끄습니다",
"TelemetryNoticeActionChoose": "선택",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "expandir",
"TranscriptThought": "Pensamento concluído",
"TranscriptThoughtFor": "Pensou por {duration}",
"TelemetryNoticeDefaultOn": "As estatísticas de uso estão ativadas por padrão. Compartilhar totais de versão, plataforma, sessões, funções e erros com Codewhale e o operador PostHog. Sem conteúdo ou IP. docs/TELEMETRY.md Desativar: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "As estatísticas de uso estão ativadas por padrão. Compartilhar totais de versão, plataforma, sessões, funções e erros com Codewhale e o operador PostHog. Também conta provedores, modelos do catálogo público, totais de tokens e o uso de ferramentas integradas. Sem conteúdo ou IP. docs/TELEMETRY.md Desativar: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Estatísticas de uso do Codewhale",
"TelemetryNoticeBody": "O Codewhale conta: a versão em execução, a família do sistema operacional e da\nCPU, a duração e o resultado da sessão, e contagens agregadas de recursos e\nerros.\n\nEle nunca coleta suas conversas, código, prompts, arquivos, nomes de\narquivos, repositórios ou ramos, conteúdo do modelo nem credenciais — e nunca\nenvia uma cronologia da atividade do agente por turno ou ferramenta.\n\nVocê é identificado apenas por um ID aleatório armazenado nesta máquina,\nsubstituído a cada 90 dias. Mude de ideia a qualquer momento:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md\n\nAs estatísticas de uso estão ativadas por padrão. Codewhale e PostHog processam esses totais quando o envio está configurado. Nenhum IP é coletado.",
"TelemetryNoticeCompactBody": "Conta: versão, OS/CPU, tempo/resultado, funções/erros.\nID local: rotação 90d.\nNunca: chat/código/prompts/arquivos/nomes; conteúdo do modelo/credenciais; turnos/ferramentas.\nEsquema: docs/TELEMETRY.md\nDesl.: codewhale config set telemetry false\nAs estatísticas de uso estão ativadas por padrão. Codewhale e PostHog processam esses totais quando o envio está configurado. Nenhum IP é coletado.",
"TelemetryNoticeBody": "O Codewhale conta: a versão em execução, a família do sistema operacional e da\nCPU, a duração e o resultado da sessão, e contagens agregadas de recursos e\nerros.\n\nTambém conta os provedores e modelos do catálogo público que você usa, seus totais de tokens e quantas vezes cada ferramenta integrada é executada.\n\nEle nunca coleta suas conversas, código, prompts, arquivos, nomes de\narquivos, repositórios ou ramos, conteúdo do modelo nem credenciais — e nunca\nenvia uma cronologia da atividade do agente por turno ou ferramenta.\n\nVocê é identificado apenas por um ID aleatório armazenado nesta máquina,\nsubstituído a cada 90 dias. Mude de ideia a qualquer momento:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md\n\nAs estatísticas de uso estão ativadas por padrão. Codewhale e PostHog processam esses totais quando o envio está configurado. Nenhum IP é coletado.",
"TelemetryNoticeCompactBody": "Conta: versão, OS/CPU, tempo/resultado, funções/erros.\nTambém: provedor/modelo, tokens, ferramentas.\nID local: rotação 90d.\nNunca: chat/código/prompts/arquivos/nomes; conteúdo do modelo/credenciais; turnos/ferramentas.\nEsquema: docs/TELEMETRY.md\nDesl.: codewhale config set telemetry false\nAs estatísticas de uso estão ativadas por padrão. Codewhale e PostHog processam esses totais quando o envio está configurado. Nenhum IP é coletado.",
"TelemetryNoticeChoiceKeep": "Sim, manter as contagens anônimas",
"TelemetryNoticeChoiceDisable": "Não, desativar o rastreamento",
"TelemetryNoticeActionChoose": "escolher",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "развернуть",
"TranscriptThought": "Обдумывание завершено",
"TranscriptThoughtFor": "Обдумывание заняло {duration}",
"TelemetryNoticeDefaultOn": "Отправка статистики использования включена по умолчанию. Передавать сводные счётчики версий, платформ, сеансов, функций и ошибок Codewhale и обработчику PostHog. Без содержимого и IP. docs/TELEMETRY.md Отключить: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Отправка статистики использования включена по умолчанию. Передавать сводные счётчики версий, платформ, сеансов, функций и ошибок Codewhale и обработчику PostHog. Также считает провайдеров, модели из публичного каталога, суммы токенов и использование встроенных инструментов. Без содержимого и IP. docs/TELEMETRY.md Отключить: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Статистика использования Codewhale",
"TelemetryNoticeBody": "Codewhale считает: версию, операционную систему и семейство процессора,\nдлительность и результат сеанса, а также сводные счётчики функций и ошибок.\n\nОн никогда не собирает ваши разговоры, код, запросы, файлы, имена файлов,\nрепозиториев или веток, содержимое модели и учётные данные — и никогда не\nотправляет хронологию действий агента по отдельным ходам или инструментам.\n\nДля идентификации служит только случайный идентификатор на этом устройстве,\nкоторый заменяется каждые 90 дней. Передумать можно в любое время:\n codewhale config set telemetry false\n\nПолная схема по полям: docs/TELEMETRY.md\n\nОтправка статистики использования включена по умолчанию. Codewhale и PostHog обрабатывают эти счётчики, если отправка настроена. IP не собираются.",
"TelemetryNoticeCompactBody": "Счёт: версия, ОС/CPU, время/итог сеанса, функции/ошибки. Локальный случайный ID меняется каждые 90 дней. Никогда: чаты/код/запросы/файлы/имена, данные модели/учётные данные, ходы/инструменты. Схема: docs/TELEMETRY.md Выкл.: codewhale config set telemetry false\nОтправка статистики использования включена по умолчанию. Codewhale и PostHog обрабатывают эти счётчики, если отправка настроена. IP не собираются.",
"TelemetryNoticeBody": "Codewhale считает: версию, операционную систему и семейство процессора,\nдлительность и результат сеанса, а также сводные счётчики функций и ошибок.\n\nТакже считает провайдеров и модели из публичного каталога, которые вы используете, суммы их токенов и то, как часто запускается каждый встроенный инструмент.\n\nОн никогда не собирает ваши разговоры, код, запросы, файлы, имена файлов,\nрепозиториев или веток, содержимое модели и учётные данные — и никогда не\nотправляет хронологию действий агента по отдельным ходам или инструментам.\n\nДля идентификации служит только случайный идентификатор на этом устройстве,\nкоторый заменяется каждые 90 дней. Передумать можно в любое время:\n codewhale config set telemetry false\n\nПолная схема по полям: docs/TELEMETRY.md\n\nОтправка статистики использования включена по умолчанию. Codewhale и PostHog обрабатывают эти счётчики, если отправка настроена. IP не собираются.",
"TelemetryNoticeCompactBody": "Счёт: версия, ОС/CPU, время/итог сеанса, функции/ошибки. Также: провайдер/модель, токены, инструменты. Локальный случайный ID меняется каждые 90 дней. Никогда: чаты/код/запросы/файлы/имена, данные модели/учётные данные, ходы/инструменты. Схема: docs/TELEMETRY.md Выкл.: codewhale config set telemetry false\nОтправка статистики использования включена по умолчанию. Codewhale и PostHog обрабатывают эти счётчики, если отправка настроена. IP не собираются.",
"TelemetryNoticeChoiceKeep": "Да, оставить анонимный подсчёт",
"TelemetryNoticeChoiceDisable": "Нет, отключить отслеживание",
"TelemetryNoticeActionChoose": "выбрать",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "розгорнути",
"TranscriptThought": "Обмірковування завершено",
"TranscriptThoughtFor": "Обмірковування тривало {duration}",
"TelemetryNoticeDefaultOn": "Надсилання статистики використання ввімкнено за замовчуванням. Передавати зведені лічильники версій, платформ, сеансів, функцій і помилок Codewhale та обробнику PostHog. Без вмісту та IP. docs/TELEMETRY.md Вимкнути: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Надсилання статистики використання ввімкнено за замовчуванням. Передавати зведені лічильники версій, платформ, сеансів, функцій і помилок Codewhale та обробнику PostHog. Також рахує провайдерів, моделі з публічного каталогу, суми токенів і використання вбудованих інструментів. Без вмісту та IP. docs/TELEMETRY.md Вимкнути: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Статистика використання Codewhale",
"TelemetryNoticeBody": "Codewhale рахує: версію, операційну систему й сімейство процесора,\nтривалість і результат сеансу, а також зведені лічильники функцій і помилок.\n\nВін ніколи не збирає ваші розмови, код, запити, файли, назви файлів,\nсховищ або гілок, вміст моделі чи облікові дані — і ніколи не надсилає\nхронологію дій агента за окремими кроками або інструментами.\n\nДля розпізнавання використовується лише випадковий ідентифікатор на цьому\nпристрої, який замінюють кожні 90 днів. Передумати можна будь-коли:\n codewhale config set telemetry false\n\nПовна схема за полями: docs/TELEMETRY.md\n\nНадсилання статистики використання ввімкнено за замовчуванням. Codewhale і PostHog обробляють ці лічильники, якщо надсилання налаштовано. IP не збираються.",
"TelemetryNoticeCompactBody": "Лічба: версія, ОС/CPU, час/результат сеансу, функції/помилки. Випадковий локальний ID змінюється кожні 90 днів. Ніколи: чати/код/запити/файли/назви, дані моделі/облікові дані, кроки/інструменти. Схема: docs/TELEMETRY.md Вимк.: codewhale config set telemetry false\nНадсилання статистики використання ввімкнено за замовчуванням. Codewhale і PostHog обробляють ці лічильники, якщо надсилання налаштовано. IP не збираються.",
"TelemetryNoticeBody": "Codewhale рахує: версію, операційну систему й сімейство процесора,\nтривалість і результат сеансу, а також зведені лічильники функцій і помилок.\n\nТакож рахує провайдерів і моделі з публічного каталогу, якими ви користуєтеся, суми їхніх токенів і те, як часто запускається кожен вбудований інструмент.\n\nВін ніколи не збирає ваші розмови, код, запити, файли, назви файлів,\nсховищ або гілок, вміст моделі чи облікові дані — і ніколи не надсилає\nхронологію дій агента за окремими кроками або інструментами.\n\nДля розпізнавання використовується лише випадковий ідентифікатор на цьому\nпристрої, який замінюють кожні 90 днів. Передумати можна будь-коли:\n codewhale config set telemetry false\n\nПовна схема за полями: docs/TELEMETRY.md\n\nНадсилання статистики використання ввімкнено за замовчуванням. Codewhale і PostHog обробляють ці лічильники, якщо надсилання налаштовано. IP не збираються.",
"TelemetryNoticeCompactBody": "Лічба: версія, ОС/CPU, час/результат сеансу, функції/помилки. Також: провайдер/модель, токени, інструменти. Випадковий локальний ID змінюється кожні 90 днів. Ніколи: чати/код/запити/файли/назви, дані моделі/облікові дані, кроки/інструменти. Схема: docs/TELEMETRY.md Вимк.: codewhale config set telemetry false\nНадсилання статистики використання ввімкнено за замовчуванням. Codewhale і PostHog обробляють ці лічильники, якщо надсилання налаштовано. IP не збираються.",
"TelemetryNoticeChoiceKeep": "Так, лишити анонімний підрахунок",
"TelemetryNoticeChoiceDisable": "Ні, вимкнути відстеження",
"TelemetryNoticeActionChoose": "вибрати",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "mở rộng",
"TranscriptThought": "Đã suy nghĩ xong",
"TranscriptThoughtFor": "Đã suy nghĩ trong {duration}",
"TelemetryNoticeDefaultOn": "Báo cáo sử dụng được bật theo mặc định. Chia sẻ tổng số phiên bản, nền tảng, phiên, tính năng và lỗi với Codewhale và bên xử lý PostHog. Không thu thập nội dung hay IP. docs/TELEMETRY.md Tắt: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Báo cáo sử dụng được bật theo mặc định. Chia sẻ tổng số phiên bản, nền tảng, phiên, tính năng và lỗi với Codewhale và bên xử lý PostHog. Cũng đếm nhà cung cấp, mô hình trong danh mục công khai, tổng token và mức dùng công cụ tích hợp. Không thu thập nội dung hay IP. docs/TELEMETRY.md Tắt: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Báo cáo sử dụng Codewhale",
"TelemetryNoticeBody": "Codewhale đếm: phiên bản bạn chạy, hệ điều hành và họ CPU, thời lượng và kết\nquả phiên, cùng số liệu tổng hợp về tính năng và lỗi.\n\nHệ thống không bao giờ thu thập cuộc trò chuyện, mã nguồn, prompt, tệp, tên\ntệp, kho mã hoặc nhánh, nội dung mô hình hay thông tin đăng nhập của bạn — và\nkhông bao giờ gửi dòng thời gian hoạt động của tác nhân theo lượt hoặc công cụ.\n\nBạn chỉ được nhận diện bằng một ID ngẫu nhiên lưu trên máy này, được thay\nmới sau mỗi 90 ngày. Đổi ý bất cứ lúc nào:\n codewhale config set telemetry false\n\nLược đồ đầy đủ theo từng trường: docs/TELEMETRY.md\n\nBáo cáo sử dụng được bật theo mặc định. Codewhale và PostHog xử lý các số liệu này khi việc gửi được cấu hình. Không thu thập IP.",
"TelemetryNoticeCompactBody": "Số liệu: phiên bản, hệ thống/CPU, thời lượng/kết quả và tổng tính năng/lỗi.\nID cục bộ ngẫu nhiên đổi mỗi 90 ngày.\nKhông bao giờ: trò chuyện/mã/prompt/tệp/tên, nội dung mô hình/thông tin đăng nhập, lượt/công cụ.\nLược đồ: docs/TELEMETRY.md\nTắt: codewhale config set telemetry false\nBáo cáo sử dụng được bật theo mặc định. Codewhale và PostHog xử lý các số liệu này khi việc gửi được cấu hình. Không thu thập IP.",
"TelemetryNoticeBody": "Codewhale đếm: phiên bản bạn chạy, hệ điều hành và họ CPU, thời lượng và kết\nquả phiên, cùng số liệu tổng hợp về tính năng và lỗi.\n\nCũng đếm các nhà cung cấp và mô hình trong danh mục công khai bạn dùng, tổng token của chúng và số lần mỗi công cụ tích hợp chạy.\n\nHệ thống không bao giờ thu thập cuộc trò chuyện, mã nguồn, prompt, tệp, tên\ntệp, kho mã hoặc nhánh, nội dung mô hình hay thông tin đăng nhập của bạn — và\nkhông bao giờ gửi dòng thời gian hoạt động của tác nhân theo lượt hoặc công cụ.\n\nBạn chỉ được nhận diện bằng một ID ngẫu nhiên lưu trên máy này, được thay\nmới sau mỗi 90 ngày. Đổi ý bất cứ lúc nào:\n codewhale config set telemetry false\n\nLược đồ đầy đủ theo từng trường: docs/TELEMETRY.md\n\nBáo cáo sử dụng được bật theo mặc định. Codewhale và PostHog xử lý các số liệu này khi việc gửi được cấu hình. Không thu thập IP.",
"TelemetryNoticeCompactBody": "Số liệu: phiên bản, hệ thống/CPU, thời lượng/kết quả và tổng tính năng/lỗi.\nThêm: nhà cung cấp/mô hình, token, công cụ.\nID cục bộ ngẫu nhiên đổi mỗi 90 ngày.\nKhông bao giờ: trò chuyện/mã/prompt/tệp/tên, nội dung mô hình/thông tin đăng nhập, lượt/công cụ.\nLược đồ: docs/TELEMETRY.md\nTắt: codewhale config set telemetry false\nBáo cáo sử dụng được bật theo mặc định. Codewhale và PostHog xử lý các số liệu này khi việc gửi được cấu hình. Không thu thập IP.",
"TelemetryNoticeChoiceKeep": "Có, tiếp tục đếm ẩn danh",
"TelemetryNoticeChoiceDisable": "Không, tắt theo dõi",
"TelemetryNoticeActionChoose": "chọn",
+3 -3
View File
@@ -225,10 +225,10 @@
"TranscriptReasoningExpand": "展开",
"TranscriptThought": "思考完毕",
"TranscriptThoughtFor": "思考用时 {duration}",
"TelemetryNoticeDefaultOn": "使用统计默认开启。 向 Codewhale 和数据处理方 PostHog 分享版本/平台、会话、功能和错误的汇总计数。不收集内容或 IP。docs/TELEMETRY.md 关闭: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "使用统计默认开启。 向 Codewhale 和数据处理方 PostHog 分享版本/平台、会话、功能和错误的汇总计数。还统计服务商、公开目录中的模型、Token 总数和内置工具的使用次数。不收集内容或 IP。docs/TELEMETRY.md 关闭: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale 使用统计",
"TelemetryNoticeBody": "Codewhale 只统计:您运行的版本、操作系统和 CPU 系列、会话时长和结果,\n以及汇总的功能和错误计数。\n\n它绝不会收集您的对话、代码、提示词、文件、仓库名或分支名、模型内容\n或凭据,也绝不会发送按回合或按工具记录的智能体活动。\n\n系统只使用存储在本机上的随机 ID 来识别您,该 ID 每 90 天更换一次。\n您可以随时更改:\n codewhale config set telemetry false\n\n逐字段完整架构:docs/TELEMETRY.md\n\n使用统计默认开启。 配置发送后,Codewhale 和 PostHog 会处理这些计数。不收集 IP。",
"TelemetryNoticeCompactBody": "统计:版本、系统/处理器、会话时长/结果、功能/错误总数。\n随机本地标识每 90 天更换。\n绝不收集:对话/代码/提示词/文件/名称、模型内容/凭据、按回合/工具记录的活动。\n架构:docs/TELEMETRY.md\n关闭:codewhale config set telemetry false\n使用统计默认开启。 配置发送后,Codewhale 和 PostHog 会处理这些计数。不收集 IP。",
"TelemetryNoticeBody": "Codewhale 只统计:您运行的版本、操作系统和 CPU 系列、会话时长和结果,\n以及汇总的功能和错误计数。\n\n还统计你使用的服务商和公开目录中的模型、其 Token 总数,以及每个内置工具的运行次数。\n\n它绝不会收集您的对话、代码、提示词、文件、仓库名或分支名、模型内容\n或凭据,也绝不会发送按回合或按工具记录的智能体活动。\n\n系统只使用存储在本机上的随机 ID 来识别您,该 ID 每 90 天更换一次。\n您可以随时更改:\n codewhale config set telemetry false\n\n逐字段完整架构:docs/TELEMETRY.md\n\n使用统计默认开启。 配置发送后,Codewhale 和 PostHog 会处理这些计数。不收集 IP。",
"TelemetryNoticeCompactBody": "统计:版本、系统/处理器、会话时长/结果、功能/错误总数。\n另计:服务商/模型、Token、工具使用。\n随机本地标识每 90 天更换。\n绝不收集:对话/代码/提示词/文件/名称、模型内容/凭据、按回合/工具记录的活动。\n架构:docs/TELEMETRY.md\n关闭:codewhale config set telemetry false\n使用统计默认开启。 配置发送后,Codewhale 和 PostHog 会处理这些计数。不收集 IP。",
"TelemetryNoticeChoiceKeep": "是,保留匿名统计",
"TelemetryNoticeChoiceDisable": "不,关闭匿名统计",
"TelemetryNoticeActionChoose": "选择",
+3 -3
View File
@@ -223,10 +223,10 @@
"HistoryHintRestore": "Esc 還原",
"HistoryNoMatches": " 無相符",
"TranscriptReasoningExpand": "展開",
"TelemetryNoticeDefaultOn": "使用統計預設開啟。 向 Codewhale 和資料處理方 PostHog 分享版本/平台、工作階段、功能和錯誤的彙總計數。不收集內容或 IP。docs/TELEMETRY.md 關閉: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "使用統計預設開啟。 向 Codewhale 和資料處理方 PostHog 分享版本/平台、工作階段、功能和錯誤的彙總計數。也統計服務商、公開目錄中的模型、Token 總數和內建工具的使用次數。不收集內容或 IP。docs/TELEMETRY.md 關閉: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale 使用統計",
"TelemetryNoticeBody": "Codewhale 只統計:您執行的版本、作業系統和 CPU 系列、工作階段時間和結果,\n以及彙總的功能和錯誤計數。\n\n它絕不會收集您的對話、程式碼、提示詞、檔案、儲存庫名稱或分支名稱、\n模型內容或憑證,也絕不會傳送逐回合或逐工具記錄的代理活動。\n\n系統只使用儲存在此機器上的隨機 ID 來識別您,該 ID 每 90 天更換一次。\n您可以隨時更改:\n codewhale config set telemetry false\n\n逐欄位完整結構:docs/TELEMETRY.md\n\n使用統計預設開啟。 設定傳送後,Codewhale 和 PostHog 會處理這些計數。不收集 IP。",
"TelemetryNoticeCompactBody": "統計:版本、系統/處理器、工作階段時間/結果、功能/錯誤總數。\n隨機本機識別碼每 90 天更換。\n絕不收集:對話/程式碼/提示詞/檔案/名稱、模型內容/憑證、逐回合/工具記錄的活動。\n結構:docs/TELEMETRY.md\n關閉:codewhale config set telemetry false\n使用統計預設開啟。 設定傳送後,Codewhale 和 PostHog 會處理這些計數。不收集 IP。",
"TelemetryNoticeBody": "Codewhale 只統計:您執行的版本、作業系統和 CPU 系列、工作階段時間和結果,\n以及彙總的功能和錯誤計數。\n\n也統計你使用的服務商和公開目錄中的模型、其 Token 總數,以及每個內建工具的執行次數。\n\n它絕不會收集您的對話、程式碼、提示詞、檔案、儲存庫名稱或分支名稱、\n模型內容或憑證,也絕不會傳送逐回合或逐工具記錄的代理活動。\n\n系統只使用儲存在此機器上的隨機 ID 來識別您,該 ID 每 90 天更換一次。\n您可以隨時更改:\n codewhale config set telemetry false\n\n逐欄位完整結構:docs/TELEMETRY.md\n\n使用統計預設開啟。 設定傳送後,Codewhale 和 PostHog 會處理這些計數。不收集 IP。",
"TelemetryNoticeCompactBody": "統計:版本、系統/處理器、工作階段時間/結果、功能/錯誤總數。\n另計:服務商/模型、Token、工具使用。\n隨機本機識別碼每 90 天更換。\n絕不收集:對話/程式碼/提示詞/檔案/名稱、模型內容/憑證、逐回合/工具記錄的活動。\n結構:docs/TELEMETRY.md\n關閉:codewhale config set telemetry false\n使用統計預設開啟。 設定傳送後,Codewhale 和 PostHog 會處理這些計數。不收集 IP。",
"TelemetryNoticeChoiceKeep": "是,保留匿名使用統計",
"TelemetryNoticeChoiceDisable": "不,關閉匿名統計",
"TelemetryNoticeActionChoose": "選擇",
+134 -1
View File
@@ -10,7 +10,7 @@
use std::sync::Mutex;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::event::{Counters, Errors, TurnWall};
use crate::event::{Counters, Errors, Event, ModelCounters, TurnWall};
/// One session's accumulated counts.
#[derive(Debug, Default)]
@@ -38,6 +38,44 @@ pub struct SessionCounters {
/// only: a `String` here would be the seam through which a customer's
/// `[providers.<name>]` table key reaches the wire.
providers: Mutex<Vec<&'static str>>,
models: Mutex<Vec<ModelSlot>>,
tools: Mutex<Vec<(&'static str, u32, u32)>>,
}
#[derive(Debug)]
struct ModelSlot {
provider: &'static str,
model: String,
counters: ModelCounters,
turn_wall: TurnWall,
}
/// At most this many distinct provider and model pairs per session; later
/// pairs are folded into the provider's `other` row.
const MAX_MODEL_ROWS: usize = 16;
/// How one model turn ended.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TurnOutcome {
/// Completed.
Ok,
/// Failed.
Failed,
/// Interrupted by the user or the runtime.
Interrupted,
}
/// Token counts for one model turn.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TurnTokens {
/// Input tokens, including cache reads.
pub input: u64,
/// Output tokens.
pub output: u64,
/// Input tokens read from the prompt cache.
pub cache_read: u64,
/// Input tokens written to the prompt cache.
pub cache_write: u64,
}
/// Which counter to bump. A closed enum so a call site cannot invent a key.
@@ -117,6 +155,101 @@ impl SessionCounters {
}
}
/// Record one model turn: its provider, model, outcome, tokens and wall time.
pub fn record_model_turn(
&self,
provider: codewhale_config::ProviderKind,
model: &str,
outcome: TurnOutcome,
tokens: TurnTokens,
secs: u64,
) {
let provider = provider.as_str();
let mut model = crate::event::reported_model_id(model);
let Ok(mut models) = self.models.lock() else {
return;
};
let known = models
.iter()
.any(|slot| slot.provider == provider && slot.model == model);
if !known && models.len() >= MAX_MODEL_ROWS {
model = "other".to_string();
}
let index = match models
.iter()
.position(|slot| slot.provider == provider && slot.model == model)
{
Some(index) => index,
None => {
models.push(ModelSlot {
provider,
model,
counters: ModelCounters::default(),
turn_wall: TurnWall::default(),
});
models.len() - 1
}
};
let slot = &mut models[index];
let counters = &mut slot.counters;
match outcome {
TurnOutcome::Ok => counters.turns_ok = counters.turns_ok.saturating_add(1),
TurnOutcome::Failed => counters.turns_failed = counters.turns_failed.saturating_add(1),
TurnOutcome::Interrupted => {
counters.turns_interrupted = counters.turns_interrupted.saturating_add(1);
}
}
counters.input_tokens = counters.input_tokens.saturating_add(tokens.input);
counters.output_tokens = counters.output_tokens.saturating_add(tokens.output);
counters.cache_read_tokens = counters.cache_read_tokens.saturating_add(tokens.cache_read);
counters.cache_write_tokens = counters
.cache_write_tokens
.saturating_add(tokens.cache_write);
slot.turn_wall.observe_secs(secs);
}
/// Record one tool call by name. `is_mcp` marks an MCP pool tool.
pub fn record_tool_call(&self, name: &str, is_mcp: bool, failed: bool) {
let tool = crate::event::reported_tool_name(name, is_mcp);
let Ok(mut tools) = self.tools.lock() else {
return;
};
let index = match tools.iter().position(|(known, _, _)| *known == tool) {
Some(index) => index,
None => {
tools.push((tool, 0, 0));
tools.len() - 1
}
};
let row = &mut tools[index];
row.1 = row.1.saturating_add(1);
if failed {
row.2 = row.2.saturating_add(1);
}
}
/// The `model_usage` and `tool_usage` events for this session so far.
#[must_use]
pub fn usage_events(&self) -> Vec<Event> {
let mut events = Vec::new();
if let Ok(models) = self.models.lock() {
events.extend(models.iter().map(|slot| Event::ModelUsage {
provider: slot.provider.to_string(),
model: slot.model.clone(),
counters: slot.counters,
turn_wall: slot.turn_wall,
}));
}
if let Ok(tools) = self.tools.lock() {
events.extend(tools.iter().map(|(tool, calls, errors)| Event::ToolUsage {
tool: (*tool).to_string(),
calls: *calls,
errors: *errors,
}));
}
events
}
/// Snapshot the feature counters.
#[must_use]
pub fn counters(&self) -> Counters {
+193 -5
View File
@@ -1,9 +1,9 @@
//! The wire schema. This module is the whole of what may leave the machine.
//!
//! Every field below is an integer, a boolean, or a **closed enum string**,
//! except exactly three bounded strings: `app_version`, `git_sha`, and
//! `panic_site`. Each of those three has a written rule and a test pinning the
//! rule. There is no free-form string type here and no open-keyed map, which is
//! except exactly four bounded strings: `app_version`, `git_sha`,
//! `panic_site`, and `model_usage.model`, which is closed against the bundled
//! model catalog. Each of those four has a written rule. There is no free-form string type here and no open-keyed map, which is
//! what makes "aggregates and events, never content" a property the compiler
//! and the test suite can enforce rather than a promise.
//!
@@ -33,10 +33,10 @@ use serde::{Deserialize, Serialize};
/// Wire schema version. Bumped on any field add, remove, or retype; never
/// reused. `crates/telemetry/tests/golden/v1.json` pins what v1 was.
pub const SCHEMA_VERSION: u32 = 3;
pub const SCHEMA_VERSION: u32 = 4;
/// Default-on policy disclosure version. This is not human consent.
pub const NOTICE_VERSION: u32 = 5;
pub const NOTICE_VERSION: u32 = 6;
/// Which product surface produced a batch.
///
@@ -274,6 +274,9 @@ pub enum SessionSource {
Api,
/// Not stated.
Unknown,
/// Started under CI (`CI` or `GITHUB_ACTIONS` set), including the GitHub
/// Action, so automation is never counted as a person.
Ci,
}
impl SessionSource {
@@ -284,6 +287,7 @@ impl SessionSource {
Self::Fork,
Self::Api,
Self::Unknown,
Self::Ci,
];
/// The wire spelling.
@@ -295,6 +299,7 @@ impl SessionSource {
Self::Fork => "fork",
Self::Api => "api",
Self::Unknown => "unknown",
Self::Ci => "ci",
}
}
}
@@ -681,6 +686,183 @@ pub enum Event {
/// Closed counts; never a URL, user identity, or work content.
counters: ProductCounters,
},
/// One session's model turns and token counts for one provider and model.
ModelUsage {
/// A known provider id ([`is_known_provider_id`]).
provider: String,
/// A bundled-catalog model id, or `other` ([`is_reported_model_id`]).
model: String,
/// Turn outcomes and token totals.
counters: ModelCounters,
/// Turn wall-clock buckets for these turns.
turn_wall: TurnWall,
},
/// One session's calls of one tool.
ToolUsage {
/// A first-party tool name, `mcp` or `other` ([`is_reported_tool_name`]).
tool: String,
/// Calls made.
calls: u32,
/// Calls that returned an error.
errors: u32,
},
}
/// Turn outcomes and token totals for one provider and model in a session.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModelCounters {
/// Turns that completed.
pub turns_ok: u32,
/// Turns that failed.
pub turns_failed: u32,
/// Turns the user or the runtime interrupted.
pub turns_interrupted: u32,
/// Input tokens, including cache reads.
pub input_tokens: u64,
/// Output tokens, including reasoning.
pub output_tokens: u64,
/// Input tokens served from the provider's prompt cache.
pub cache_read_tokens: u64,
/// Input tokens written to the provider's prompt cache.
pub cache_write_tokens: u64,
}
impl ModelCounters {
/// Wire keys, in order.
pub const FIELDS: &'static [&'static str] = &[
"turns_ok",
"turns_failed",
"turns_interrupted",
"input_tokens",
"output_tokens",
"cache_read_tokens",
"cache_write_tokens",
];
}
/// First-party tool names reported by `tool_usage`. Any other name is
/// reported as `mcp` or `other`, so a plugin's or server's tool name never
/// leaves the machine.
pub const TOOL_NAMES: &[&str] = &[
"agent",
"apply_patch",
"authenticate",
"bash",
"code_execution",
"create_goal",
"diagnostics",
"edit",
"edit_file",
"exec_shell",
"execute_tools",
"fetch_url",
"file_search",
"fim_edit",
"finance",
"get_goal",
"git_blame",
"git_commit_plan",
"git_diff",
"git_fetch",
"git_log",
"git_merge_tree",
"git_show",
"git_status",
"grep_files",
"handle_read",
"image_ocr",
"js_execution",
"list_dir",
"load_skill",
"lsp",
"memory_get",
"memory_search",
"note",
"notify",
"pandoc_convert",
"project_map",
"read",
"read_file",
"read_media",
"remember",
"request_plugin_install",
"request_user_input",
"retrieve_tool_result",
"revert_turn",
"review",
"rlm",
"run_tests",
"run_verifiers",
"session_get",
"session_search",
"start_mcp_server",
"task_create",
"task_shell_wait",
"todo_write",
"tool_search",
"update_goal",
"update_plan",
"validate_data",
"verify",
"wait_for_dev_server",
"web_search",
"workflow",
"write",
"write_file",
];
/// The `tool` value for a tool name: itself when first-party, `mcp` for an
/// MCP pool tool, otherwise `other`.
#[must_use]
pub fn reported_tool_name(name: &str, is_mcp: bool) -> &'static str {
match TOOL_NAMES.iter().find(|known| **known == name) {
Some(known) => known,
None if is_mcp => "mcp",
None => "other",
}
}
/// Whether `value` may appear as `tool_usage.tool`.
#[must_use]
pub fn is_reported_tool_name(value: &str) -> bool {
value == "mcp" || value == "other" || TOOL_NAMES.contains(&value)
}
/// The `model` value for a model id: the lowercased id when the bundled model
/// catalog lists it, otherwise `other`. Custom, local and fine-tuned model
/// names therefore never leave the machine.
#[must_use]
pub fn reported_model_id(model: &str) -> String {
let id = model.trim().to_ascii_lowercase();
if is_catalogued_model(&id) {
id
} else {
"other".to_string()
}
}
/// Whether `value` may appear as `model_usage.model`.
#[must_use]
pub fn is_reported_model_id(value: &str) -> bool {
value == "other" || (value == value.to_ascii_lowercase() && is_catalogued_model(value))
}
fn is_catalogued_model(id: &str) -> bool {
if id.is_empty() || id.len() > 128 {
return false;
}
let catalog = codewhale_config::catalog::bundled_models_dev_catalog();
catalog.reviewed.intrinsic.contains_key(id)
|| catalog
.models
.keys()
.any(|key| key.eq_ignore_ascii_case(id))
|| catalog.providers.values().any(|provider| {
provider
.models
.keys()
.any(|key| key.eq_ignore_ascii_case(id))
})
}
impl Event {
@@ -693,6 +875,8 @@ impl Event {
Self::SessionEnd { .. } => "session_end",
Self::Panic { .. } => "panic",
Self::ProductUsage { .. } => "product_usage",
Self::ModelUsage { .. } => "model_usage",
Self::ToolUsage { .. } => "tool_usage",
Self::OperationsSummary { .. } => "operations_summary",
}
}
@@ -727,6 +911,10 @@ impl Event {
providers.iter().all(|name| is_known_provider_id(name))
}
Self::Panic { site } => is_reduced_panic_site(site),
Self::ModelUsage {
provider, model, ..
} => is_known_provider_id(provider) && is_reported_model_id(model),
Self::ToolUsage { tool, .. } => is_reported_tool_name(tool),
}
}
}
+3 -2
View File
@@ -50,7 +50,7 @@ use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Duration;
pub use actor::{BATCH_MAX_BYTES, BATCH_MAX_EVENTS, FlushOutcome};
pub use counters::{Counter, ErrorCounter, SessionCounters};
pub use counters::{Counter, ErrorCounter, SessionCounters, TurnOutcome, TurnTokens};
pub use decision::{
EndpointError, TELEMETRY_DIR, TelemetryConsent, TelemetryDecision, decide, decide_in_home,
load_setup_state_for_decision, load_setup_state_for_decision_at, re_decide, validate_endpoint,
@@ -58,7 +58,8 @@ pub use decision::{
pub use envelope::reduce_panic_site;
pub use event::{
Arch, Batch, ColdStartBucket, Counters, DurationBucket, Errors, Event, ExitClass, InstallKind,
Libc, NOTICE_VERSION, Os, ProductCounters, SCHEMA_VERSION, SessionSource, Surface, TurnWall,
Libc, ModelCounters, NOTICE_VERSION, Os, ProductCounters, SCHEMA_VERSION, SessionSource,
Surface, TurnWall,
};
/// How long the shutdown flush may hold the process.
+4 -1
View File
@@ -24,6 +24,9 @@ pub const NOTICE_BODY: &str = "\
Codewhale counts: which version you run, OS and CPU family, session
duration and outcome, and aggregate feature and error counters.
It also counts the providers and public catalog models you use, their
token totals, and how often each built-in tool runs.
It never collects your conversations, code, prompts, files, repo or
branch names, model content, or credentials — and it never sends a
per-turn or per-tool timeline of agent activity.
@@ -38,7 +41,7 @@ Usage reporting is on by default. Codewhale and PostHog process these
counts when delivery is configured. No IP is collected.";
/// Concise disclosure for every armed runtime surface, including headless CLI.
pub const STARTUP_DISCLOSURE: &str = "Usage reporting is on by default: Codewhale and PostHog process aggregate version/platform, session, feature and error counts when delivery is configured. No content or IP. Turn off: codewhale config set telemetry false. Details: codewhale config telemetry";
pub const STARTUP_DISCLOSURE: &str = "Usage reporting is on by default: Codewhale and PostHog process aggregate version/platform, session, feature, error, model/token and built-in tool counts when delivery is configured. No content or IP. Turn off: codewhale config set telemetry false. Details: codewhale config telemetry";
/// Present the policy once per revision without recording human acceptance.
/// Failure to save the display marker only causes a later repeat disclosure.
+33 -7
View File
@@ -172,6 +172,30 @@ fn every_event() -> Vec<Event> {
probes: 2,
probes_failed: 0,
},
Event::ModelUsage {
provider: "deepseek".to_string(),
model: "other".to_string(),
counters: crate::event::ModelCounters {
turns_ok: 12,
turns_failed: 1,
turns_interrupted: 0,
input_tokens: 418_230,
output_tokens: 9_120,
cache_read_tokens: 380_001,
cache_write_tokens: 0,
},
turn_wall: TurnWall {
lt_5s: 3,
five_to_thirty: 8,
thirty_to_onetwenty: 2,
gte_120s: 0,
},
},
Event::ToolUsage {
tool: "read_file".to_string(),
calls: 41,
errors: 2,
},
]
}
@@ -279,6 +303,8 @@ fn every_payload_field_is_bounded() {
"install_id" => uuid::Uuid::parse_str(&value).is_ok(),
p if p.ends_with(".site") => is_reduced_panic_site(&value),
p if p.ends_with(".previous_version") => is_app_version(&value),
p if p.ends_with(".tool") => is_reported_tool_name(&value),
p if p.ends_with(".model") => is_reported_model_id(&value),
_ => enums.contains(&value),
};
assert!(ok, "unbounded string leaf at {path}: {value:?}");
@@ -1632,7 +1658,7 @@ fn no_public_api_accepts_a_bare_bool() {
// ------------------------------------------------- docs and code are welded --
const TELEMETRY_DOC: &str = include_str!("../../../docs/TELEMETRY.md");
const GOLDEN_V3: &str = include_str!("../tests/golden/v3.json");
const GOLDEN_V4: &str = include_str!("../tests/golden/v4.json");
/// Extract the fenced ```jsonc blocks from the schema doc, in order.
fn jsonc_blocks(doc: &str) -> Vec<String> {
@@ -1758,7 +1784,7 @@ fn event_field_names_match_documented_schema() {
let blocks = jsonc_blocks(TELEMETRY_DOC);
assert_eq!(
blocks.len(),
7,
9,
"expected one jsonc block for the envelope and one per event variant; \
a parse miss must fail rather than silently pass"
);
@@ -1838,9 +1864,9 @@ fn event_field_names_match_documented_schema() {
}
#[test]
fn golden_payload_v3() {
fn golden_payload_v4() {
assert_eq!(NOTICE_VERSION.to_string(), TELEMETRY_NOTICE_VERSION);
// `crates/telemetry/tests/golden/v3.json` is one fully-populated instance of
// `crates/telemetry/tests/golden/v4.json` is one fully-populated instance of
// the envelope and every event. Any field add, remove, or retype fails here
// until the developer re-blesses it under a bumped `SCHEMA_VERSION` — and it
// is also the artifact a future receiver author reads to know exactly what
@@ -1856,15 +1882,15 @@ fn golden_payload_v3() {
rendered.push('\n');
if std::env::var("CODEWHALE_BLESS_TELEMETRY_GOLDEN").is_ok() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden/v3.json");
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden/v4.json");
std::fs::create_dir_all(path.parent().expect("parent")).expect("create golden dir");
std::fs::write(&path, &rendered).expect("write golden");
return;
}
assert_eq!(
rendered, GOLDEN_V3,
"the v3 payload changed; bump SCHEMA_VERSION and re-bless the golden file"
rendered, GOLDEN_V4,
"the v4 payload changed; bump SCHEMA_VERSION and re-bless the golden file"
);
}
+117
View File
@@ -0,0 +1,117 @@
{
"schema_version": 4,
"notice_version": 6,
"sent_at": "2026-08-03T18:04:11Z",
"install_id": "3f2a9c1e-0000-4000-8000-000000000001",
"app_version": "0.9.4",
"git_sha": "abcdef012345",
"surface": "control-plane",
"os": "macos",
"arch": "aarch64",
"libc": "none",
"tty": true,
"events": [
{
"event": "install_or_upgrade",
"kind": "upgrade",
"previous_version": "0.9.3-rc.1"
},
{
"event": "session_start",
"source": "resume"
},
{
"event": "session_end",
"duration_bucket": "1m_10m",
"exit_class": "panic",
"cold_start_bucket": "250_1000",
"providers": [
"custom",
"deepseek"
],
"counters": {
"turns": 14,
"tool_calls": 61,
"fleet_dispatch": 0,
"workflow_run": 0,
"subagent_spawn": 2,
"mcp_server_connected": 0,
"memory_search": 0,
"approval_modal_shown": 0,
"approval_auto_allowed": 0,
"command_palette_open": 3
},
"errors": {
"auth_preflight_failed": 0,
"provider_http_4xx": 0,
"provider_http_5xx": 1,
"tool_denied_by_policy": 0,
"tool_timeout": 0,
"network_error": 0
},
"turn_wall": {
"lt_5s": 9,
"5_30s": 4,
"30_120s": 1,
"gte_120s": 0
}
},
{
"event": "panic",
"site": "crates/tui/src/tui/ui.rs:8801:17"
},
{
"event": "product_usage",
"counters": {
"page_view": 1,
"docs_view": 0,
"install_copy": 0,
"download": 0,
"signup": 0,
"login": 0,
"session_create": 0,
"session_resume": 0,
"turn_submit": 0,
"turn_complete": 0,
"settings_open": 0,
"integration_connect": 0,
"error_shown": 0
}
},
{
"event": "operations_summary",
"requests": 10,
"errors": 1,
"duration_ms_total": 1500,
"duration_ms_max": 300,
"probes": 2,
"probes_failed": 0
},
{
"event": "model_usage",
"provider": "deepseek",
"model": "other",
"counters": {
"turns_ok": 12,
"turns_failed": 1,
"turns_interrupted": 0,
"input_tokens": 418230,
"output_tokens": 9120,
"cache_read_tokens": 380001,
"cache_write_tokens": 0
},
"turn_wall": {
"lt_5s": 3,
"5_30s": 8,
"30_120s": 2,
"gte_120s": 0
}
},
{
"event": "tool_usage",
"tool": "read_file",
"calls": 41,
"errors": 2
}
]
}
+31
View File
@@ -7358,6 +7358,7 @@ impl Engine {
// Update session usage
self.session.total_usage.add(&turn.usage);
record_model_turn_telemetry(self.api_provider, &self.session.model, &turn, status);
if !self.is_acp_turn() {
self.record_goal_usage_for_turn(&turn.usage, turn.started_at);
}
@@ -11074,3 +11075,33 @@ use codewhale_tools::names::{REQUEST_USER_INPUT_NAME, is_tool_search_tool};
#[cfg(test)]
pub(crate) mod tests;
fn record_model_turn_telemetry(
provider: ProviderKind,
model: &str,
turn: &TurnContext,
status: TurnOutcomeStatus,
) {
let usage = &turn.parent_route_usage;
if usage.input_tokens == 0 && usage.output_tokens == 0 && status == TurnOutcomeStatus::Completed
{
return;
}
let outcome = match status {
TurnOutcomeStatus::Completed => codewhale_telemetry::TurnOutcome::Ok,
TurnOutcomeStatus::Failed => codewhale_telemetry::TurnOutcome::Failed,
TurnOutcomeStatus::Interrupted => codewhale_telemetry::TurnOutcome::Interrupted,
};
codewhale_telemetry::session_counters().record_model_turn(
provider,
model,
outcome,
codewhale_telemetry::TurnTokens {
input: u64::from(usage.input_tokens),
output: u64::from(usage.output_tokens),
cache_read: u64::from(usage.prompt_cache_hit_tokens.unwrap_or(0)),
cache_write: u64::from(usage.prompt_cache_write_tokens.unwrap_or(0)),
},
turn.started_at.elapsed().as_secs(),
);
}
@@ -855,6 +855,11 @@ impl Engine {
// counted here for the same reason — one site, not one per tool.
let telemetry = codewhale_telemetry::session_counters();
telemetry.bump(codewhale_telemetry::Counter::ToolCalls);
telemetry.record_tool_call(
&tool_name,
dispatch == "mcp",
!outcome.as_ref().is_ok_and(|result| result.result.success),
);
if tool_name == "memory_search" {
telemetry.bump(codewhale_telemetry::Counter::MemorySearch);
}
+21 -2
View File
@@ -904,6 +904,9 @@ fn telemetry_session_end() -> codewhale_telemetry::Event {
/// A no-op unless this process was armed.
fn record_signal_session_end() {
codewhale_telemetry::set_exit_class(codewhale_telemetry::ExitClass::Signal);
for event in codewhale_telemetry::session_counters().usage_events() {
codewhale_telemetry::record_blocking(event);
}
codewhale_telemetry::record_blocking(telemetry_session_end());
}
@@ -2320,6 +2323,19 @@ fn telemetry_session_source(command: Option<&Commands>) -> codewhale_telemetry::
}
}
fn telemetry_session_source_for_run(
command: Option<&Commands>,
) -> codewhale_telemetry::SessionSource {
let flag = |name: &str| {
std::env::var(name).is_ok_and(|value| !matches!(value.trim(), "" | "0" | "false"))
};
if flag("CI") || flag("GITHUB_ACTIONS") {
codewhale_telemetry::SessionSource::Ci
} else {
telemetry_session_source(command)
}
}
/// Read-only commands must not create telemetry state as a side effect.
// Sessions export only projects local records to an explicit output. Like
// listing, it must not initialize telemetry or interactive session state.
@@ -2387,7 +2403,7 @@ fn arm_telemetry(cli: &Cli, command: Option<&Commands>) {
arm_telemetry_with_setup(
cli.config.clone(),
telemetry_surface(command),
telemetry_session_source(command),
telemetry_session_source_for_run(command),
None,
);
}
@@ -2441,6 +2457,9 @@ async fn finish_telemetry(outcome: &Result<()>, surface: codewhale_telemetry::Su
{
codewhale_telemetry::set_exit_class(codewhale_telemetry::ExitClass::Error);
}
for event in codewhale_telemetry::session_counters().usage_events() {
codewhale_telemetry::record(event);
}
codewhale_telemetry::record(telemetry_session_end());
if surface == codewhale_telemetry::Surface::Cli {
let persistence = codewhale_telemetry::persist_local_blocking();
@@ -2497,7 +2516,7 @@ async fn run_async_main_inner(
let telemetry_notice_plan = if surface == codewhale_telemetry::Surface::Tui {
crate::telemetry_notice::plan_if_due(
cli.config.clone(),
telemetry_session_source(command.as_ref()),
telemetry_session_source_for_run(command.as_ref()),
)
} else {
crate::telemetry_notice::TelemetryNoticePlan::NotDue
+75 -8
View File
@@ -58,6 +58,7 @@ completely; only one of them erases anything.
```sh
codewhale config set telemetry false # opt out: stops collection and erases state
CODEWHALE_TELEMETRY=0 codewhale # kill switch: stops collection, erases nothing
DO_NOT_TRACK=1 codewhale # the same kill switch, by the common convention
codewhale --telemetry false # the same kill switch, for one command
```
@@ -161,14 +162,17 @@ most once per flush point and never grows a queue.
## Event schema
`SCHEMA_VERSION = 3`. `notice_version = 5` identifies the disclosed default-on
policy; it does not assert human consent or that a notice was seen. The closed
`SCHEMA_VERSION = 4`. `notice_version = 6` identifies the disclosed default-on
policy; it does not assert human consent or that a notice was seen. Schema 4
adds the `model_usage` and `tool_usage` events and the `ci` session source.
Schema-3 batches with `notice_version = 5` are still accepted without them, which
is what the web and desktop control-plane relay sends. The closed
v2 contract still accepts only `consent_version = 4`, retaining its original
explicit opt-in meaning. Fields cannot be mixed between versions. The unchanged
v1 contract remains first-party only and **never sent to PostHog**; it refuses
new fields, surfaces, and `product_usage` events.
Every field is an integer, a boolean, or a **closed enum string**, except exactly three bounded strings: `app_version`, `git_sha`, `panic_site`. Each of the three has a written rule and a test pinning the rule. **There is no free-form string type in this schema, and no open-keyed map.** That is the property that makes red line 3 enforceable rather than aspirational.
Every field is an integer, a boolean, or a **closed enum string**, except exactly four bounded strings: `app_version`, `git_sha`, `panic_site`, and `model_usage.model`, which is closed against the bundled model catalog. Each of the four has a written rule and a check on the drain path. **There is no free-form string type in this schema, and no open-keyed map.** That is the property that makes red line 3 enforceable rather than aspirational.
### Batch envelope — sent on every POST
@@ -283,7 +287,10 @@ Emitted once when `state.json`'s `last_version` differs from `app_version`.
{ "event": "session_start", "source": "interactive" }
```
`source` is `SessionSource` (`crates/state/src/lib.rs:34-41`) stringified by `session_source_to_str` (`:1909-1917`): `interactive | resume | fork | api | unknown`.
`source` is `SessionSource` (`crates/state/src/lib.rs:34-41`) stringified by `session_source_to_str` (`:1909-1917`): `interactive | resume | fork | api | unknown | ci`.
`ci` replaces the others whenever `CI` or `GITHUB_ACTIONS` is set to a true
value, including the Codewhale GitHub Action, so automated runs are never
counted as people. Only that boolean environment fact is read.
### Event: `session_end`
@@ -418,6 +425,65 @@ are aggregate latency values; `probes` and `probes_failed` count health probes.
}
```
### Event: `model_usage`
One event per provider and model a session used, sent with `session_end`.
It is how Codewhale decides which providers and models to support first, which
to make defaults, and whether prompt caching is working.
```jsonc
{
"event": "model_usage",
"provider": "deepseek",
"model": "deepseek-v4-flash",
"counters": {
"turns_ok": 12,
"turns_failed": 1,
"turns_interrupted": 0,
"input_tokens": 418230,
"output_tokens": 9120,
"cache_read_tokens": 380001,
"cache_write_tokens": 0
},
"turn_wall": { "lt_5s": 3, "5_30s": 8, "30_120s": 2, "gte_120s": 0 }
}
```
`provider` follows the `providers` rule of `session_end`. `model` is the
lowercased model id **only when it appears in the public model catalog bundled
with Codewhale**; any other name (custom, local, fine-tuned or private models,
and `[providers.<name>]` routes) is sent as `other`. Token counts are the
provider's own usage numbers for the main conversation; `input_tokens` includes
cache reads. Turn counts are by outcome, and `turn_wall` buckets each turn's
wall time as `session_end` does. A session reports at most 16 provider and model
pairs; later pairs are counted under `other`. No per-turn timeline is sent.
### Event: `tool_usage`
One event per tool a session called, sent with `session_end`. It is how
Codewhale decides which built-in tools to invest in, fix or retire.
```jsonc
{ "event": "tool_usage", "tool": "read_file", "calls": 41, "errors": 2 }
```
`tool` is a first-party tool name from this closed list, `mcp` for any MCP
server tool, or `other` for any other tool (plugins and configured tools):
`agent`, `apply_patch`, `authenticate`, `bash`, `code_execution`, `create_goal`,
`diagnostics`, `edit`, `edit_file`, `exec_shell`, `execute_tools`, `fetch_url`,
`file_search`, `fim_edit`, `finance`, `get_goal`, `git_blame`, `git_commit_plan`,
`git_diff`, `git_fetch`, `git_log`, `git_merge_tree`, `git_show`, `git_status`,
`grep_files`, `handle_read`, `image_ocr`, `js_execution`, `list_dir`,
`load_skill`, `lsp`, `memory_get`, `memory_search`, `note`, `notify`,
`pandoc_convert`, `project_map`, `read`, `read_file`, `read_media`, `remember`,
`request_plugin_install`, `request_user_input`, `retrieve_tool_result`,
`revert_turn`, `review`, `rlm`, `run_tests`, `run_verifiers`, `session_get`,
`session_search`, `start_mcp_server`, `task_create`, `task_shell_wait`,
`todo_write`, `tool_search`, `update_goal`, `update_plan`, `validate_data`,
`verify`, `wait_for_dev_server`, `web_search`, `workflow`, `write`, `write_file`.
`errors` counts calls that returned an error, never the error text or the
arguments.
### What the endpoint does — a shipping gate, not a footnote
This section was a gate on configuring any non-loopback endpoint. The endpoint is now configured by default, so this is a description of a service that exists rather than a promise about one that might.
@@ -438,7 +504,7 @@ Batches are **IP-stripped at ingest**. No IP is stored, logged, or joined to `in
**Retention: three months.** That is Cloudflare's fixed window for Analytics Engine and it is not configurable, so it is a ceiling rather than a policy — there is no setting that could make it longer.
**Optional PostHog processor.** After first-party storage, an explicitly
configured ingest may forward validated schema-v3 / notice-v5 batches and
configured ingest may forward validated schema-v4 / notice-v6 and schema-v3 / notice-v5 batches and
legacy schema-v2 / consent-v4 batches to
PostHog's [batch capture API](https://posthog.com/docs/api/capture). The only
permitted origins are `https://us.i.posthog.com` and `https://eu.i.posthog.com`.
@@ -446,8 +512,9 @@ Legacy v1 batches cannot reach that path. PostHog receives the same bounded
envelope and event fields, the batch timestamp as its event timestamp, and
`codewhale:<install_id>` as its anonymous `distinct_id`. Events are named
`codewhale_install_or_upgrade`, `codewhale_session_start`,
`codewhale_session_end`, `codewhale_panic`, `codewhale_product_usage`, or
`codewhale_operations_summary`.
`codewhale_session_end`, `codewhale_panic`, `codewhale_product_usage`,
`codewhale_operations_summary`, `codewhale_model_usage`, or
`codewhale_tool_usage`.
`$process_person_profile = false`, `$geoip_disable = true`, and `$ip = null`
are fixed transport controls. No request metadata, profile identification,
SDK, autocapture, session replay, advertising, or work content is forwarded.
@@ -492,7 +559,7 @@ It prints the daily series, a 7-day trend over complete UTC days, the freshness
### What is never collected — the public red-line list
Prompts; completions; tool arguments; diffs; patches; file contents; filenames; absolute or relative paths; git remotes; repo names; branch names; workspace commit SHAs; memory entries; chat history; API keys, tokens, cookies, or `Authorization` headers (including any boolean asserting a key exists); model ids of any kind; custom provider table names; MCP server names, commands, or URLs; approval rule text; error message bodies; panic message text; per-event timestamps; keystrokes; clipboard; screenshots; microphone; camera; location; and any third-party ad or analytics SDK — there are none in the runtime binary and none may be added.
Prompts; completions; tool arguments; diffs; patches; file contents; filenames; absolute or relative paths; git remotes; repo names; branch names; workspace commit SHAs; memory entries; chat history; API keys, tokens, cookies, or `Authorization` headers (including any boolean asserting a key exists); model ids that are not in Codewhale's bundled public model catalog (custom, local, fine-tuned or private model names are sent only as `other`); custom provider table names; MCP server names, commands, URLs, or tool names; plugin and configured tool names; approval rule text; error message bodies; panic message text; per-event timestamps; keystrokes; clipboard; screenshots; microphone; camera; location; and any third-party ad or analytics SDK — there are none in the runtime binary and none may be added.
Two named traps for the implementer. `crates/state/src/lib.rs` persists `git_sha`, `git_branch`, `git_origin_url`, `cwd`, and `path` on the threads table (`:93, :399, :653`): a payload builder that accepts a `Thread` or `ThreadMeta` and derives `Serialize` breaches the contract in one line. **Never derive `Serialize` over an existing state type** — build every telemetry struct from scratch with explicit fields. And `crates/core/src/lib.rs:1389-1398` is the one place in the tree where the word `telemetry` sits inside a JSON object next to `prompt`, `base_url`, and `has_api_key`. It is the object someone will copy. Do not.
+5 -3
View File
@@ -146,15 +146,17 @@ take the next free slot.
| `blob12` | `duration_bucket` (`session_end` only) |
| `blob13` | `exit_class` (`session_end` only) |
| `blob14` | `cold_start_bucket` (`session_end` only; `''` on surfaces that do not measure it) |
| `blob15` | `providers`, comma-joined, already sorted and deduplicated |
| `blob16` | `panic_site` (`panic` only) — a `crates/…` path or the literal `<dep>` |
| `blob15` | `providers`, comma-joined, already sorted and deduplicated; the one `provider` on `model_usage` |
| `blob16` | `panic_site` (`panic`) — a `crates/…` path or the literal `<dep>`; `model` (`model_usage`); `tool` (`tool_usage`) |
| `blob17` | `sent_at` — the *batch* timestamp. Events carry none. |
| `blob18` | `aggregate_counters` — closed JSON counts, for `product_usage` or `operations_summary` |
| `blob19` | `schema_version` |
| `blob20` | `privacy_version`: notice5 for v3, consent4 for v2, empty for v1; `blob19` keeps their meanings distinct |
| `blob20` | `privacy_version`: notice6 for v4, notice5 for v3, consent4 for v2, empty for v1; `blob19` keeps their meanings distinct |
| `double1..10` | `counters`: `turns`, `tool_calls`, `fleet_dispatch`, `workflow_run`, `subagent_spawn`, `mcp_server_connected`, `memory_search`, `approval_modal_shown`, `approval_auto_allowed`, `command_palette_open` |
| `double11..16` | `errors`: `auth_preflight_failed`, `provider_http_4xx`, `provider_http_5xx`, `tool_denied_by_policy`, `tool_timeout`, `network_error` |
| `double17..20` | `turn_wall`: `lt_5s`, `5_30s`, `30_120s`, `gte_120s` |
| `double1..11` on `model_usage` | `turns_ok`, `turns_failed`, `turns_interrupted`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`, then `turn_wall` `lt_5s`, `5_30s`, `30_120s`, `gte_120s` |
| `double1..2` on `tool_usage` | `calls`, `errors` |
Columns not relevant to an event are `''` / `0`. `tty` is a blob because the 20
doubles are exactly used by the three numeric structs — Analytics Engine's
+14 -1
View File
@@ -24,7 +24,7 @@
*/
import type { Batch, Event } from "./schema";
import { COUNTER_FIELDS, ERROR_FIELDS, TURN_WALL_FIELDS } from "./schema";
import { COUNTER_FIELDS, ERROR_FIELDS, MODEL_COUNTER_FIELDS, TURN_WALL_FIELDS } from "./schema";
/** The subset of `AnalyticsEngineDataset` this Worker uses. */
export interface DataPointSink {
@@ -134,6 +134,19 @@ function toDataPoint(batch: Batch, event: Event): DataPoint {
case "panic":
blobs[15] = event.site;
break;
case "model_usage":
blobs[14] = event.provider;
blobs[15] = event.model;
doubles = [
...MODEL_COUNTER_FIELDS.map((field) => event.counters[field]),
...TURN_WALL_FIELDS.map((field) => event.turn_wall[field]),
...EMPTY_DOUBLES.slice(MODEL_COUNTER_FIELDS.length + TURN_WALL_FIELDS.length),
];
break;
case "tool_usage":
blobs[15] = event.tool;
doubles = [event.calls, event.errors, ...EMPTY_DOUBLES.slice(2)];
break;
}
return {
+11 -2
View File
@@ -1,5 +1,12 @@
/** Optional processor for the existing validated ingest; no second collector. */
import { CONSENT_VERSION, NOTICE_VERSION, SCHEMA_VERSION, type Batch } from "./schema";
import {
CONSENT_VERSION,
NOTICE_VERSION,
SCHEMA_VERSION,
V3_NOTICE_VERSION,
V3_SCHEMA_VERSION,
type Batch,
} from "./schema";
export interface PostHogConfig {
/** Unset by default. Exactly one of the two regional HTTPS capture origins. */
@@ -16,7 +23,9 @@ const HOSTS = ["https://us.i.posthog.com", "https://eu.i.posthog.com"];
/** V2 keeps its opt-in contract; v3 identifies the disclosed opt-out policy. */
export async function deliverPostHog(batch: Batch, config: PostHogConfig): Promise<void> {
const explicitConsent = batch.schema_version === 2 && batch.consent_version === CONSENT_VERSION;
const disclosedPolicy = batch.schema_version === SCHEMA_VERSION && batch.notice_version === NOTICE_VERSION;
const disclosedPolicy =
(batch.schema_version === SCHEMA_VERSION && batch.notice_version === NOTICE_VERSION) ||
(batch.schema_version === V3_SCHEMA_VERSION && batch.notice_version === V3_NOTICE_VERSION);
if (
!(explicitConsent || disclosedPolicy) ||
batch.events.length === 0 ||
+108 -6
View File
@@ -17,10 +17,16 @@
*/
/** `SCHEMA_VERSION` in `crates/telemetry/src/event.rs`. */
export const SCHEMA_VERSION = 3;
export const SCHEMA_VERSION = 4;
/** Notice-v5 batches without `model_usage`, `tool_usage` or the `ci` source. */
export const V3_SCHEMA_VERSION = 3;
/** Disclosed default-on policy, not a record of human acceptance. */
export const NOTICE_VERSION = 5;
export const NOTICE_VERSION = 6;
/** The notice version schema v3 batches carry. */
export const V3_NOTICE_VERSION = 5;
/** Explicit consent to the PostHog processor disclosure. */
export const CONSENT_VERSION = 4;
@@ -128,6 +134,7 @@ export const SESSION_SOURCES = [
"fork",
"api",
"unknown",
"ci",
] as const;
/** `DurationBucket` wire spellings. */
@@ -184,6 +191,33 @@ export const OPERATIONS_FIELDS = [
"requests", "errors", "duration_ms_total", "duration_ms_max", "probes", "probes_failed",
] as const;
/** `ModelCounters::FIELDS`; turn counts are u32, token counts u64. */
export const MODEL_COUNTER_FIELDS = [
"turns_ok",
"turns_failed",
"turns_interrupted",
"input_tokens",
"output_tokens",
"cache_read_tokens",
"cache_write_tokens",
] as const;
/** `event::TOOL_NAMES` plus the `mcp` and `other` buckets. */
export const TOOL_NAMES = [
"agent", "apply_patch", "authenticate", "bash", "code_execution", "create_goal",
"diagnostics", "edit", "edit_file", "exec_shell", "execute_tools", "fetch_url",
"file_search", "fim_edit", "finance", "get_goal", "git_blame", "git_commit_plan",
"git_diff", "git_fetch", "git_log", "git_merge_tree", "git_show", "git_status",
"grep_files", "handle_read", "image_ocr", "js_execution", "list_dir", "load_skill",
"lsp", "memory_get", "memory_search", "note", "notify", "pandoc_convert",
"project_map", "read", "read_file", "read_media", "remember", "request_plugin_install",
"request_user_input", "retrieve_tool_result", "revert_turn", "review", "rlm",
"run_tests", "run_verifiers", "session_get", "session_search", "start_mcp_server",
"task_create", "task_shell_wait", "todo_write", "tool_search", "update_goal",
"update_plan", "validate_data", "verify", "wait_for_dev_server", "web_search",
"workflow", "write", "write_file", "mcp", "other",
] as const;
/** `Counters::FIELDS`, in declaration order. */
export const COUNTER_FIELDS = [
"turns",
@@ -237,6 +271,8 @@ export const EVENT_FIELDS: Readonly<Record<string, readonly string[]>> = {
],
panic: ["event", "site"],
product_usage: ["event", "counters"],
model_usage: ["event", "provider", "model", "counters", "turn_wall"],
tool_usage: ["event", "tool", "calls", "errors"],
operations_summary: ["event", ...OPERATIONS_FIELDS],
};
@@ -287,6 +323,13 @@ const PROVIDER_RE = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;
const U32_MAX = 4294967295;
/**
* `event::is_reported_model_id` shape: a lowercased bundled-catalog id or
* `other`. The client closes the value against its catalog; the server bounds
* the shape, as it does for providers.
*/
const MODEL_RE = /^[a-z0-9][a-z0-9._:/@+-]{0,127}$/;
/** Derived contract for CWC's product-only ingress; runtime events stay here. */
export const CWC_PRODUCT_SCHEMA = {
$schema: "http://json-schema.org/draft-07/schema#",
@@ -297,8 +340,8 @@ export const CWC_PRODUCT_SCHEMA = {
additionalProperties: false,
required: ENVELOPE_FIELDS,
properties: {
schema_version: { const: SCHEMA_VERSION },
notice_version: { const: NOTICE_VERSION },
schema_version: { const: V3_SCHEMA_VERSION },
notice_version: { const: V3_NOTICE_VERSION },
sent_at: { type: "string", pattern: SENT_AT_RE.source },
install_id: { type: "string", pattern: INSTALL_ID_RE.source },
app_version: { type: "string", maxLength: MAX_VERSION_LEN, pattern: VERSION_RE.source },
@@ -361,6 +404,14 @@ export type Event =
}
| { event: "panic"; site: string }
| { event: "product_usage"; counters: ProductCounters }
| {
event: "model_usage";
provider: string;
model: string;
counters: Record<(typeof MODEL_COUNTER_FIELDS)[number], number>;
turn_wall: TurnWall;
}
| { event: "tool_usage"; tool: string; calls: number; errors: number }
| ({ event: "operations_summary" } & Record<(typeof OPERATIONS_FIELDS)[number], number>);
export interface Batch {
@@ -415,6 +466,24 @@ function enumString(
return null;
}
function countMap(
value: unknown,
fields: readonly string[],
where: string,
max: number,
): string | null {
if (!isPlainObject(value)) return `${where}: not an object`;
const keyError = keysExactly(value, fields, where);
if (keyError) return keyError;
for (const field of fields) {
const item = value[field];
if (typeof item !== "number" || !Number.isInteger(item) || item < 0 || item > max) {
return `${where}.${field}: not a count`;
}
}
return null;
}
function u32Map(
value: unknown,
fields: readonly string[],
@@ -520,6 +589,23 @@ function validateEvent(value: unknown, where: string): string | null {
}
case "product_usage":
return u32Map(value.counters, PRODUCT_COUNTER_FIELDS, `${where}.counters`);
case "model_usage": {
if (typeof value.provider !== "string" || !PROVIDER_RE.test(value.provider)) {
return `${where}.provider: not a provider id`;
}
if (typeof value.model !== "string" || !MODEL_RE.test(value.model)) {
return `${where}.model: not a catalogued model id`;
}
return (
countMap(value.counters, MODEL_COUNTER_FIELDS, `${where}.counters`, Number.MAX_SAFE_INTEGER) ??
u32Map(value.turn_wall, TURN_WALL_FIELDS, `${where}.turn_wall`)
);
}
case "tool_usage":
return (
enumString(value.tool, TOOL_NAMES, `${where}.tool`) ??
countMap({ calls: value.calls, errors: value.errors }, ["calls", "errors"], where, U32_MAX)
);
case "panic": {
const site = value.site;
if (typeof site !== "string" || site.length > MAX_PANIC_SITE_LEN) {
@@ -547,7 +633,13 @@ export function validateBatch(value: unknown): Accepted | Rejection {
const legacy = value.schema_version === 1;
const explicitConsent = value.schema_version === 2;
if (!legacy && !explicitConsent && value.schema_version !== SCHEMA_VERSION) {
const preUsage = value.schema_version !== SCHEMA_VERSION;
if (
!legacy &&
!explicitConsent &&
value.schema_version !== SCHEMA_VERSION &&
value.schema_version !== V3_SCHEMA_VERSION
) {
return { ok: false, reason: "batch.schema_version: unsupported" };
}
const fields = legacy ? LEGACY_ENVELOPE_FIELDS : explicitConsent ? V2_ENVELOPE_FIELDS : ENVELOPE_FIELDS;
@@ -556,7 +648,11 @@ export function validateBatch(value: unknown): Accepted | Rejection {
if (explicitConsent && value.consent_version !== CONSENT_VERSION) {
return { ok: false, reason: "batch.consent_version: explicit current consent required" };
}
if (!legacy && !explicitConsent && value.notice_version !== NOTICE_VERSION) {
if (
!legacy &&
!explicitConsent &&
value.notice_version !== (preUsage ? V3_NOTICE_VERSION : NOTICE_VERSION)
) {
return { ok: false, reason: "batch.notice_version: current policy required" };
}
if (typeof value.sent_at !== "string" || !SENT_AT_RE.test(value.sent_at)) {
@@ -602,6 +698,12 @@ export function validateBatch(value: unknown): Accepted | Rejection {
if (legacy && (event.event === "product_usage" || event.event === "operations_summary")) {
return { ok: false, reason: "events: aggregate product/operations events require schema v2 or v3" };
}
if (preUsage && (event.event === "model_usage" || event.event === "tool_usage")) {
return { ok: false, reason: "events: model_usage and tool_usage require schema v4" };
}
if (preUsage && event.event === "session_start" && event.source === "ci") {
return { ok: false, reason: "events: the ci session source requires schema v4" };
}
if (event.event === "operations_summary" && value.surface !== "control-plane") {
return { ok: false, reason: "events: operations_summary requires control-plane surface" };
}