Paquetes públicos
@botmesh/baileys
toJid() lanza con una entrada que no puede convertir en JID. Devolvía
'@s.whatsapp.net' para una entrada sin dígitos, y un texto inválido …@g.us:<usuario>
para { groupId, userId }. Las dos lanzan ahora un TypeError.
toJid(userId) para la persona.
Las ediciones de texto llegan al bot. Un mensaje editado se descartaba. Ahora llega como
mensaje con el texto nuevo y el ref.id original, como en Telegram.
isEditedMessage(ctx) lo distingue. El dedupe del core deja pasar la edición y sigue
descartando un reintento idéntico. Solo se entrega la edición de un mensaje que el proceso
ha visto, enviado en el mismo chat por quien manda la edición: la que nombra el mensaje de
otra persona, uno del propio bot o uno de otro chat se descarta, igual que la de un original
que ya no está en la caché de mensajes del provider (p. ej. tras un reinicio). Para volver al
comportamiento anterior:
@botmesh/ai
provider: 'openai' con baseUrl envía max_tokens. Con cualquier baseUrl que no
sea api.openai.com ni Azure OpenAI (*.openai.azure.com, *.cognitiveservices.azure.com o
un endpoint de Azure AI Foundry *.services.ai.azure.com bajo /openai), el tope de tokens de salida va ahora en
max_tokens, que es lo que esperan los servidores compatibles con OpenAI (vLLM, LM Studio,
gateways). Si tu baseUrl es un proxy delante de OpenAI y usas un modelo o-series o gpt-5,
vuelve a fijar el campo:
memory.maxFactLength, memory.maxFacts o
memory.ttl inválido lanza ahora un RangeError desde aiPlugin() en vez de fallar en
plug() (o, con maxFactLength, descartar en silencio todos los hechos).
onStreamEnd tiene un contrato fijo. GenerateInput.onStreamEnd(info) (un
AIStreamEndInfo: stopReason, más usage en Anthropic y Gemini) se llama exactamente una vez
por llamada a stream(), tras el último fragmento y antes de que termine la iteración, y solo
si el stream termina con normalidad: nunca si falla, se aborta o el consumidor sale antes con
break. Una excepción lanzada por el callback hace fallar la iteración del consumidor. Todos
los proveedores integrados y MockAIProvider lo cumplen, también la regla del aborto cuando el
cliente ignora signal (un stream abortado no llama al callback y rechaza con el motivo del
aborto); un AIProvider propio que llame a onStreamEnd también debería (comprueba
signal.aborted antes de llamarlo).
Entradas y datos congelados. MockAIProvider pasa a @botmesh/ai/testing, los helpers del
plugin y los nombres de los tipos de los SDK a @botmesh/ai/internal, y los vocabularios,
presets y textos exportados quedan congelados. Ver
Migrar a la API estable.
@botmesh/meta
- Los límites interactivos se comprueban antes de enviar. Un
buttonsocardsin botones, ids o títulos de botón repetidos (comparados tras el corte a 20 caracteres), una lista sin filas, con más de 10 filas o con ids de fila repetidos rechazan ahora conUnsupportedContentError. Graph también los rechazaba, pero con un error opaco tras la petición. - Una respuesta interactiva que no es una pulsación (por ejemplo
call_permission_reply) llega como mensajeunknowncon el payload enctx.ref.native, no como interacciónbuttoncon datos vacíos. Un handler que dependía de eso debe leer el mensaje. - Las subidas de media tienen su propio timeout,
uploadTimeoutMs(5 minutos por defecto). - Nueva opción
langpara las etiquetas quepublishForm()fija en un Flow (por defecto'en'): ponle elBotConfig.langde tu bot.publishForm(def, { lang })la sobrescribe. - Deprecados (se quitan en v3):
toProviderError(usatoGraphErrorde@botmesh/graph-core),GraphClient(internos del transporte), losverifyChallenge/verifySignaturereexportados (usaprovider.verifyChallenge(query)yprovider.ingest(), o impórtalos de@botmesh/graph-core) ydebug(pasa unlogger).phoneNumberIdadmitestring | numberen el tipo.
@botmesh/telegram
- En modo webhook,
ingest()trasstop()responde503y no emite nada (Telegram reenvía el Update tras el reinicio). Los hosts que vuelven a arrancar el provider no tienen que cambiar nada. - Las subidas de archivos tienen su propio timeout,
uploadTimeoutMs(5 minutos por defecto). - Las encuestas se envían no anónimas y sus votos llegan al bot. Cada
poll_answerse convierte en un evento'poll'con la misma forma que en WhatsApp (vote.ref,vote.senderId,vote.optionsconvoters). Los miembros del chat ven quién votó. Los canales no admiten votantes públicos: una encuesta a un canal se reintenta una vez como anónima (sin votos) y el canal se recuerda. Para mantener encuestas anónimas (sin recibir votos):
pollTimeoutMssustituye apollTimeout(segundos, deprecado):pollTimeout: 30equivale apollTimeoutMs: 30_000.toProviderErrorpasa a sertoTelegramError(el nombre antiguo es un alias deprecado),debugqueda deprecado (pasa unlogger) y los helpers de cableparseUpdate,parsePollAnswer,toOutboundyMEDIA_UPLOADson@experimental.
@botmesh/sqlite-store
saveMessage y setCursor lanzan TypeError con un id de mensaje, id de chat, clave o
valor de cursor que lleve un surrogate UTF-16 suelto (antes SQLite lo guardaba como U+FFFD y
dos ids podían compartir fila). Escapa o quita esos caracteres antes de guardarlos.
Versionado del esquema. La base de datos guarda su layout en PRAGMA user_version
(SQLITE_SCHEMA_VERSION, ahora 2). Abrir un archivo escrito por una versión anterior
(versión 0 o 1) lo migra en el sitio y conserva todas las filas; no hay que hacer nada.
La versión 2 incorpora a las migraciones versionadas las tablas del scheduler durable
(schedules, schedule_fences) y la de durabilidad (durable): antes se creaban sobre la
marcha la primera vez que se llamaba a schedules() o durability(), y ahora se crean al
abrir el archivo. Un
archivo escrito por una versión más nueva se rechaza con SQLiteSchemaVersionError
(code: 'SQLITE_SCHEMA_TOO_NEW') y no se toca: no se admite volver atrás, así que haz una
copia del archivo antes de actualizar si puedes necesitar revertir.
Volver a la alpha anterior. La alpha anterior escribe y lee la versión de esquema 1.
En cuanto esta versión abre un archivo, este pasa a la versión 2 y la alpha anterior lo
rechaza con SQLITE_SCHEMA_TOO_NEW en su siguiente arranque, aunque la app nunca llame a
schedules() ni a durability(). Pasa lo mismo en un despliegue escalonado: un proceso que
sigue en la alpha anterior y comparte el archivo falla al reiniciarse, así que para todos
los procesos de la versión vieja antes de que el primero de la nueva abra el archivo. Para
volver atrás, restaura la copia hecha antes de actualizar. La migración no cambia ninguna
fila (solo añade tablas con CREATE TABLE IF NOT EXISTS), así que, si no tienes copia,
puedes fijar a mano PRAGMA user_version = 1 con la CLI sqlite3 mientras ningún proceso
tenga el archivo abierto, y la alpha anterior vuelve a abrirlo. El salto de versión existe
para que las migraciones futuras puedan dar por hecho que esas tablas existen; sin él
seguirían fuera de la versión.
Opciones nuevas:
busyTimeoutMs (por defecto 5000, PRAGMA busy_timeout) y un logger que acepta
cualquier LoggerLike. Las cadenas DDL exportadas SCHEDULES_SCHEMA y DURABLE_SCHEMA
quedan deprecadas.
@botmesh/messenger y @botmesh/instagram
Los dos canales son públicos desde la beta (1.0.0-beta), así que su primera versión en npm
ya trae la entrada congelada. Dentro del monorepo o de un fork, importar un nombre quitado
deja de compilar:
La opción
debug queda deprecada en los dos: pasa un logger. Más de 3 botones (o 10
filas de lista) en Messenger, o más de 13 quick replies en Instagram, rechazan con
UnsupportedContentError en vez de cortarse.
@botmesh/evolution, @botmesh/wppconnect y @botmesh/wa-gateway-kit
Nuevos en npm en la beta (antes eran privados). Si los usabas desde el monorepo:
- Los parsers de webhooks, los mapeadores de salida y los tokens de media (
parseWebhook,parseMessage,toOutbound,numberedMenu,keyFromRef,target,jidOf…) y los helpers JWT de Evolution (verifyHs256Jwt,signHs256Jwt) pasaron a@botmesh/evolution/internaly@botmesh/wppconnect/internal, sin garantía semver. - Los providers ya no reexportan
WaIdentityStoreninormalizeJid: impórtalos de@botmesh/wa-gateway-kit.IdentityPersistenceyRestHttpOptionssiguen en la entrada principal de los providers. - Se quitó la opción
debug: pasa unlogger(pino({ level: 'debug' })). - Las opciones inválidas lanzan
TypeError(antes unErrorgenérico; los mensajes no cambian). Además,baseUrlya no admite credenciales (user:pass@), query string ni fragmento,identityCacheSizedebe ser un entero positivo (oInfinity) yhttp.timeoutMs/http.maxBytesse validan al construir el provider, no en la primera petición. - Solo una URL
http(s)se entrega al gateway para que la descargue.{ url }es siempre un enlace, como en Meta y Telegram: admitehttp(s)o undata:URI en base64 (que va inline), y una ruta local o una URLfile:ahí rechaza conUnsupportedContentErroren vez de leerse (antes se pasaba tal cual y el gateway intentaba abrir esa ruta en su propio host). Envía un archivo local como{ path }o como string con la ruta; el bot lo lee y lo manda en base64.
Plugins que pasan a npm
@botmesh/rag, @botmesh/voice, @botmesh/nlu, @botmesh/dialogflow,
@botmesh/chatwoot, @botmesh/tickets y @botmesh/moderation se publican en npm desde
la beta (1.0.0-beta). El código que
los usaba desde el monorepo (workspace:*) necesita estos cambios:
- Helpers movidos a una entrada
/internal(sin semver; una aplicación rara vez los necesita):@botmesh/rag/internal(splitMarkdownSections,resolveChunkOptions,cosine,hashText,tokenize,resolveEmbedder,createPdfParser),@botmesh/voice/internal(trimPunctuation,resolveStt,resolveTts,OPENAI_TTS_MIMETYPES,elevenLabsMimetype,estimateOggDurationSec,estimateDurationSec,sniffMimetype),@botmesh/dialogflow/internal(normalizeEsResult,normalizeCxResult,toSessionId,parseCredentials,createServiceAccountJwt,DIALOGFLOW_SCOPE,DEFAULT_TOKEN_URI),@botmesh/chatwoot/internal(ChatwootMapping,verifyWebhook,inboundText,outboundText),@botmesh/tickets/internal(renderTemplate,localClock) y@botmesh/moderation/internal(findLinks,findInviteLinks,compileTerms,foldText). @botmesh/dialogflow:dialogflowPlugines el nombre canónico ydialogflowContextun alias obsoleto.languageCodevale por defecto'en'(antes'es'): ponle el idioma de tu agente. En grupos,sessionScopevale por defecto'participant'(una sesión de Dialogflow por miembro, como las conversaciones); usasessionScope: 'chat'para mantener una sesión por grupo. Las opciones mal escritas (minConfidence: 80, unfulfillmentdesconocido…) lanzan ahoraDialogflowError(INVALID_CONFIG) enbot.plug(), y los textos de más de 256 caracteres ya no se envían (maxTextLength).- Datos congelados:
DEFAULT_VOICE_LIMITS,SUPPORTED_EXTENSIONS(rag),TICKET_PRIORITIESyTICKET_STATUSESya no se pueden mutar; cópialos antes. @botmesh/chatwoot: se rechaza una firma de webhook sinx-chatwoot-timestamp(no caducaba, así que una petición capturada se podía repetir). ActivawebhookAllowUntimedSignature: truesolo para un emisor que no pueda añadir el timestamp.@botmesh/tickets:TicketKeyValuetiene unsetIfopcional. Un almacenamiento propio que lo implemente pasa el desk a modo compartido (seguro con réplicas).
Paquetes que eran privados
Estas notas importan al código que usaba los paquetes desde el monorepo o un fork antes de la beta.@botmesh/discord, @botmesh/email y @botmesh/flow-schema están en npm desde la
beta; @botmesh/tiktok y los paquetes de producto siguen siendo privados.
@botmesh/discord:ctx.interaction.id(interactionId) es el id de la interacción a secas; el token se queda en el provider, y la forma alfa"<id>:<token>"deacknowledgeInteraction()se quitó antes de la primera publicación (solo se conocen los ids que recibió el provider). Las interacciones que el bot no confirma enautoAcknowledgeMs(2 s; el alias alfaautoAckMsse quitó) se difieren por él; ponautoAcknowledgeMs: falsepara seguir haciéndolo tú. Los mensajes enviados llevanallowed_mentions: { parse: ['users'] }si no indican otro (allowedMentionslo cambia), así que@everyone,@herey las menciones de rol ya no notifican por defecto. Las ediciones de mensajes (MESSAGE_UPDATE) llegan ahora como mensajes con elref.idoriginal (isEditedMessage(ctx);editedMessages: 'ignore'las descarta).- Mapeadores de errores renombrados (los nombres antiguos son alias deprecados):
toTikTokError,toEmailError.toDiscordErrorno tiene alias:toProviderErrorse quitó de@botmesh/discordantes de su primera publicación. - Stores de producto:
InboxStore.erase,ContactStore.eraseyDeploymentStore.pruneson métodos nuevos obligatorios, yDeploymentStore.list/InboxStore.listNotesaceptan un cursorafter. Un store propio debe añadirlos (las suites de conformidad compartidas detest-utils/los comprueban).attachInbox()sintenantIdguarda ahora las conversaciones en el tenant del propio bot en vez de'local'. @botmesh/flow-schema:publishFlow()acepta cualquier registro con unpublish()compatible (unDeploymentRegistryencaja) y ya no depende de@botmesh/deploymentsen tiempo de ejecución.