Limitaciones conocidas
Los vacíos honestos - qué está probado contra una aplicación real en ejecución y qué no.
En esta página
- Las pruebas corren solo contra el proveedor mock
- Real ColdBox integration testing
- La condición de carrera del primer request de toAi()
- Tres verificaciones de integración - cerradas por una ruta distinta a la planeada originalmente
- Corregido: las pruebas de CLI de proceso de SO real detectaron un bug real de resolución de módulos
- Corregido: un conjunto de bugs reales encontrados al construir el framework de pruebas
- La búsqueda del miniserver de serve es solo por PATH
- El home de runtime de BoxLang delimitado alcanza a serve / invoke --server incondicionalmente; los verbos en proceso solo si tu instalación de BoxLang carga .env
- El paso de conveniencia box install de new no está ejercitado por una prueba automatizada
- chat necesita una TTY real
- Corregido: chat e invoke por defecto (sin --server ) solían fallar para un Agent.bx basado en clase
- Ninguna prueba de humo de instalación del executable de box.json
- schedules/Scheduler.bx no se valida en tiempo de build
- Corregido: un bug real en el verbo test - resolver "el jar actual de BoxLang" era ambiguo
- Los destinos ssh / docker / digitalocean de deploy no están ejercitados por una prueba automatizada de proceso real
- Los destinos ftp / sftp de deploy : el manejo de conexión real está probado, una carga exitosa real no
- build no revierte un .build/app parcialmente escrito ante un fallo a mitad de generación
- Se observó un StackOverflowError intermitente en testBx , no relacionado con el propio código de este proyecto
- Gateways de estilo push (Telegram) probados solo contra un seam mockeado de API/scheduler - ninguna integración de plataforma en vivo corre en CI
- WhatsApp Personal (puente no oficial de cuenta personal) - investigado, no construido
- GatewaySession es de todo el proyecto y solo de agente raíz (v1)
- Corregido: GatewaySessionBootstrap.bx buscaba gateways por la clave incorrecta de aiGatewayRegistry()
- Corregido: el snapshot publicado de bx-ai solía retrasarse respecto a su propia rama development
- La interfaz web de chat v1 ( exposes: "webui" ) - qué es real, y qué solo pudo comprobarse contra documentación, no contra un servidor en vivo
- El almacén SQLite de la interfaz web - verificado a nivel de biblioteca, no a través de un arranque real de ColdBox
- La interfaz web sobre HTTP real: probada para /health y las rutas de preferencia de espacio de trabajo compartido
- El propio front-end de la interfaz web: conducido en un navegador real, pero contra una API mockeada
- Los encabezados de grupo de la navegación del sitio siguen en inglés en un idioma traducido
Limitaciones conocidas
BxAgents está en desarrollo activo. Esta página rastrea los vacíos honestos - qué está probado contra una aplicación real en ejecución, qué todavía solo corre contra el proveedor "mock" de bx-ai, y las peculiaridades reales ascendentes con las que este proyecto se topó.
Las pruebas corren solo contra el proveedor mock
Cada spec de vía rápida (pipeline de build, generadores, verbos de CLI) y la suite de integración de ColdBox ejercitan el proveedor "mock" incorporado de bx-ai - nunca una llamada de red real a un LLM. Esto es deliberado (rápido, gratis, CI determinista), pero significa que ninguna prueba automatizada actualmente demuestra que un proveedor real (OpenAI, Anthropic, etc.) realmente funciona correctamente de extremo a extremo. Haz al menos una ejecución manual de chat/serve contra un proveedor real y una clave de API desechable antes de depender de esto en producción.
Real ColdBox integration testing
./gradlew testColdBoxIntegration arranca un proceso real de boxlang-miniserver contra el propio Application.bx/Bootstrap de una app generada, y hace un request HTTP genuino a través de una ruta registrada con toAi(). Esta es la prueba más sólida de la suite - detectó tres bugs reales en ColdBoxAppGenerator.bx durante el desarrollo (un extends faltante en un Binder de WireBox, un orden incorrecto de argumentos del constructor de Bootstrap, y una llamada simple a getInstance() que no existe en Binder).
La condición de carrera del primer request de toAi()
El primer request HTTP a la ruta toAi() de una app recién arrancada puede fallar transitoriamente con "Function [getInstance] not found" - una condición de carrera genuina de inyección perezosa de ColdBox/WireBox en el propio delegado getInstance del Router, no un bug de BxAgents. Tiene éxito de forma confiable en cada request posterior a que algo más ya haya forzado a WireBox a construir el singleton GeneratedAgent una vez (un health check, una sesión de chat, otra ruta). Envía un request de calentamiento antes de depender de una ruta toAi() recién desplegada bajo carga.
Tres verificaciones de integración - cerradas por una ruta distinta a la planeada originalmente
tests/specs/integration/RuntimeStartupSmokeSpec.bx todavía tiene tres xit(), ya que este archivo se ejecuta vía el corredor de CLI (runTests.bxs/testBx), y el modo CLI de BoxLang nunca da un scope cgi - que el RoutingService de ColdBox necesita incluso para cargar el router en el arranque. Eso es un hecho estructural sobre el modo CLI, no un vacío: las tres verificaciones ahora están probadas de verdad en otro lugar, en tests/specs/integration/coldbox/ColdBoxRuntimeSpec.bx (que se ejecuta dentro de un request HTTP real servido por un proceso real de boxlang-miniserver):
- Un request HTTP enrutado por ColdBox real llegando al agente generado de extremo a extremo - probado por
ColdBoxRuntimeSpec.bxmás la propia aserciónPOST /api/chat/invokederunColdBoxIntegrationTests.bxs. schedules/*realmente registrándose con unSchedulerde ColdBox en vivo - probado víaSchedulerService.getSchedulers()["appScheduler@coldbox"].hasTask(...)contra un arranque real, sin esperar a que se dispare un cron real (la granularidad más gruesa soportada es 1 minuto, lo que gravaría cada ejecución de CI por una prueba adicional marginal una vez que la tarea se confirma en vivo y registrada).chaty una ruta HTTP servida (serve) nunca divergiendo - corregido de su redacción original (chatdeliberadamente nunca arranca WireBox en absoluto, así que nunca puede compartir el objeto singleton de WireBox por diseño) a lo que realmente importa: instanciarGeneratedAgentFactoryfuera de WireBox produce un agente conductualmente equivalente al propio singleton de WireBox.
Corregido: las pruebas de CLI de proceso de SO real detectaron un bug real de resolución de módulos
ModuleCliProcessTest.java genera procesos hijo genuinos de java -jar <boxlang-jar> module:bxagents <verb> ... contra una copia de la estructura real del módulo instalable (build/modules/bxagents), apuntado a un modulesDirectory real - exactamente como una instalación real de BoxLang carga este módulo. Cada otro spec de CLI llama a ModuleConfig.main()/al run() de cada verbo en proceso en su lugar, lo cual es más rápido pero nunca prueba que el módulo se resuelva correctamente una vez genuinamente instalado. Esta prueba detectó a todo verbo de CLI fallando con "class not located" en el momento en que se ejecutó a través de un proceso de módulo genuinamente instalado, porque las referencias cruzadas internas usaban una ruta con puntos simple bxagents.models.... que solo se resolvía gracias al propio boxlang.json de dev/test de este repositorio (un mapeo /bxagents declarado a mano) - un módulo genuinamente instalado nunca obtiene ese mapeo. Corregido cambiando las referencias internas de cada clase anidada a la forma de sufijo relativo al módulo Class@bxagents (ver el docblock de init() de BuildPipeline.bx para la explicación completa); la salida de estructura de módulo de build.gradle se movió de build/module a build/modules/bxagents (el nombre de la carpeta debe ser igual al nombre del módulo para que el descubrimiento de modulesDirectory lo encuentre en absoluto), y el boxlang.json de dev/test también lo carga ahora como un módulo real.
Corregido: un conjunto de bugs reales encontrados al construir el framework de pruebas
Construir el matcher toHaveCalledTool de BaseAgentSpec y el arnés de integración de ColdBox sacó a la luz - y corrigió - una serie de bugs reales, no detectados previamente. Registrados aquí como un changelog de lecciones aprendidas, no como vacíos abiertos:
- Los agentes nunca recibían sus propias tools. La llamada
aiAgent()generada porColdBoxAppGeneratornunca pasaba en absoluto un argumentotools:- confirmado contra el código fuente real de bx-ai (AiAgent.bxnunca referenciaaiToolRegistry()internamente). Ningún agente que BxAgents haya construido jamás, en ningún contexto, realmente recibió sus tools declaradas; pasó desapercibido porque ninguna prueba afirmaba sobre una invocación real de tool, solo sobre una respuesta no vacía. Corregido enColdBoxAppGenerator.renderAgentFactory(): cadaGeneratedAgentFactory.bxgenerado ahora carga su propio directoriotools/víaToolRegistryLoader.bx(una ruta absoluta, no una relativa - la resolución relativa deaiToolRegistry().scan("tools")falla silenciosamente desde un contexto cargado porDynamicClassLoadercomochat), luego pasatools: aiToolRegistry().getAll()a cada llamada deaiAgent(). varno puede usarse en el nivel superior de un script.bxs. Declara en el scopelocal, que solo existe dentro de una función, así que unvar x = ...de nivel superior lanzaScope [local] is not available in this contexten tiempo de ejecución, no en tiempo de análisis - sobrevive la revisión y solo se dispara en el camino de código que lo alcanza. Nunca escribasvarfuera de una función en un.bxs, y envuelve la salida de diagnóstico en su propio try/catch para que nunca pueda enmascarar el fallo que está describiendo.- El ajuste de aplicación para un datasource por defecto es
this.datasource, NOthis.defaultDatasource. Este último se acepta silenciosamente y no hace nada -queryExecute()todavía falla conNo default datasource defined..., nombrando el datasource registrado que estás intentando seleccionar, lo cual se lee como si el mecanismo de selección estuviera roto en lugar de que el ajuste esté mal escrito. - El
moduleSettings.qb.defaultOptionsde qb no llegaba aQueryBuilderen un arranque real de ColdBox, aunque el propio cableado deonLoad()de qb se lee como correcto - cada query fallaba con "no default datasource" a pesar de que el datasource estaba registrado.ChatDb.query()ahora llama a.mergeDefaultOptions( { datasource : static.DATASOURCE } )en cada builder explícitamente en lugar de depender de ese cableado. - Una app generada ahora también declara
this.defaultDatasource, así que cualquier cosa que ejecute una query sin nombrar uno (ColdBox mismo, otro módulo) alcanza el único datasource en lugar de fallar.ColdBoxAppGeneratorSpeccubre tanto el caso con web UI como el caso sin web UI. - El
models/ChatDb.bxgenerado no compilaba, y ningún spec unitario lo notó. Un bug de comillas de literal de cadena de BoxLang (?: ""emitido como?: ", abriendo una cadena sin terminar) se enviaba en cada proyecto de web UI generado; cada caso deWebUiGeneratorSpecsolo hacía grep del texto generado, nunca intentaba compilarlo. Ahora hay un caso que afirma que cada línea generada tiene un número par de comillas dobles - la lección más amplia siendo que un spec de generador que solo hace grep de su propia salida no prueba que la salida sea código válido. GET /chat/api/healthprueba mucho menos de lo que afirmaba un comentario obsoleto. La acciónhealth()generada renderiza un literal 200 y no toca nada más - no dice nada sobre WireBox,ChatDbo SQLite. Un comentario enrunColdBoxIntegrationTests.bxssolía afirmar lo contrario; corregido en el lugar.- El
JSONReporterde TestBox producía un StackOverflow en specs que sostenían singletons de framework en vivo (objetos reales decontroller/WireBox/qb reflexionados recursivamente durante la serialización) - una suite que pasaba se reportaba como una ejecución fallida.tests/runner-coldbox.bxmahora ensambla su propio reporte vía un helpertoScalar()en su lugar, y registra los conteos de aprobado/fallido antes de que se construya el reporte. - En una máscara de tiempo de BoxLang,
nnson NANOSEGUNDOS, no minutos - usamm. (Nótese la trampa inversa en una máscara de fecha, dondemmes el mes.) - Dos acciones de
handlers/ChatUi.bxtenían un agujero de autorización a pesar de derivar identidad correctamente en otras partes./pendingy/resumese direccionaban solo porthreadId, así que un visitante con elthreadIdde otra persona podía leer sus tool calls pendientes y responder sus aprobaciones - derivar identidad del lado del servidor da atribución, no control de acceso. Ambas ahora comparan al llamador contra eluserIdque el agente checkpointeó en las opciones de la ejecución. - La comprobación de ruta de
ProjectValidatorno detectaba el traversal.., atrapando solo un separador inicial -../../var/lib/chat.dbpasaba limpiamente. Ahora se rechaza en ambos casos, coincidiendo segmentos de ruta completos para que nombres legítimos como..hiddensigan pasando. chr()no existe en BoxLang - el BIF eschar(). Costó varios ciclos de CI porque el fallo es uno de tiempo de ejecución que sobrevive la revisión (tests/runner-coldbox.bxmlo llamaba en su primer marcador de progreso, así que cada request devolvía 500 al entrar).- La instrumentación que un reintento puede sobrescribir no registra nada. Un archivo de progreso se truncaba en cada reintento, ocultando la evidencia real y convirtiendo un simple 500 en un opaco "HTTP 408, never responded"; los reintentos mismos también eran activamente dañinos, apilando ejecuciones completas de pruebas concurrentes sobre un pool de trabajadores pequeño. Corregido: un solo request (la sonda de health check antes de él ya prueba que el servidor está arriba), registro de progreso de solo-adición etiquetado por request.
requestes un nombre de scope incorporado reservado. Una variable de bucle llamadarequestlo sombrea silenciosamente sin ningún error en absoluto - corregido renombrando arecordedRequesten los matchers deBaseAgentSpec.bx.
La búsqueda del miniserver de serve es solo por PATH
serve busca boxlang-miniserver solo en PATH (MiniServerLauncher.findExecutable()). No hay respaldo a una ruta configurada o a un binario incluido - si no está instalado y en PATH, serve falla con un error claro y accionable, pero no hay forma alternativa de apuntarlo a uno. invoke --server reutiliza serve internamente, así que hereda la misma búsqueda solo por PATH - su propia prueba de InvokeSpec.bx para el round-trip HTTP real comprueba MiniServerLauncher.findExecutable() primero y se omite (en lugar de fallar) cuando no hay ningún binario real en PATH, el mismo idioma que RuntimeStartupSmokeSpec.bx ya usa para su propia comprobación de presencia del jar. Haz al menos una ejecución manual de bxAgents invoke --message=... --server en una máquina con boxlang-miniserver instalado antes de depender del camino HTTP real en producción - el mismo enfoque honesto ya usado en otro lugar de esta página para vacíos que esta suite no puede cerrar por sí misma en cada entorno.
El home de runtime de BoxLang delimitado alcanza a serve/invoke --server incondicionalmente; los verbos en proceso solo si tu instalación de BoxLang carga .env
serve delimita el propio home de runtime de BoxLang del miniserver a .build/runtime vía serverHome (un campo real y confirmado de MiniServerConfig - el propio texto de ayuda de CLI de MiniServer de boxlang-web: -s, --serverHome <PATH> BoxLang server home directory (default: ~/.boxlang)), así que la caché de clases compiladas de cada proyecto y cualquier override de configuración están aislados por proyecto en lugar de compartidos globalmente en ~/.boxlang. invoke --server hereda esto ya que reutiliza serve internamente. Esta parte es incondicional - nosotros mismos escribimos la configuración de lanzamiento para ese proceso.
chat, build, test, e invoke en proceso por defecto son diferentes: se ejecutan dentro del proceso bxAgents ya iniciado, cuyo propio runtime de BoxLang - y por lo tanto su home - se resolvió antes de que cualquiera de nuestro código de BoxLang, incluyendo el propio main() de ModuleConfig.bx, tuviera oportunidad de ejecutarse (el motor tiene que existir para interpretar ese código en absoluto). BoxRuntime es un singleton a nivel de JVM; su home está fijado en la primera inicialización, así que nada que una clase de verbo haga desde dentro de ese proceso ya en ejecución puede cambiarlo retroactivamente.
SÍ hay una palanca real para esto, sin embargo: BoxRunner (el propio punto de entrada de CLI de núcleo de BoxLang, no solo el miniserver) lee una variable de entorno BOXLANG_HOME genuina antes de que BoxRuntime se inicialice - confirmado directamente contra el jar de runtime real (no solo la documentación): ejecutarlo con BOXLANG_HOME=<path> configurado como una variable de entorno de SO real (las rutas relativas se resuelven contra el CWD, igual que una ruta absoluta) puebla la estructura de home completa en esa ruta en lugar de ~/.boxlang, cada vez. new genera un .env que declara BOXLANG_HOME=.build/runtime - la misma ruta que usa serve - exactamente por esta razón (reflejando la propia convención .env/BOXLANG_HOME de ortus-boxlang/bx-ai-intro), en la raíz del proyecto - es decir, el directorio desde el que un usuario realmente ejecuta bxAgents <verb> para los verbos en proceso, que es el lugar correcto para que un cargador de .env basado en CWD lo encuentre.
Lo que confirmamos, y lo que no pudimos. boxlang-miniserver (lo que lanza serve) sí tiene carga automática de .env real e incorporada - confirmado descompilando y luego realmente ejecutando ortus.boxlang.web.MiniServer directamente: con ningún envFile configurado, resuelve .env en relación a el webRoot del servidor (no la raíz del proyecto) y, si se encuentra, lo carga como Java Properties, aplicando cada clave vía System.setProperty() - los valores reales cargados de esta manera SÍ son visibles para el código de BoxLang vía getSystemSetting(). El jar de runtime de núcleo crudo (BoxRunner, usado por chat/build/test/invoke por defecto) no tiene lógica equivalente en ninguna parte de él (confirmado haciendo grep en cada clase del jar en busca de .env) - así que esos verbos solo captan .env si la propia CLI boxlang que tienes instalada (un lanzador nativo provisto por BVM, no este jar crudo) hace su propia carga de .env antes de que la JVM arranque, de la manera en que ortus-boxlang/bx-ai-intro depende de ello. Eso es plausible y consistente con el uso en el mundo real de ese proyecto, pero no es algo que este sandbox tenga el binario real para verificar directamente.
Una peculiaridad específica y confirmada incluso donde sí ocurre la carga de .env: BOXLANG_HOME mismo no tiene efecto a menos que sea una variable de entorno de SO real - no una propiedad de sistema de la JVM, y no un flag -D. Verificado dos veces directamente contra los jars reales: (1) ejecutar boxlang-miniserver contra un webRoot cuyo .env declaraba BOXLANG_HOME=customhome cargó el archivo (confirmado vía su propia línea de log "Loaded environment variables from:" y vía getSystemSetting() devolviendo correctamente otros valores de .env), sin embargo el servidor aún registraba Logs Directory: /root/.boxlang/logs - el home por defecto, no customhome; (2) lanzar BoxRunner directamente con -DBOXLANG_HOME=<path> (una propiedad de sistema de la JVM, sin .env involucrado) tampoco tuvo efecto en el home resuelto, aunque getSystemSetting( "BOXLANG_HOME" ) devolvía felizmente el valor del flag desde código BoxLang. Así que la resolución de BOXLANG_HOME específicamente solo lee la variable de entorno de SO verdadera - a diferencia de la mayoría de los ajustes, que getSystemSetting() devuelva un valor para ella no significa que el home del runtime realmente se haya movido. Si tu CLI de boxlang carga .env de la misma manera en que lo hace internamente MiniServer (System.setProperty después de que la JVM ya ha arrancado) en lugar de exportar una variable de entorno real antes de lanzar la JVM, BOXLANG_HOME en .env no alcanzará el home del runtime aunque .env mismo se cargue con éxito - todo lo demás en él seguirá funcionando. Si tu instalación no te da esto en absoluto, cárgalo tú mismo antes de ejecutar comandos (por ejemplo, set -a; source .env; set +a) para obtener el mismo aislamiento que serve ya obtiene incondicionalmente.
El paso de conveniencia box install de new no está ejercitado por una prueba automatizada
new ejecuta box install dentro de la carpeta tests/ generada por defecto, así que bxAgents test funciona de inmediato (ver Referencia de CLI). Esta es una operación de red real (CommandBox resuelve testbox contra ForgeBox) - confirmada de tomar ~25 segundos y fallar con un error de certificado en este sandbox de desarrollo específicamente, la misma restricción de ForgeBox-inalcanzable ya señalada en otro lugar del propio tooling de este proyecto. NewSpec.bx solo ejercita el camino rápido y determinista --skipInstall (y las invocaciones de new de ModuleCliProcessTest.java de proceso de SO / ModuleConfigCliSpec.bx en proceso también pasan --skipInstall por la misma razón) - el propio camino de intento de instalación por defecto se verifica solo por pruebas manuales, no por CI.
chat necesita una TTY real
chat usa el propio MiniConsole de BoxLang, que invoca stty para configurar el modo raw de terminal - solo puede ejecutarse contra una terminal interactiva genuina. No funciona canalizado, redirigido, ni desde un proceso no interactivo (un job de CI, un script). No hay modo de respaldo no interactivo.
Corregido: chat e invoke por defecto (sin --server) solían fallar para un Agent.bx basado en clase
Anteriormente, tanto chat como invoke por defecto lanzaban The requested class [agent.classes.agentClass] has not been located in any class resolver. antes de siquiera alcanzar al agente. Causa raíz: ambos verbos cargan el GeneratedAgentFactory.bx generado en proceso vía DynamicClassLoader.instantiate() (sin contenedor ColdBox involucrado), y la factory generada solía instanciar un Agent.bx basado en clase vía una búsqueda de ruta con puntos relativa, que solo se resuelve una vez que algo ha registrado un mapeo que hace resoluble la raíz de la app - y nada lo hacía fuera de un arranque real de ColdBox. Registrar uno a mitad de script tampoco lo corrige (confirmado empíricamente).
Corrección: ColdBoxAppGenerator.copyAgentClass() ahora devuelve la propia ruta de archivo absoluta de la clase copiada, y renderClassBasedAgentStatement() la instancia vía DynamicClassLoader.instantiate( absolutePath, context ) - la misma primitiva que chat/invoke ya usan para cargar GeneratedAgentFactory.bx mismo - en lugar de un new "..."() relativo. Confirmado contra examples/class-based-agent/: chat, invoke por defecto, invoke --server, y serve todos construyen y ejecutan correctamente el mismo agente.
Ninguna prueba de humo de instalación del executable de box.json
box.json declara "boxlang": { "executable": "bxAgents" } así que una instalación real de módulo produce un comando bxAgents nativo (ver Instalación). Este cableado en sí no está ejercitado por una prueba automatizada - depende del propio comportamiento documentado del instalador de módulos de BoxLang para generar wrappers ejecutables, verificado leyendo su código fuente, no por una prueba de instalar-y-ejecutar en el propio CI de este repositorio.
schedules/Scheduler.bx no se valida en tiempo de build
Ya que es código real, escrito a mano, de ColdBox pasado sin tocar (ver schedules/), build no puede comprobarlo de manera significativa de la forma en que solía comprobarse la configuración { cron, action } - un error de sintaxis, un typo en una llamada getInstance( "..." ), o referenciar un nombre de agente que no existe, todo pasa build limpiamente y solo sale a la superficie cuando la app generada realmente arranca (serve), igual que cualquier otra clase real de BoxLang cuyo contenido este proyecto no posee. build sí atrapa un error más estrecho y adyacente: dos agentes (raíz o subagente, a cualquier profundidad) declarando el mismo name - eso es una colisión de binding de config/WireBox.bx que este proyecto genera él mismo, así que se comprueba en tiempo de validación igual que todo lo demás que genera.
Corregido: un bug real en el verbo test - resolver "el jar actual de BoxLang" era ambiguo
TestRunnerService.bx genera un proceso hijo para ejecutar los tests/specs de un proyecto, y necesita saber con qué jar lanzarlo. El truco original de "de qué jar se cargó esta clase" se rompió una vez que boxlang-miniserver-*.jar (un fat jar que empaqueta su propia copia de BoxRuntime) también estaba en el classpath - el classloader podía resolver al jar del miniserver en su lugar, lanzando silenciosamente MiniServer.main (que sale con 1 inmediatamente) en lugar de BoxRunner.main. Reproducible solo a través de la suite completa testBx, no de forma aislada, lo cual es lo que hizo fácil pasarlo por alto.
Corregido resolviendo el jar desde java.class.path en su lugar - escaneando en busca de una entrada boxlang-*.jar que no contenga miniserver, recayendo en el viejo truco de codeSource solo si no se encuentra tal entrada.
Los destinos ssh/docker/digitalocean de deploy no están ejercitados por una prueba automatizada de proceso real
SshTargetSpec.bx/DockerTargetSpec.bx afirman sobre el comando scp/ssh/docker exacto que construye cada destino (un array de argumentos real de ProcessBuilder) sin invocar jamás el binario real - el mismo enfoque de "capturar, no ejecutar" usado en otra parte de esta suite para cualquier cosa que necesite un binario que podría no estar en PATH en CI (ver los saltos de assumeTrue de MiniServerLauncherTest). La lógica de copia de local SÍ está ejercitada de verdad (LocalTargetSpec.bx), incluyendo una prueba de regresión para un bug latente real que este refactor corrigió: el Deploy.bx original elegía el .bxa "más nuevo" vía un ordenamiento léxico de nombre de archivo, que silenciosamente elige mal una vez que un proyecto alcanza versiones de dos dígitos (v9.0.0 ordena después de v10.0.0) - corregido ordenando por el tiempo de modificación de archivo real (DistArtifactLocator).
DigitalOceanTargetSpec.bx de forma similar solo hace pruebas unitarias de la lógica pura de buildAppSpec()/findExistingAppId() - las llamadas reales GET/POST /v2/apps nunca se ejercitan en CI, ya que eso necesita una cuenta real de DigitalOcean y un token de API. Haz al menos un deploy --name=<ssh-entry> manual contra una VM desechable real, y un deploy --name=<digitalocean-entry> contra una cuenta real de DO con una app desechable, antes de depender de ninguno de los dos en producción - el mismo enfoque honesto ya usado arriba para el vacío de pruebas solo-con-proveedor-mock.
Los destinos ftp/sftp de deploy: el manejo de conexión real está probado, una carga exitosa real no
A diferencia de ssh/docker (que invocan binarios externos y así solo pueden tener su construcción de comando probada sin un servidor real), ftp/sftp llaman al componente bx:ftp real del módulo bx-ftp en proceso - no hay opción de "capturar el comando, no ejecutar". BaseFtpTargetSpec.bx en su lugar hace un intento de conexión genuino contra 127.0.0.1 en un puerto en el que nada escucha, y afirma que el error real de conexión rechazada se captura y se relanza como un BxAgents.DeployFailed claro (confirmado vía el propio código fuente de bx-ftp que cada acción lanza en caso de fallo en lugar de devolver un succeeded: false suave). Esto prueba el camino real de conectar/envolver-error/limpiar de extremo a extremo - lo que no puede probar es una carga exitosa real.
Ese vacío es específicamente porque este sandbox de desarrollo no tiene salida TCP cruda alguna - solo HTTPS a través del proxy del entorno (confirmado directamente: tanto curl ftp://test.rebex.net como una conexión cruda /dev/tcp a un host FTP público colgaron y expiraron por tiempo de espera, y docker info no muestra ningún daemon corriendo, así que los propios servidores de prueba FTP/SFTP de Docker que trae bx-ftp tampoco pudieron iniciarse aquí). Esta es una restricción del sandbox, no una limitación de código - haz al menos un deploy --name=<ftp-entry> y un deploy --name=<sftp-entry> manuales contra un servidor real alcanzable (o los propios servidores de prueba docker-compose up de bx-ftp, desde una máquina que tenga acceso a Docker/red) antes de depender de ninguno de los dos en producción, el mismo enfoque honesto ya usado arriba para ssh/digitalocean.
build no revierte un .build/app parcialmente escrito ante un fallo a mitad de generación
BuildPipeline.build() elimina y recrea .build/app de antemano, luego ejecuta los generadores de la Fase 5 en secuencia. Cada entrada que la Fase 3 (ProjectValidator) puede comprobar se comprueba antes de que nada de eso ocurra, así que un fallo genuino a mitad de generación debería ser raro en la práctica - pero si aun así ocurre uno (por ejemplo, una entrada de models//schedules//mcp/ que falla al cargar, o un problema de entorno/sistema de archivos), .build/app queda en disco en un estado parcialmente escrito en lugar de restaurarse a su contenido previo o limpiarse. Un build exitoso posterior lo sobrescribe limpiamente, así que esto no es pegajoso, pero cualquier cosa que inspeccione .build/app entre un build fallido y el siguiente (un paso de CI, un reintento manual de package) puede ver una app rota, medio generada. Todavía no hay un paso de rollback/directorio-temporal-luego-intercambio.
Se observó un StackOverflowError intermitente en testBx, no relacionado con el propio código de este proyecto
Mientras se investigaba este hito, ./gradlew testBx ocasionalmente (no en cada ejecución) estrellaba toda la JVM con un StackOverflowError dentro de la propia serialización JSON genérica de objetos del motor de BoxLang (DynamicObjectSerializer/BoxStructSerializer llamándose mutuamente, alternando, hasta que se agota la pila - confirmado que sigue ocurriendo con -Xss16m, así que es un ciclo genuino, no meramente una estructura profunda pero finita). Aislado vía git stash (el fallo se reprodujo idénticamente sin ninguno de los cambios de esta sesión aplicados, contra el mismo commit exacto ya en development) y vía bisección de tests/specs/** en cada subdirectorio tanto individualmente como combinados (cada subconjunto, e incluso cada subconjunto-menos-uno, corrió limpio) - solo la única ejecución completa lo reproduce ocasionalmente, y volver a ejecutar la misma suite completa inmediatamente después de un fallo a veces pasa limpio. Esto apunta a una condición de carrera dependiente del tiempo (interactuando plausiblemente con la propia tarea everySecond() en segundo plano real de ExampleScheduler, que imprime desde su propio pool de hilos concurrentemente con cualquier spec que esté ejecutándose en ese momento) más que a un bug en algún spec particular o en el propio código generado de este proyecto. Si testBx falla con un StackOverflowError y ninguna otra explicación, reinténtalo antes de asumir una regresión real - actualmente no es reproducible a voluntad, así que no existe ninguna prueba de regresión automatizada para ello, y llevarlo hasta una única causa raíz estaba fuera del alcance de esta ronda.
Gateways de estilo push (Telegram) probados solo contra un seam mockeado de API/scheduler - ninguna integración de plataforma en vivo corre en CI
TelegramGatewaySpec.bx ejercita la propia lógica de TelegramGateway (normalización entrante, fragmentación saliente en el límite de 4096 caracteres, construcción de teclado inline HITL, registro/eliminación de tarea de scheduler) enteramente contra un seam de prueba inyectable apiCaller/setScheduler() - nunca una llamada real a la API de bots de Telegram, y nunca un arranque real de scheduler de ColdBox. Esto prueba que el propio código del gateway es correcto, no que realmente funciona de extremo a extremo contra la API real de Telegram o un scheduler real en ejecución. Haz al menos un bxAgents serve manual contra un proyecto con un bot de Telegram real respaldado por botTokenEnvVar antes de depender de él en producción - el mismo enfoque honesto ya usado arriba para los vacíos de pruebas solo-con-proveedor-mock/sin-conexión-en-vivo en otras partes de este archivo. La misma advertencia se aplicará a cada futuro gateway de estilo push (Slack, Discord, Email, WhatsApp) construido de la misma manera.
SlackGatewaySpec.bx lleva el mismo vacío, un nivel más profundo: la conexión websocket persistente de SlackGateway se prueba vía un seam inyectable setSocketOpener() (un objeto falso que sustituye al java.net.http.WebSocket real), así que cada aserción de manejo de frame/lógica de reconexión en el spec corre con cero E/S de red real. Lo que sí se verificó genuinamente de forma directa (no mockeado): una prueba de humo independiente instanció el SlackSocketListener(gateway) real (que implements="java:java.net.http.WebSocket$Listener" directamente - BoxLang lo compila como un implementador de JVM genuino, sin necesidad de proxy) y llamó al HttpClient.newWebSocketBuilder().buildAsync(...) real contra una dirección inalcanzable, confirmando que la propia interoperabilidad de BoxLang a Java funciona correctamente hasta la frontera de red (falló con un simple java.net.ConnectException, no un error de casting/interoperabilidad) - pero ninguna prueba aquí ha completado jamás un handshake real de Socket Mode contra los servidores reales de Slack. Haz al menos un bxAgents serve manual contra un proyecto con credenciales reales de app de Slack respaldadas por botTokenEnvVar/appTokenEnvVar antes de depender de él en producción.
DiscordGatewaySpec.bx lleva el vacío idéntico, por la razón idéntica: la lógica de manejo de frame/latido/reconexión de DiscordGateway se ejercita enteramente contra seams inyectables setApiCaller()/setSocketOpener(), cero E/S de red real. La misma disciplina de prueba de humo independiente se aplicó aquí también - gateway.onConnect() conducido contra una llamada real de HttpClient.newWebSocketBuilder().buildAsync(...) a una dirección inalcanzable falló con un simple java.net.ConnectException, no un error de casting/interoperabilidad, confirmando que la cadena de interoperabilidad funciona. Lo que NO se verificó: un handshake real de Gateway (Hello → Identify → READY) contra los servidores reales de Discord, temporización real de latido bajo la propia tolerancia de Discord, o que el valor por defecto de intents (GUILDS+GUILD_MESSAGES+DIRECT_MESSAGES+MESSAGE_CONTENT = 37377) sea realmente suficiente para recibir contenido de mensaje una vez que MESSAGE_CONTENT está habilitado/aprobado para un bot real en el Discord Developer Portal. Haz al menos un bxAgents serve manual contra un proyecto con un bot real de Discord respaldado por botTokenEnvVar (con MESSAGE_CONTENT habilitado) antes de depender de él en producción.
EmailGatewaySpec.bx lleva una versión mayor del mismo vacío. El IMAP entrante se prueba enteramente vía un seam inyectable setImapPoller() (structs de mensaje normalizados enlatados, sin buzón real), y el saliente se prueba enteramente vía un seam inyectable setMailService() (FakeMailService/FakeMail, sustituyendo a MailService@cbmailservices - EmailGateway nunca pasa por WireBox real en absoluto en estos specs, ya que no hay arranque real de ColdBox). Lo que sí se verificó genuinamente de forma directa esta sesión (no mockeado, no asumido): la superficie real de API jakarta.mail de la que depende fetchInboundMessages() (Session.getDefaultInstance(), Flags/Flags.Flag/FlagTerm, Store.getStore("imaps"), Folder.READ_WRITE, MimeMultipart, InternetAddress) contra los jars reales de jakarta.mail-api/Angus Mail (descargados de forma independiente para esto - no están vendidos en el propio classpath de prueba de este repositorio), confirmando que cada nombre de clase/método usado realmente existe y se resuelve; un Store.connect() real contra una dirección inalcanzable, conducido a través del propio EmailGateway.pollInbox() (no un helper omitido), falló con un simple error de tiempo de espera de conexión, no un error de interoperabilidad/casting, confirmando que la cadena de interoperabilidad alcanza correctamente la frontera de red real, la misma disciplina que las pruebas de humo de websocket de Slack/Discord. Lo que explícitamente NO se verificó, y es un vacío estrictamente mayor que el de los gateways de plataforma de chat: ningún handshake IMAP real contra un buzón real, ningún módulo real cbmailservices/bx-mail instalado en ninguna parte de este repositorio o su arnés de pruebas (ninguno está vendido de la forma en que lo están bx-ai/TestBox), así que el camino de resolución de WireBox (MailService@cbmailservices realmente existiendo, el bx:mail de BXMail realmente enviando) nunca se ha ejercitado en absoluto, mockeado o real, en este código base. Haz al menos un bxAgents serve manual contra un proyecto con credenciales IMAP reales Y una instalación real de cbmailservices/bx-mail (confirma que box install tuvo éxito y que moduleSettings.cbmailservices se resuelve) antes de depender de este gateway en producción - este es el menos verificado de los cuatro gateways de estilo push enviados hasta ahora.
WhatsAppCloudGatewaySpec.bx cubre la propia lógica del gateway de forma exhaustiva y real, no mockeada: el camino de verificación de firma se ejercita con firmas HMAC-SHA256 genuinamente computadas (javax.crypto.Mac/SecretKeySpec, verificadas de forma cruzada e independiente esta sesión tanto contra openssl dgst -hmac como contra el propio módulo hmac de Python antes de confiar en el cómputo de BoxLang - se atrapó una discrepancia real de vector de referencia que resultó ser un typo en el valor esperado copiado a mano, no un bug, pero solo la verificación cruzada lo atrapó), el handshake de verificación, el despacho/deduplicación de webhook, el envío saliente, y el renderizado de botón/lista interactivo se conducen todos a través de los métodos públicos reales del gateway con solo la propia llamada HTTP saliente a la API de Graph siendo stubbeada (setApiCaller()). Lo que NO se verificó: las propias llamadas de contexto de request de ColdBox del handlers/WhatsAppCloud.bx generado (event.getHTTPContent()/event.getHTTPHeader()/event.renderData(), el acceso a parámetros de query de clave con puntos fusionada en el scope URL de rc para el handshake GET) contra un arranque real de ColdBox - estos SÍ son los idiomas documentados y estándar de handler REST de ColdBox (confirmados contra la propia receta de documentación "Building REST APIs" de ColdBox, no adivinados), un punto de partida significativamente más confiable que la suposición de clave aiGatewayRegistry() no documentada que resultó estar equivocada en otra parte de este archivo - pero "documentado" no es lo mismo que "probado que funciona en este contexto generado" - se difirió en lugar de arriesgar ese radio de impacto para lo que es un vacío de menor confianza-de-estar-realmente-equivocado que el bug de clave de registro. Haz al menos un bxAgents serve real + una prueba real de webhook de Meta (o curl) contra /webhooks/whatsapp-cloud antes de depender de esta ruta en producción. Tampoco se ha hecho jamás una llamada real a la API de Graph - la capa HTTP de deliver()/requestHumanInteraction() se ejercita solo vía el seam de prueba apiCaller.
TeamsGatewaySpec.bx cubre la propia lógica del gateway de forma exhaustiva y real, no mockeada: la verificación de JWT se ejercita contra un par de claves RSA de 2048 bits genuinamente generado (java.security.KeyPairGenerator) y JWTs de prueba firmados a mano construidos enteramente dentro del spec (sin fixtures precomputadas, sin dependencia externa de openssl en tiempo de prueba) - una firma válida se acepta y despacha, mientras que una firma manipulada, un aud incorrecto, un iss incorrecto, y un exp expirado se confirman de forma independiente cada uno como rechazados con 401. El camino de actividad de invocación (clic de botón de Adaptive Card), el despacho de mensajes, el filtrado exclusivo de scope personal, el enhebrado vía replyToId, la fragmentación, y el renderizado de Adaptive Card se conducen todos a través de los métodos públicos reales del gateway con solo la propia llamada REST de Connector saliente siendo stubbeada (setApiCaller()) y las obtenciones de JWKS/token OAuth2 siendo stubbeadas (setJwksFetcher()/setTokenFetcher()). Lo que NO se verificó: las propias llamadas de contexto de request de ColdBox del handlers/Teams.bx generado contra un arranque real de ColdBox (la misma categoría de vacío que el propio handler de WhatsApp Cloud, y diferido por la razón idéntica - ver esa entrada arriba); nunca se ha hecho una obtención real de token OAuth2 ni una llamada REST de Connector real contra los endpoints reales de Microsoft; y la compensación de JWKS-cacheado-por-la-vida-de-la-instancia (ver docs/conventions/gateways/teams.md) significa que un escenario real de rotación de claves tampoco se ha ejercitado jamás. Haz al menos un bxAgents serve real + un registro real de app de Teams (App ID/contraseña del portal Azure/Bot Framework) + un cliente real de Teams enviando un DM antes de depender de este gateway en producción.
TwilioGatewaySpec.bx cubre la propia lógica del gateway de forma exhaustiva y real, no mockeada: el camino de verificación X-Twilio-Signature HMAC-SHA1/base64 se ejercita con una firma genuinamente computada construida en línea en el spec, verificada de forma cruzada e independiente esta sesión contra los propios módulos hmac/hashlib de Python antes de confiar en la implementación de BoxLang (la misma disciplina que la propia verificación cruzada HMAC-SHA256 de WhatsApp Cloud) - una firma de referencia real se computó en Python para una combinación conocida de token de autenticación/URL/parámetros y se confirmó que coincide exactamente con la salida de BoxLang. El análisis de cuerpo con codificación de formulario (incluyendo un + literal viajando correctamente de ida y vuelta a través de la codificación de porcentaje %2B), el override de publicUrl para despliegues de proxy inverso/túnel, el modelo dual de TwiML-ack-luego-respuesta-REST-asíncrona, la fragmentación de salida, y la correlación de respuesta HITL indexada por número de teléfono se conducen todos a través de los métodos públicos reales del gateway con solo la propia llamada HTTP saliente a la API de Messages siendo stubbeada (setApiCaller()). Lo que NO se verificó: la propia llamada event.getUrl() del handlers/Twilio.bx generado contra un arranque real de ColdBox (la misma categoría de vacío que los propios handlers de WhatsApp Cloud/Teams, diferido por la razón idéntica) - event.getUrl() es un método documentado de Routable/Request Context de ColdBox (confirmado vía el MCP de documentación de ColdBox), no una suposición, pero "documentado" no es "probado en este contexto generado". Tampoco se ha hecho jamás una llamada real a la API de Messages de Twilio. Haz al menos un bxAgents serve real + una prueba real de webhook de número de teléfono de Twilio antes de depender de esta ruta en producción - y nótese que la correlación HITL indexada por número de teléfono (ver docs/conventions/gateways/twilio.md) tiene una limitación real y documentada: una segunda solicitud HITL al mismo número de teléfono antes de que se responda la primera sobrescribiría la entrada pendingApprovals de la primera, huerfanándola silenciosamente. Tampoco hay lista blanca/limitación de tasa construida para SMS entrante - a diferencia de Eve, que documenta (pero no aplica en código) que su propia configuración allowFrom es "obligatoria", este puerto no tiene ninguna compuerta equivalente en absoluto; cualquier número de teléfono puede enviar mensajes a un número desplegado de Twilio y alcanzar al agente.
GitHubGatewaySpec.bx cubre la propia lógica del gateway de forma exhaustiva y real, no mockeada, y la lógica central de este gateway se condujo además a través de una prueba de humo independiente de BoxLang real durante el desarrollo (no solo el spec permanente) - así fue como se atrapó un bug genuino antes de que jamás llegara a la suite de pruebas: la lógica de subcadena del helper de extracción de mención llamaba a left( body, 0 ) cada vez que la @mention ocurría justo al comienzo de un comentario (un caso muy común), y el left() de BoxLang lanza "Count cannot be zero" en lugar de devolver una cadena vacía para un conteo de cero - confirmado disparándolo con un cuerpo de comentario real, luego corregido ramificando explícitamente alrededor del caso de longitud cero en lugar de asumir que left()/mid() toleran eso. La verificación de X-Hub-Signature-256, la compuerta de regex-lookahead de @mention (confirmada vía una prueba de humo dedicada de que un bot llamado mybot NO se dispara en @mybot2), las protecciones contra bucle de bot, la deduplicación de id de entrega, la identidad de conversación de issue-vs-hilo-de-review, deliver(), y la correlación HITL de @mention-a-respuesta se conducen todos a través de los métodos públicos reales del gateway con solo la propia llamada REST de GitHub saliente siendo stubbeada (setApiCaller()). Lo que NO se verificó: el handlers/GitHub.bx generado contra un arranque real de ColdBox (la misma categoría de vacío que el propio handler de cada otro gateway de webhook en este proyecto, diferido por la razón idéntica). Nunca se ha hecho una llamada real a la API de GitHub, y nunca se ha usado una GitHub App/PAT real contra un repositorio real - haz al menos un bxAgents serve real + un webhook real de GitHub configurado contra un repositorio de prueba antes de depender de este gateway en producción.
SignalGatewaySpec.bx cubre la propia lógica del gateway de forma exhaustiva y real, no mockeada: el análisis JSON-RPC/SSE de handleSseEvent() (manejo de línea en blanco/JSON inválido, filtrado de mensaje de grupo, enhebrado de cita, respaldo a sourceUuid cuando falta un nombre para mostrar), la forma de envío y fragmentación de deliver(), y la correlación/coincidencia de decisión HITL se conducen todos a través de los métodos públicos reales del gateway con solo las llamadas de E/S salientes rpcCaller/connector siendo stubbeadas, la misma disciplina de prueba de seam que cada otro gateway en este proyecto. Dos hallazgos de nivel BoxLang salieron a la superficie durante el desarrollo, ambos resueltos y vale la pena registrarlos como peligros generales en lugar de bugs específicos del gateway: (1) un script de prueba de humo desechable que nombró una variable local request estaba interactuando silenciosamente con el propio scope reservado request de BoxLang en lugar de crear una variable simple, produciendo errores engañosos de "método no encontrado"/"discrepancia de tipo de argumento" desde HttpClient.send() que se veían exactamente como una limitación genuina de interoperabilidad Java pero desaparecían por completo una vez que se renombró la variable - SignalGateway.bx mismo nunca tuvo un bug; (2) un try/catch colocado directamente en el nivel superior de un script de prueba de humo .bxs independiente (no dentro de una función) disparó un java.lang.VerifyError: Inconsistent stackmap frames, una limitación real de verificación de bytecode del compilador de scripts de nivel superior de BoxLang, rastreada vía el stack trace hasta la propia clase generada del script de prueba, no SignalGateway.bx - corregido envolviendo el try/catch dentro de una función nombrada en su lugar. Lo que NO se verificó, y es el vacío más grande entre todos los gateways de estilo push enviados hasta ahora: nunca ha habido un daemon real de signal-cli disponible en este entorno, así que todo el ciclo de vida de conexión SSE asíncrona - abrir el stream vía HttpClient.sendAsync()+BodyHandlers.ofLines(), el bucle de retroceso exponencial de reconexión contra una conexión genuinamente inestable, el watchdog de inactividad de 30s/120s forzando una reconexión, y un round-trip JSON-RPC en vivo - nunca se ha ejercitado de extremo a extremo, solo probado por humo a nivel de plomería de interoperabilidad (una prueba independiente alcanzó un java.net.ConnectException real contra una dirección inalcanzable, probando que la cadena es sólida, no que funciona contra un daemon en vivo). Haz al menos un bxAgents serve manual contra un proyecto con un daemon real y en ejecución de signal-cli y una cuenta real vinculada de Signal antes de depender de este gateway en producción - esta es una arquitectura de transporte genuinamente nueva en este código base (el único gateway basado en SSE entre Telegram/Slack/Discord/Email/Signal), no solo una nueva plataforma sobre una forma de transporte ya probada.
WhatsApp Personal (puente no oficial de cuenta personal) - investigado, no construido
El plan original (igualando la propia arquitectura de Hermes Agent) pedía un WhatsAppPersonalGateway construido generando un subproceso de Node.js ejecutando @whiskeysockets/baileys (el cliente de protocolo multi-dispositivo de WhatsApp Web que el propio Hermes usa - licenciado MIT, su bridge.js completo se leyó directamente del código fuente real de Hermes, no resumido). Ese enfoque se dejó de lado a mitad de sesión por instrucción directa de preferir una integración nativa BoxLang/JVM sobre un puente de subproceso, y de usar una biblioteca Java nativa solo si es de código abierto y ni GPL ni LGPL.
Esa búsqueda encontró Cobalt (com.github.auties00:cobalt, antes WhatsappWeb4j) - una implementación Java real, licenciada MIT, mantenida activamente (900+ estrellas) del protocolo de "cliente vinculado" multi-dispositivo de WhatsApp, con una API fluida documentada (WhatsAppClient.builder().linkedApi().webClient()..., addNewMessageListener(), sendMessage()) que coercionaría limpiamente desde un closure de BoxLang vía la propia coerción SAM-de-Java documentada de BoxLang (confirmado vía el MCP de documentación de BoxLang - no se necesita createDynamicProxy() para un listener de método único abstracto). Dos bloqueos reales salieron a la superficie durante la verificación, no adivinados:
- La primera lectura de pom.xml (rama
masterde Cobalt, una reescritura multi-módulo en progreso) requiere Java 25 - dos versiones mayores por delante de la propia línea base documentada de BoxLang (Java 21+, confirmado vía el MCP de documentación de BoxLang y coincidiendo con el propio JDK21.0.10de este proyecto). Al volver a comprobar contra el artefacto realmente publicado en Maven Central (cobalt:0.0.10, lo real que una<dependency>resolvería hoy, no la reescritura no publicada) se mostró<java.version>21</java.version>- así que el hallazgo de Java-25 fue una falsa alarma causada por leer la rama equivocada, no un bloqueo real. Vale la pena registrarlo como precaución: elpom.xmlde rama por defecto de un repositorio de GitHub no es necesariamente lo que está en Maven Central. - El
cobalt:0.0.10real y publicado traecom.aspose:aspose-wordscomo una dependencia dura de tiempo de compilación (usada internamente para generar miniaturas de vista previa de enlaces a partir de documentos Word) - Aspose.Words for Java es de licencia comercial/propietaria, no de código abierto, así que empaquetarlo violaría la misma restricción de licencia que Cobalt mismo fue elegido para satisfacer. El grafo de dependencias completo (~15 jars: zxing, qr-terminal, curve25519, protobuf-base, jackson o fastjson2 dependiendo de la versión, libphonenumber, dd-plist, apk-parser, link-preview, jaffree, ez-vcard, slf4j, más Aspose) todos necesitarían descargarse y empaquetarse manualmente bajo la carpetalibs/de este módulo - los módulos de BoxLang no tienen resolución de dependencias de estilo Maven propia (confirmado vía el MCP de documentación de BoxLang: los jars de terceros se empaquetan directamente en la carpetalibs/de un módulo, cargados por un classloader por módulo - no hay clavejavaLibrariesenbox.jsonque resolvería un árbol de dependencias automáticamente).
Dada la contaminación de licencia vía Aspose y el esfuerzo de ensamblaje manual de fat-jar sin herramientas de resolución de dependencias disponibles para verificar que el resultado realmente carga, WhatsApp Personal fue descartado del alcance en lugar de enviarse como un gateway real o un stub. validGatewayTypes de ProjectValidator y TYPE_CLASS_MAP de GatewayGenerator no incluyen una entrada whatsapp-personal - un proyecto que intenta declarar uno obtiene el error de validación existente de "tipo de gateway desconocido", igual que cualquier otro tipo no soportado, en lugar de un stub medio construido engañoso. Revisitar esto es razonable si Cobalt alguna vez elimina la dependencia de Aspose (es una funcionalidad estrecha - miniaturas de vista previa de enlaces de documentos Word - no central para la mensajería), o si una sesión futura decide que el enfoque de puente de subproceso Node/Baileys (rechazado en esta ronda por preferencia de arquitectura, no por un bloqueo técnico) es preferible después de todo.
GatewaySession es de todo el proyecto y solo de agente raíz (v1)
Un proyecto con al menos una entrada de gateway de estilo push obtiene exactamente un GatewaySession generado, agrupando cada gateway de estilo push y siempre vinculado al agente raíz del proyecto - coincidiendo con el precedente existente de que la exposición HTTP exposes: "agent" también es siempre solo-de-agente-raíz (ver gateways/).
Esto es la arquitectura, no una carencia. Un proyecto tiene un único agente raíz, y a los subagents/ se les delega desde ese raíz - el raíz decide qué va a quién. Por eso todo punto de entrada (gateways, la ruta HTTP del agente, la web UI, los schedules) llega al raíz por diseño. Vincular un gateway directamente a un subagente esquivaría precisamente al agente que debe orquestarlo, y por eso no existe una clave targetAgent por gateway: para lograr "Telegram habla con soporte, Slack habla con investigación", esa lógica de enrutado va en el raíz, que delega, en lugar de partir la puerta de entrada.
Corregido: GatewaySessionBootstrap.bx buscaba gateways por la clave incorrecta de aiGatewayRegistry()
Un bug real, previamente enviado: el interceptor generado buscaba gateways por el propio nombre de archivo de la entrada gateways/* descubierta (por ejemplo, "telegramChannel"), pero el GatewayRegistry.register() de bx-ai siempre indexa por el propio getName() fijo de la clase del gateway (por ejemplo, "telegram") - nunca nada suministrado por el llamador. Esto significaba que la construcción de GatewaySession lanzaba una excepción en el arranque de ColdBox para cada proyecto generado con un gateway de estilo push; se envió sin detectarse porque la única cobertura previa afirmaba sobre el texto del archivo generado, nunca contra un registro en vivo. Corregido en GatewayGenerator.generate(): el interceptor ahora busca gateways por su cadena de TIPO, deduplicada, con una prueba de regresión permanente (GatewayGeneratorSpec.bx) que registra un gateway real y prueba que la clave emitida lo resuelve vía un aiGatewayRegistry() en vivo.
Una consecuencia real y permanente que esta corrección expone (sigue siendo cierta hoy, no es un bug): porque el registro está indexado por tipo, no por entrada, dos entradas gateways/* del mismo tipo de estilo push colisionan en el mismo slot de registro a nivel de todo el proyecto - por ejemplo, dos entradas type: "telegram" (dos tokens de bot diferentes) silenciosamente harían que el segundo registro sobrescriba al primero, y GatewaySession solo vería alguna vez uno de ellos. No hay un alias/override de nombre de registro por entrada hoy. Una instancia por tipo de estilo push, por proyecto, es el verdadero techo de v1.
Corregido: el snapshot publicado de bx-ai solía retrasarse respecto a su propia rama development
Mientras la API aiGatewayRegistry()/GatewaySession/BaseGateway de bx-ai y su IAiMemory.summarize() delimitado por userId/conversationId (necesario para /compact) todavía no se habían publicado, este proyecto fijó bx-ai@3.4.0-snapshot - un artefacto de ForgeBox continuamente republicado que repetidamente se retrasaba respecto al HEAD real de la rama development de bx-ai por alguna cantidad de tiempo. Tres veces separadas, un downloadModules reciente obtuvo un snapshot anterior a la API que el propio código generado de este proyecto ya llamaba, rompiendo el arranque de gateway (Function 'aiGatewayRegistry' not found), toda la capa de gateway (faltando BaseGateway.bx), o /compact (resumiendo silenciosamente el scope equivocado). CI lo evitaba clonando y construyendo la rama development de bx-ai directamente y superponiéndola sobre lo que fuera que downloadModules obtuviera.
bx-ai desde entonces ha cortado un release real y estable 3.4.0 que lo lleva todo - confirmado directamente contra el artefacto publicado en ForgeBox (BaseGateway.bx, GatewaySession.bx, los BIFs aiGatewayRegistry/aiGatewaySession, MockService.bx, y summarize( config, userId, conversationId ) están todos presentes). build.gradle ahora fija esa versión estable en lugar del snapshot, y el paso de superposición de CI se elimina - downloadModules solo ya es suficiente.
La interfaz web de chat v1 (exposes: "webui") - qué es real, y qué solo pudo comprobarse contra documentación, no contra un servidor en vivo
WebUiGeneratorSpec.bx y una prueba de extremo a extremo de BuildPipelineSpec.bx ambas conducen a las clases reales WebUiGenerator/BuildPipeline contra fixtures reales: el shell estático <path>/index.html se confirma que se escribe y se plantilla correctamente (los placeholders __API_BASE__/__APP_TITLE__ sustituidos, nunca dejados en la salida), el opcional interceptors/WebUiAuthGate.bx se confirma que se genera solo cuando apiKeyEnvVar está configurado, que protege exactamente <path>/api/* y nunca el shell <path> desnudo mismo, y que se registra correctamente en la lista interceptors:[...] de config/ColdBox.bx de extremo a extremo a través del BuildPipeline real. El interceptor generado también se confirmó esta sesión que compila e instancia limpiamente vía una prueba de humo independiente (cargado a través de la misma primitiva DynamicClassLoader que el propio pipeline de build usa).
Lo que NO se verificó - y no pudo, en este entorno de desarrollo: una prueba real de navegador de un bxAgents serve real de la página realmente cargando, transmitiendo una respuesta, y la compuerta X-API-Key realmente rechazando/aceptando requests sobre HTTP real. El propio arnés runColdBoxIntegrationTests.bxs/tests/coldbox de este proyecto (el mismo que probó que la ruta /invoke de toAi() funciona de extremo a extremo para http-gateway-agent) requiere que tests/coldbox esté presente vía un box install real dentro de tests/ - el acceso a red de CommandBox y ForgeBox ambos estuvieron indisponibles en el sandbox de esta sesión, así que ese arnés no pudo ejercitarse para la interfaz web (ni reconfirmarse para nada más) esta sesión. Dos consecuencias directas:
- La forma exacta de respuesta JSON de
/invokeusada por las propias páginas/documentación generadas de este proyecto ({"input": "..."}de entrada, una respuesta conteniendo"success": true) está confirmada empíricamente solo vía la propia aserción previa, ya pasando, derunColdBoxIntegrationTests.bxs(una comprobación de subcadena, no una aserción de forma completa) - no reverificada esta sesión. - El formato de cable SSE de
/streamque analiza el propio JS de la interfaz web (líneasdata: {"token":"..."}, terminadas pordata: [DONE]) se toma directamente de la propia documentación oficial "AI Routing" de ColdBox, no reconfirmada de forma independiente contra un servidor en vivo esta sesión - a diferencia de casi cualquier otra afirmación de formato de cable en la documentación de este proyecto, que fueron verificadas cruzadamente contra código real en ejecución dondequiera que fue posible (ver, por ejemplo, las verificaciones cruzadas independientes de Python/openssl de los esquemas de firma HMAC-SHA256/SHA1 en otras partes de este archivo).
Haz al menos un bxAgents serve real + una prueba real de navegador (mensaje enviado, respuesta transmitida, la compuerta X-API-Key realmente devuelve 401 a un request que carece de la clave) antes de depender de esta funcionalidad en producción - el mismo consejo permanente ya dado para el vacío de no-probado-contra-un-arranque-real de cada handler de webhook generado en otras partes de este archivo.
El almacén SQLite de la interfaz web - verificado a nivel de biblioteca, no a través de un arranque real de ColdBox
La pila qb + bx-sqlite subyacente a models/ChatDb.bx se verificó directamente esta sesión contra los jars reales, no inferida de la documentación: los propios fuentes .cfc de qb 13.1.0 compilan y corren nativamente en BoxLang 1.16 sin bx-compat-cfml; SQLiteGrammar + SchemaBuilder realmente crean las tablas e índices v1 contra un archivo SQLite real; un segundo pase de migración es un no-op limpio; QueryBuilder hace round-trip de inserciones, lecturas filtradas/ordenadas y eliminaciones; y la clave primaria compuesta de preferences realmente rechaza un (userId, prefKey) duplicado. Se encontraron dos restricciones de esta manera en lugar de asumirse - qb requiere un datasource nombrado (su propio appendSqlComments() tipa ese argumento como string, así que un struct inline lanza antes de que corra cualquier SQL) - y SchemaBuilder@qb se mapea solo con su argumento grammar, nunca recibiendo moduleSettings.qb.defaultOptions - y el código generado está moldeado en torno a ambos.
Las tres suposiciones de cableado de las que esto depende - que getInstance( "ChatDb" ) se resuelve a través del propio WireBox de la app generada, que SchemaBuilder@qb/QueryBuilder@qb se resuelven una vez que qb está instalado como un módulo real de ColdBox (qb es una dependencia de box.json, no vendida aquí - el mismo vacío honesto que ya lleva cbmailservices), y que this.datasources en el Application.bx generado se capta como se espera - ahora se ejercitan de extremo a extremo para el camino de preferencias compartidas: runColdBoxIntegrationTests.bxs escribe y lee una preferencia a través de POST/GET /chat/api/preferences desde fuera del proceso (ver abajo), lo cual solo funciona si las tres se cumplen. Lo que no se ejercita todavía de la misma manera es el CRUD de conversación, la protección entre usuarios, y el pase de migración - esos están probados a nivel de biblioteca y vía WebUiRuntimeSpec.bx leyendo objetos reales de WireBox dentro de un arranque, pero no conducidos desde un segundo llamador HTTP independiente de la forma en que ahora lo está el camino de preferencias. Haz un bxAgents serve contra un proyecto webui con qb y bx-sqlite genuinamente instalados antes de confiar en el almacén en producción; el modo de fallo si alguna de las tres suposiciones de cableado está mal es ruidoso en el arranque (un mapeo de WireBox no resoluble o un driver JDBC desconocido), no silencioso.
La interfaz web sobre HTTP real: probada para /health y las rutas de preferencia de espacio de trabajo compartido
runColdBoxIntegrationTests.bxs obtiene GET /chat/api/health desde fuera del servidor, contra la app fixture generada arrancada por un boxlang-miniserver real, y falla el build si no es un 200. En CI devuelve:
+ App probe GET /chat/api/health -> status=200
body: {"success":true,"status":"ok"}
Ese request es la prueba base de extremo a extremo para el lado del servidor de la interfaz web: el enrutamiento de ColdBox alcanzando handlers/ChatUi.bx, WireBox resolviendo el handler y ChatDb, this.datasources dándole a bx-sqlite un archivo SQLite usable, y qb siendo un módulo de ColdBox genuinamente activado - nada de lo cual puede establecer ninguna aserción de texto fuente. El mismo script corredor va más allá y conduce dos rutas más sobre HTTP real contra ese mismo arranque: POST /chat/api/preferences/set y GET /chat/api/preferences, llamadas como dos requests HTTP separados y sin cookies, específicamente para probar el comportamiento de "un solo espacio de trabajo compartido" de la interfaz web sin cuentas - que una preferencia escrita por un llamador se lee de vuelta por un segundo llamador completamente independiente, a través de ChatDb/SQLite, no simplemente reflejada desde una sesión. WebUiRuntimeSpec.bx luego cubre el resto del comportamiento del almacén (migraciones, CRUD de conversación con scope, la protección entre usuarios) leyendo objetos reales de WireBox dentro de ese mismo arranque.
Lo que todavía no está cubierto sobre HTTP real: la lista/historial de conversación y la transmisión SSE. Conducir esas rutas desde WebUiRuntimeSpec no es posible tal como está estructurado, porque ese spec se ejecuta dentro de un request servido por el mismo miniserver único, así que una llamada de loopback competiría por el pool de trabajadores que el propio corredor ocupa. Cubrirlas necesita ya sea un segundo proceso de servidor o un pool de hilos de miniserver más grande, y es un seguimiento natural en lugar de algo que fingir.
Nótese que este párrafo previamente citó runner-coldbox.bxm never responded: HTTP 408 como evidencia de esa inanición. Esa atribución era incorrecta, y vale la pena registrarlo porque engañó a este proyecto durante varios ciclos de CI: el 408 no tenía nada que ver con llamadas de loopback. La página del corredor llamaba a chr( 10 ) - un BIF que no existe - así que devolvía 500 al entrar en cada request, y el bucle de reintento del orquestador se abrió paso a través de 90 segundos de esos 500 antes de reportar el tiempo de espera final. Ambos están corregidos. La preocupación de inanición-de-loopback arriba es una razón estructural genuina para no conducir más rutas desde dentro de ese spec, pero sigue siendo una expectativa bien fundada en lugar de algo que realmente se haya observado fallando aquí.
El propio front-end de la interfaz web: conducido en un navegador real, pero contra una API mockeada
El JavaScript de la página enviada se ejercitó de verdad esta sesión, no solo se afirmó como texto fuente: el index.html generado se cargó en Chromium sin cabeza y se condujo de extremo a extremo con cada ruta <path>/api/* interceptada y respondida con payloads realistas. Confirmado funcionando de esa manera - la barra lateral de conversación renderizando desde GET /conversations y cambiando/renombrando/eliminando a través de sus propias rutas; GET /info moldeando la barra de herramientas (Compact apareciendo solo cuando capabilities.compact es true, el nombre del modelo aterrizando en el encabezado); el tema llegando desde preferences del lado del servidor y aplicándose; /history rehidratando el transcript; un turno de SSE real transmitiendo contenido, razonamiento y chips de tool-call desde el envelope de bx-ai; renderizado de markdown; y New chat creando una conversación del lado del servidor y abriéndola. Cero errores de JavaScript, cero placeholders __TOKEN__ sin reemplazar, y el layout de pantalla estrecha se capturó en 390px.
Lo que eso NO prueba: la API era un mock de Playwright, no el handlers/ChatUi.bx generado corriendo bajo un arranque real de ColdBox contra un almacén SQLite real. Las formas de request y respuesta se tomaron del propio código fuente del handler generado, así que una desviación entre los dos no sería atrapada por esto. Todo en la entrada de limitaciones conocidas de arriba sobre el almacén se aplica aquí también - un bxAgents serve manual contra un proyecto webui con qb y bx-sqlite genuinamente instalados sigue siendo la puerta honesta antes del uso en producción.
Los encabezados de grupo de la navegación del sitio siguen en inglés en un idioma traducido
Cada entrada de navegación respaldada por una página real toma su etiqueta del title del frontmatter de esa página, así que un árbol traducido obtiene etiquetas de navegación traducidas sin esfuerzo - Instalación, Inicio rápido, Introducción a BxAgents y demás se muestran correctamente bajo /es/.
Los cuatro nodos de agrupación sintéticos de docs/nav.json (Getting Started, Conventions, Guides, Reference, más el subgrupo Example Agents) no tienen ninguna página detrás, así que no hay de dónde leer un título traducido. bx-sites v1 entrega además a cada árbol de idioma el navConfig del árbol por defecto tal cual - por diseño, para que la navegación de un idioma siempre refleje la estructura de su árbol por defecto - lo que significa que tampoco hay un nav.json por idioma con el que sobrescribirlos. Por tanto se muestran en inglés en todos los idiomas.
Medido en un build real: la barra lateral de /es/ muestra ["Getting Started", "Instalación", "Inicio rápido", "Introducción a BxAgents", "Conventions", "Agent.bx", ...]. Etiquetas de página traducidas, encabezados de grupo no.
Cerrar esto requiere un cambio en bx-sites (una sobrescritura de navegación por idioma, o títulos de grupo traducibles), no un cambio aquí.