Bot de Telegram con cola duradera en SQLite: ejecuta skills de una etapa y flujos de varias fases con estado, portón humano y reanudación después de una caída.

El bot recibe comandos en Telegram, encola el trabajo y lo ejecuta como agente. La cola es duradera: si el proceso se cae a mitad de un renderizado, al volver a iniciarse reclama lo que quedó en vuelo antes de aceptar trabajo nuevo.
SQLite en WAL, claim atómico, lease con heartbeat y drain al apagarse. Un kill -9 en medio de un job no deja el estado indefinido: nada queda running con un lease activo.
Pipeline con estado por fase y por objetivo, definición congelada al crearlo, pausa para que hagas tu parte (/aprovar) y reanudación selectiva con /refazer.
Una skill es un prompt + una entrada en el registry. Un flujo es un repo con flow.json + prompts/. Ninguno de los dos requiere una línea de TypeScript en el bot.
El gateway valida quién habla (allowlist de chat), el comando se convierte en un job en la cola correcta, un worker reclama el job con un lease, el agente lo ejecuta y el notificador devuelve el resultado en el chat.
io (10) · cpu (1) · texto (2) · render (1) · navegador (1). Un render pesado no bloquea la cola de texto.
Skill = una etapa, sin estado: es aceptable empezar de cero. Flujo = varias fases con estado, cuando sería absurdo descartar el trabajo parcial.
Migrations → raíz de medios → recuperación de leases vencidos → solo entonces workers y bot. Este orden permite recuperar el proceso tras una caída.
Node con TypeScript, un bot creado en Telegram y un .env con las cinco variables obligatorias. Si falta alguna, el arranque falla de forma clara y temprana, antes de iniciar cualquier worker.
Instala y ejecuta las pruebas antes de subir cualquier cosa.
# en la raíz del repo npm install npm test
Crea el bot en @BotFather y guarda el token. El valor real nunca va al git.
# .env (modo 600, fuera de git) BOT_TOKEN=… ALLOWED_CHAT_IDS=123,456
La ruta de la base de datos de la cola, la raíz del estado y el archivo de registro — las otras tres obligatorias.
QUEUE_DB=./inemaccbot.db STATE_DIR=./estado LOG_FILE=./inemaccbot.log
Todos los comandos de abajo son reales: los de shell se ejecutan en el repo, los que empiezan con barra los escribes en Telegram.
El registry de skills y flujos se valida al iniciar: una entrada inválida hace que el servicio se detenga a propósito.
npm install npm test # vitest run npm run typecheck # tsc --noEmit npm run build # tsc -> dist/index.js
En producción mediante systemd, con el .env en el mismo directorio de trabajo (deploy/inemaccbot.service).
node dist/index.js # directo systemctl --user start inemaccbot # como servicio
En el chat autorizado. /ajuda lista todo; /skills e /fluxos son los dos catálogos.
/ping /ajuda /skills # una etapa, sin estado /fluxos # varias fases, con estado
El formato es <skill>: <entrada> [| campo]*. Campos genéricos: livesN (destino), modelo=haiku, esforco=high.
transcrever: https://… # audio → texto explicativo: <assunto> | vertical # video 9:16 imagem: uma raposa ruiva na neve | ratio=16:9
| sombra imprime fase × objetivo × cola × tarea y no pone nada en cola. Es la forma barata de descubrir que ibas a enviar por error a 12 públicos.
/promoavatar <assunto> | sombra /promoavatar <assunto> --alvo=jovens /promoavatar <assunto> | alvos=mulheres | legenda
/status es el panel de los flujos ABIERTOS; /completos lista los que terminaron. A#9, a#9, A9 e a9 son lo mismo — solo números (13) siempre es job.
/status # flujos abiertos /status A#9 # detalle fase × objetivo /fila # en ejecución, pendientes, errores en 24h /pronto A#9 # = /aprovar — abre la puerta
/refazer en un flujo, retoma desde la fase que falló; en un objetivo único, vuelve a procesar ese objetivo. /cancelar quita de la lista sin borrar nada de lo que ya se creó fuera.
/refazer A#9 mulheres # solo el público que falló /cancelar A#9 # desaparece de /status; el flujo sigue existiendo /furar j13 # pone un job pendiente al frente
Sin la palabra confirmar al final, solo muestra lo que saldría y cuánto libera. El bot solo toca lo que ÉL publicó dentro de ~/projetos/output.
/espaco # disco por área (bot × skills) /limpar A#8 # dry-run /limpar A#8 confirmar # ejecuta
claude saiu com código 143 é 128 + 15 = SIGTERM: el job fue terminado por un reinicio del servicio, no por un error del agente. Un render de reel tarda 10–15 min — dos reinicios seguidos agotan los dos intentos del mismo job.
sqlite3 inemaccbot.db \ "select id,fila,tarefa,status,flow_ref from jobs where status in ('queued','running');" # vacío → reinicia cuando quieras. Con un render en curso → espera.
Esta es la prueba del diseño: un dominio nuevo no debe exigir líneas de código en el bot. Elige entre skill y flujo según el criterio del trabajo parcial.
Aplica cuando «volver a ejecutarlo desde cero» es aceptable. Escribe el prompt, decláralo en el registry y ejecuta las pruebas. Los campos declarados deben usarse en el prompt, y las variables del prompt deben declararse; hay pruebas para ambos casos.
# 1. prompts/minhaskill.md — usa {{input}} y {{saida}}, # y la última línea del agente es RESULT: <caminho> # 2. config/skills.json { "command": "minhaskill", "fila": "texto", "kind": "agent", "prompt": "prompts/minhaskill.md", "artefato_exts": ["txt"], "max_tentativas": 2, "timeout_segundos": 3600 } # 3. npm test
Aplica cuando hay trabajo parcial que sería absurdo descartar. Gana /status, /refazer selectivo, reanudación y definición congelada. El dominio dice PARA QUIÉN; el bot sabe DÓNDE — nunca pongas la ruta en flow.json.
# 1. repo ~/projetos/<nome> con flow.json + prompts/ # 2. flow.json: nombre, prefijo (la P de P#16), versao_def, # objetivos y fases (id, alcance, cola, tipo, tarea…) # 3. config/fluxos.json: { command, repo, descricao, exemplo } # 4. comprueba sin gastar nada: /<fluxo> <assunto> | sombra
Todo dominio que entra en el catálogo responde ayuda — no por disciplina, sino por diseño: quien entiende, escribe (HELP.md en el flujo, <prompt>.help.md en la skill) y, si no se escribió, la ayuda se deriva del mismo registro que el bot usa para ejecutar. Una prueba recorre ambos catálogos y falla si algún dominio no responde con ayuda utilizable.
Etapas 0 a 5 completadas, más los flujos de dominio. La v1 (inemaccvbot, mkivideos, mkitexto) está desactivado.
chat_id ser INTEGER. Recomendación en análisis: no reemplazar, agregar.