add-reactions
Add WhatsApp emoji reaction support — receive, send, store, and search reactions.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Add WhatsApp emoji reaction support — receive, send, store, and search reactions.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
Redactar escritos jurídicos mexicanos largos (recursos de apelación, demandas, contestaciones, amparos) a partir de varios documentos de un expediente (demanda, contestación, pericial, confesional, sentencia), entregados como .docx editable. Úsalo cuando el usuario pida "recurso de apelación", "redacta el escrito", "haz la demanda/contestación en Word", o cualquier pieza procesal larga que deba correlacionar agravios/hechos con documentos fuente y jurisprudencia verificada.
Sintetizar o redactar un entregable a partir de varios documentos fuente (o pocos muy grandes) que juntos NO caben en la ventana de contexto — reportes multi-fuente, due diligence, análisis/comparación de contratos, resúmenes de expediente, investigación. Úsalo cuando debas correlacionar información entre múltiples documentos y producir un documento nuevo, y leerlos todos completos reventaría el contexto ("Prompt is too long"). NO es para leer un solo documento corto.
Busca y descarga assets de diseño (iconos, ilustraciones, 3D, animaciones Lottie, imágenes AI) desde IconScout. Dispara cuando el user pida un icono/ilustración/animación para un diseño, mockup, presentación o web, o mencione IconScout. 13.8M+ assets con licencia royalty-free.
Text-to-speech with Spanish voices (Kokoro, local & free) and OpenAI fallback
Transcribe a texto el HABLA de un audio largo o pesado (reuniones, notas de voz reenviadas, grabaciones de 10–60+ min). Trocea con ffmpeg y transcribe cada parte con Whisper, sin toparse con el límite de 25MB de la API. Úsala cuando llegue un [Audio file: ...] y el usuario pida "transcribe", "qué dice", "analiza el audio", o para resumir/analizar una grabación de voz.
Haz una versión nueva/ejecutiva/más limpia de un documento institucional (PDFs tipo "orden del día", agendas, hojas de evento, programas) MINANDO sus assets reales (logos, fotos de ponentes, texto, fuentes) y RECONSTRUYENDO en HTML — nunca re-difundiendo la página como imagen. Úsala ante "haz una nueva versión", "versión ejecutiva", "rehazlo más limpio", "adáptalo" de un PDF.
| name | add-reactions |
| description | Add WhatsApp emoji reaction support — receive, send, store, and search reactions. |
This skill adds emoji reaction support to NanoClaw's WhatsApp channel: receive and store reactions, send reactions from the container agent via MCP tool, and query reaction history from SQLite.
Check if src/status-tracker.ts exists:
test -f src/status-tracker.ts && echo "Already applied" || echo "Not applied"
If already applied, skip to Phase 3 (Verify).
git remote -v
If whatsapp is missing, add it:
git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git
git fetch whatsapp skill/reactions
git merge whatsapp/skill/reactions || {
git checkout --theirs package-lock.json
git add package-lock.json
git merge --continue
}
This adds:
scripts/migrate-reactions.ts (database migration for reactions table with composite PK and indexes)src/status-tracker.ts (forward-only emoji state machine for message lifecycle signaling, with persistence and retry)src/status-tracker.test.ts (unit tests for StatusTracker)container/skills/reactions/SKILL.md (agent-facing documentation for the react_to_message MCP tool)src/db.ts, src/channels/whatsapp.ts, src/types.ts, src/ipc.ts, src/index.ts, src/group-queue.ts, and container/agent-runner/src/ipc-mcp-stdio.tsnpx tsx scripts/migrate-reactions.ts
npm test
npm run build
All tests must pass and build must be clean before proceeding.
npm run build
Linux:
systemctl --user restart nanoclaw
macOS:
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
sqlite3 store/messages.db "SELECT * FROM reactions ORDER BY timestamp DESC LIMIT 5;"
Ask the agent to react to a message via the react_to_message MCP tool. Check your phone — the reaction should appear on the message.
In groups that require a trigger (requiresTrigger !== false and trigger !== '.*'), the 👀 acknowledgment fires only on the message that individually invoked the bot — not on every message in the processing batch. Context messages stay silent so the bot doesn't spam reactions on every comment in a group where it's one of many participants.
Main groups (trigger: '.*') keep reacting to every user message.
This mirrors OpenClaw's ackReaction.group: "mentions" mode (see docs.openclaw.ai/channels/whatsapp).
Without this filter, a single batch like ["@bot summarize", "lol", "agreed", "👍"] produces four 👀 reactions because all four advance through the message loop together. Users perceive it as the bot "watching" every comment in the group, even when it wasn't invoked.
src/index.ts derives an isInvokingMessage(msg) predicate once per turn, then uses it both for the trigger gate (some) and the markReceived loop (filter):
const needsTrigger = group.requiresTrigger !== false && group.trigger !== '.*';
const triggerPattern = needsTrigger ? getTriggerPattern(group.trigger) : null;
const stickerTrigger = group.containerConfig?.stickerTrigger !== false;
const allowlistCfg = loadSenderAllowlist();
const isInvokingMessage = (m): boolean => {
if (!needsTrigger) return true; // main group: react to everything
return (
(triggerPattern!.test(m.content.trim()) ||
(stickerTrigger && m.content.includes('[Sticker:'))) &&
((ASSISTANT_HAS_OWN_NUMBER && m.is_from_me) ||
isTriggerAllowed(chatJid, m.sender, allowlistCfg))
);
};
// Trigger gate: at least one invoking message present?
if (needsTrigger && !messages.some(isInvokingMessage)) return;
// Ack loop: only the invokers get 👀
for (const msg of messages) {
if (msg.is_from_me || msg.is_bot_message) continue;
if (!isInvokingMessage(msg)) continue;
statusTracker.markReceived(msg.id, chatJid, false, msg.sender);
}
The same predicate must be applied in both call sites where markReceived fires:
processGroupMessages (recovery / direct queue path)startMessageLoop poll cycleIf you only patch one of them, recovery messages or piped batches will leak reactions.
markThinking/markDone/markFailed use forward-only transitions, so non-tracked context messages no-op safely — no extra filtering needed downstream.is_from_me || is_bot_message filter still excludes the bot's own messages (linked-device siblings in shared-number setups, plus bot-prefixed media).stickerTrigger and the sender allowlist participate in the predicate, so sticker-triggered turns and allowlisted senders still get their 👀.81feb6a feat(reactions): only react to invoking message, not whole batch — adds the predicate-based filter to both call sites in src/index.ts. If your skill branch was forked before this commit, apply the pattern manually using the snippet above.
Failed to process reaction errorsstore/messages.db exists and is accessibleUnauthorized IPC reaction attempt blocked — the agent can only react in its own group's chat