Skip to main content
Cómo está montado @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 #private en Bot. Un plugin recibe Object.create(bot) como ctx.bot; sus métodos corren con this apuntando a ese objeto, y leer un #private a través de él lanzaría. Al kernel se llega por this, 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 tipo CALL, 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 message y send.
  • Plugins: qué registra y deshace un bot de plugin.