- Grabar una conversación (en tests o en producción) o construirla a partir del log de mensajes del store.
- Redactarla para que ningún dato personal acabe en el repositorio.
- Guardarla como JSON.
- Reproducirla en un test (o en un gate de despliegue) y fallar ante cualquier diferencia.
@botmesh/core también reexporta las funciones de grabación, redacción y formato, marcadas como
@deprecated: impórtalas de @botmesh/core/testing (la grabación también sirve en producción).
Grabar una conversación
recordTranscript(provider, options?) envuelve cualquier IProvider (un
transporte real o un MockProvider) en un RecordingProvider transparente.
Pasa recorder.provider al bot; graba los mensajes e interacciones entrantes y
cada envío correcto.
transcript({ name?, description? }) (un snapshot),
clear(), pause() y resume().
En un test creado con createTestBot, chat.transcript() devuelve esa misma forma
directamente desde el mock (ver Snapshots de conversación).
Los envíos fallidos u omitidos no se graban. La grabación también funciona en
producción (
RecordingProvider envuelve tanto un transporte real como el mock), pero el buffer guarda en
RAM los cuerpos de los mensajes, los nombres visibles y los ids sin
redactar: mantén maxTurns acotado y llama a clear() en cuanto hayas
sacado una transcripción.
Desde el log de mensajes del store
transcriptFromStore(source, chatIds, options?) lee uno o más chats con
getMessagesByChat y los une en una transcripción ordenada por timestamp.
Pasa el bot (o bot.getStore()): un Bot escribe en una vista con
namespace del store que recibe, así que el store crudo no encuentra nada con
los ids de chat sin más.
name, limit (por chat, por defecto 1000), includeFailed
(conserva los mensajes salientes con estado failed, por defecto false) e
isGroup (un booleano o (chatId) => boolean; si se omite,
transcriptFromStore lee el tipo de chat del store).
transcriptFromMessages(messages, options?) hace lo mismo con un array de
StoredMessage.
El store no guarda el contenido saliente completo ni los nombres visibles, así
que estas transcripciones solo comparan kind y text, y las reproducciones
usan el push name por defecto del mock.
El formato del archivo
- Turnos entrantes (
dir: 'in'):chatId,senderId,type,texty, opcionalmente,event('message'o'interaction', dondetextlleva los datos del botón o la lista),isGroup,displayName,idyt. - Turnos salientes (
dir: 'out'):chatId,kind,text(texto, caption, pregunta…) y, opcionalmente,content(el contenido completo apto para JSON, solo al grabar en vivo; los binarios pasan a{ $binary: { bytes, sha256 } }),idyt. tson milisegundos desde el primer turno;startedAt(epoch ms) es opcional y la redacción lo elimina.- Los ids son strings opacos: nada los parsea.
serializeTranscript(t) escribe JSON estable y cómodo para diffs (indentación
de 2 espacios y salto de línea final). parseTranscript(input) (un string JSON o un
objeto ya parseado) valida la forma y la versión y, si no cuadran, lanza TranscriptFormatError.
createTranscript(turns, meta?) construye una a mano.
Redacción
redactTranscript(transcript, options?) devuelve una copia redactada con
marcadores estables numerados por orden de aparición, así que el resultado es
determinista y redactar dos veces no cambia nada.
- Los ids pasan a
<user:N>,<chat:N>(chats de grupo) y<msg:N>. Los ids de usuario y de chat de 4 o más caracteres también se sustituyen dentro de los textos (los ids de mensaje, solo como campo completo). - Los nombres visibles pasan a
<name:N>, en el campo y dentro de cualquier texto (así también se redacta una respuesta que saluda al usuario por su nombre). Al reproducir, el marcador se envía como push name, de modo que la respuesta sigue coincidiendo. - Los emails y teléfonos (7 o más dígitos) de los textos entrantes pasan a
<email:N>/<phone:N>. Los textos salientes solo reciben los valores ya aprendidos del lado entrante (datos que el bot repite): precios, fechas o números de pedido que imprime el bot por su cuenta se quedan como están, porque la reproducción los compara con los valores reales.
Reproducir y comprobar
replayTranscript(transcript, createBot, options?) construye un bot nuevo
sobre un MockProvider en memoria, le pasa los turnos entrantes uno a uno y
compara lo que envía con los turnos salientes grabados. createBot recibe el
provider y debe devolver el bot sin arrancarlo; la reproducción lo arranca
y lo detiene.
assertTranscript lanza un TranscriptMismatchError (con .result) cuyo
mensaje es el informe. replayTranscript, en cambio, devuelve un
ReplayResult: { ok, diffs, expected, actual, report() }, donde actual es
la conversación que produjo el bot. Cada TranscriptDiff tiene un kind
('missing', 'unexpected', 'mismatch' o 'timing'), el step (índice del
turno entrante, -1 antes del primero), chatId, position, los turnos
expected y actual, el primer field distinto ('kind', 'text',
'content.buttons.0.text', 't', 'id') y un message.
formatReplayReport(transcript, diffs) genera el mismo informe.
Tras cada turno entrante, la reproducción espera a que el bot haya enviado al
menos tantos mensajes como los esperados y luego a que pase settleMs sin
actividad, así detecta tanto las respuestas que faltan como las que sobran.
Opciones de tolerancia
REPLAY_MASKS trae máscaras listas: isoDate, time, uuid y number.
Gate de publicación (@botmesh/deployments)
DeploymentRegistry guarda versiones inmutables por tenant y bot con una única
versión activa (publish, active, rollback, list).
publish(tenantId, botId, version, artifact, { gate }) ejecuta el gate
antes de activar la versión:
- si pasa, la versión se guarda y se activa con
deployment.gate = { passed: true, checkedAt, summary? }; - si falla o lanza,
publishrechaza con unDeploymentGateError(candidate,result) y no se guarda nada, así que la misma versión se puede corregir y volver a publicar.
publish devuelve una Promise, y la versión queda reservada
mientras el gate corre (un publish duplicado falla con deployment version already exists). Un gate es cualquier función (candidate) => boolean | GateResult, síncrona o async; GateResult es
{ ok, reason?, summary?, details? }.
goldenTranscriptGate(options) reproduce transcripciones doradas contra el
candidato y solo pasa si todas coinciden. Cada transcripción corre contra un
bot nuevo.
Si falla,
error.result.details.results lista { name, ok, diffs, report }
por transcripción.