@botmesh/core: las capas de paquetes, los módulos que hay detrás de
la clase Bot, el camino de un mensaje de entrada y de salida, y dónde se enchufan
las funciones opcionales (durabilidad, coordinación entre réplicas, scheduler durable,
trazas). No necesitas nada de esto para escribir un bot. Es para quien extiende el
core, escribe un canal o un store, o depura lo que pasa entre el provider y un handler.
Capas
El framework es un monorepo pnpm. Las dependencias solo van hacia abajo:
El core es agnóstico de transporte. No sabe nada de JIDs, códigos QR ni webhooks:
los ids de chat, usuario y mensaje son strings opacos que nunca parsea, y el provider
resuelve los alias de identidad (LID/PN en WhatsApp,
sender_chat en Telegram)
antes de emitir el evento, así que la misma persona llega siempre con el mismo
senderId. Todo lo específico de un canal vive en el paquete del canal.
Dentro del Bot
Bot es una fachada fina: sus métodos llevan la documentación pública y delegan.
El motor vive en packages/core/src/core/bot/:
Tres reglas lo mantienen en pie:
- Sin campos
#privateenBot. Un plugin recibeObject.create(bot)comoctx.bot; sus métodos corren conthisapuntando a ese objeto, y leer un#privatea través de él lanzaría. Al kernel se llega porthis, o sea, por el prototipo. - Enlaces tardíos. Los módulos llaman de vuelta al bot con closures (
bot.emit,bot.trigger,bot.isHandedOff…) que se leen en el momento de la llamada, así que se respeta un spy, un override de subclase o el de un bot de plugin instalado después. - El comportamiento va en un módulo, no en la fachada. Cada módulo tiene menos de 500 líneas y abre con un comentario que dice qué hace y qué no.
Pipeline de entrada
Las cajas punteadas solo existen cuando su función está configurada. Las etapas de enrutado dentro del Dispatcher, en orden, están en Conceptos básicos. Dos entradas se saltan parte de este camino: las llamadas pasan por las mismas listas de acceso y el handoff y solo llegan a triggers de tipoCALL, y bot.trigger() pasa por las listas de acceso, toma el
turno del chat en la cola (runExclusive), el gate de handoff y la resolución de
idioma, y arranca la conversación directamente: sin dedupe, persistencia, hooks de
mensaje ni matchers.
Pipeline de salida
ctx.edit() sigue el mismo camino con edit en el payload del hook. Las vistas
previas en vivo de ctx.stream() (borradores y ediciones en el sitio) solo corren los
hooks before('send'); los mensajes que el stream envía al final siguen el camino
completo. bot.enqueue() (solo con durabilidad) corre los hooks before('send'),
registra el envío y vuelve enseguida; lo entrega el barrido del outbox.
Ciclo de vida
stop() cierra la entrada y cancela los broadcasts, emite 'stopping' (ahí los plugins
cancelan el trabajo largo; @botmesh/ai aborta sus llamadas al modelo), detiene o pausa los pasos que
producen trabajo nuevo (scheduler, barrido de durabilidad), opcionalmente drena los
envíos en curso (stop({ drain: true })), cierra los envíos pausados y aborta las
conversaciones (los checkpoints se conservan), y después detiene el resto de pasos en
orden inverso (el paso storage deja antes que terminen, hasta 5 s, los envíos en vuelo y las filas
del log de los envíos que el provider ya aceptó, y luego cierra el store). Si start() falla, los pasos ya arrancados se deshacen en orden
inverso y el error se relanza.
Una función opcional añade un paso de ciclo de vida; no reparte ifs por el código
de arranque y parada.
Dónde se enchufan las funciones opcionales
Todas vienen apagadas. Un bot que no las configura corre exactamente el camino descrito arriba sin las cajas punteadas.Ver también
- Conceptos básicos: el orden completo de enrutado y las conversaciones.
- Hooks: los contratos de los hooks
messageysend. - Plugins: qué registra y deshace un bot de plugin.