AI / ML 04 Wrzesień 2026

Notificaciones sonoras para el agente de Cursor (hooks)

También en:

Hace un tiempo describí unas reglas globales de Cursor para el idioma y la puntuación. Hoy algo de un estante completamente distinto, pero igual de "vagamente útil": quiero que el agente me avise con un sonido cuando termine de trabajar en una tarea o cuando me pregunte algo. Gracias a eso ya no tengo que mirar fijamente la ventana del chat y puedo dedicarme a otra cosa mientras tanto.

La solución se basa en los hooks de Cursor. Todo el código (el instalador y los scripts de los hooks) lo guardo en un único snippet para que esta entrada no se ahogue en listados:

➡️ Snippet con el conjunto completo de archivos: bitbucket.org/LordKhayyin/.../5qp5oj

Qué te ofrece

Tres señales sonoras distintas:

Cuándo Sonido
El agente terminó un turno (un final normal). complete.oga
El agente terminó un turno formulando una pregunta en texto (el mensaje termina con ?). window-question.oga
Una acción espera tu aprobación — un comando fuera del sandbox o una escritura de archivo fuera del proyecto. bell.oga

Todos los sonidos son sonidos del sistema estándar del conjunto freedesktop, así que no hay que descargar nada adicional.

Por qué hooks y no un "skill"

Esa fue mi primera idea — "hagamos un skill". Pero un skill es una instrucción que el agente lee mientras trabaja; no sirve para reaccionar a los eventos del ciclo de vida del agente. Para "haz algo cuando el agente terminó / cuando ejecuta un comando" se usan precisamente los hooks, que intercambian JSON por stdin/stdout y se disparan en eventos concretos.

Requisitos

  • Linux con un servidor de sonido en funcionamiento (PipeWire o PulseAudio).
  • Uno de los reproductores: paplay, pw-play o ffplay (el script elige el primero disponible).
  • jq (para analizar los payloads de los hooks).
  • El conjunto de sonidos sound-theme-freedesktop en /usr/share/sounds/freedesktop/stereo/.

En macOS/Windows la solución habría que adaptarla — otro reproductor (afplay / powershell) y otros archivos de sonido. La lógica de los hooks sigue siendo la misma.

Instalación

Los hooks de usuario funcionan globalmente, en todos los proyectos, y viven en ~/.cursor/hooks.json (el registro de eventos) y ~/.cursor/hooks/ (los scripts).

Lo más rápido — descarga install.sh del snippet y ejecútalo:

bash install.sh

El instalador crea ~/.cursor/hooks.json y ~/.cursor/hooks/*.sh, les da permiso de ejecución y hace una copia de seguridad del hooks.json existente. Cursor recarga la configuración automáticamente; si los hooks no aparecen, recarga la ventana (Command Palette → "Developer: Reload Window") o comprueba Settings → Hooks.

Cómo funciona

La solución consta de cinco scripts y un archivo hooks.json. El mecanismo de detección de preguntas es más interesante de lo que parece, porque me encontré con varias trampas por el camino.

Fin del trabajo y detección de la pregunta

No existe un evento "el agente hizo una pregunta". Sí existe, en cambio, el evento stop (fin del turno). El problema: el payload de stop no contiene el texto del mensaje, y leer la transcripción no es fiable, porque el "pensamiento" interno del modelo a veces se guarda allí como texto normal y se mezcla con el mensaje visible.

La solución resultó ser el evento afterAgentResponse, cuyo payload tiene un campo text limpio — solo el mensaje visible, sin el razonamiento. Por eso el script record-response.sh (en afterAgentResponse) guarda el texto del último mensaje en un archivo de estado, y notify-stop.sh (en stop) lo lee, comprueba si termina con ? y elige el sonido "pregunta" o "listo". Separarlo así resuelve de forma robusta el retraso de la transcripción y el problema del pensamiento-como-texto.

Sonido en una solicitud de aprobación (comandos)

El evento beforeShellExecution tiene en su payload el campo sandbox. Cuando es false, el comando se ejecuta fuera del sandbox y Cursor pide aprobación — entonces approval-notify.sh reproduce bell. Cuando es true, el comando va al sandbox sin preguntar y hay silencio.

Cuidado con la trampa de jq: el operador // trata el valor false igual que null, así que .sandbox // "true" para sandbox:false devolvería "true" e invertiría la lógica. Hay que leer el valor en bruto con jq -r '.sandbox'.

Sonido en una solicitud de aprobación (escrituras de archivos)

Las herramientas de archivos no tienen una bandera de aprobación, pero pasan por preToolUse. En la práctica, Cursor detiene para aprobación las escrituras fuera de los directorios del proyecto. Por eso file-approval-notify.sh compara la ruta de destino con los directorios del workspace y reproduce bell cuando una escritura apunta fuera del proyecto. Este evento se dispara antes de la ventana de aprobación, así que el sonido llega en el momento adecuado.

Limitaciones (compromisos conscientes)

  • El cuadro de pregunta con opciones (AskQuestion) no da sonido. Esa herramienta interactiva no emite ningún hook, y stop solo llega después de tu respuesta. Por eso detecto las preguntas por el texto que termina con ?, no por el cuadro.
  • El sonido de aprobación puede ser excesivo. bell suena en cada comando fuera del sandbox y en cada escritura fuera del proyecto — también cuando la ruta o el comando en cuestión está en tu allowlist y se ejecuta sin preguntar. Los payloads no llevan un campo que distinga "auto-aprobado" de "una solicitud de aprobación real".
  • Omito las lecturas de archivos. Las lecturas normalmente no detienen la acción, así que reproducir en ellas sería solo ruido.

Personalización

  • Otros sonidos — cambia el nombre en el argumento de la llamada a play-sound.sh (p. ej. bell → dialog-warning). Encontrarás los nombres disponibles en /usr/share/sounds/freedesktop/stereo/.
  • Desactivar el sonido de aprobación para comandos — elimina la sección beforeShellExecution de hooks.json.
  • Desactivar el sonido de aprobación para archivos — elimina la sección preToolUse.

Resumen

Unos pocos scripts pequeños y un archivo de configuración hacen que el agente deje de trabajar "en silencio": oigo cuando terminó, cuando pregunta algo y cuando espera mi aprobación. El resto de mi atención queda libre para otras cosas. El conjunto completo de archivos está en el snippet de Bitbucket.