extensions/
Añade comandos y tareas programadas al chat y al agente servido, en un archivo o una carpeta, con configuración.
En esta página
extensions/
Una extensión añade cosas a BxAgents sin modificarlo: comandos de chat (/deploy, /roll 2d6), listeners de los eventos del chat y de bx-ai, y tareas programadas que se ejecutan cuando el agente se sirve. Es una clase que se ve y se comporta como un ModuleConfig de ColdBox: tiene configure(), onLoad() y onUnload(), su configuración viene de Agent.yaml y se le entregan objetos con $inject.
Un archivo es una extensión completa:
extensions/
hello.bx /hello
roll.bx /roll, /coin
standup/ una carpeta también es una extensión
Extension.bx
commands/Standup.bx
schedulers/Reminder.bx
Un proyecto funcional con todo esto es examples/extensions-agent.
La extensión más pequeña
extensions/hello.bx. El nombre del archivo es el nombre de la extensión.
class {
function configure() {
variables.settings = { greeting : "Hola" }
}
@Command( "hello" )
@Hint( "saluda: /hello <nombre>" )
function greet( agentContext, commandArguments ) {
return agentContext.reply().ok( "#settings.greeting#, #commandArguments#!" )
}
}
Ejecuta bxAgents chat y escribe /hello Ada. /extensions muestra lo que está cargado.
Un método de comando recibe el agentContext (la conversación y nada más; el mismo objeto que reciben los comandos integrados) y el texto que sigue al nombre del comando. Devuelve un Reply (agentContext.reply().ok( "..." ), .warn(), .keyValues(), .grid(), .choose()) o simplemente una cadena. Si lanza una excepción, la persona ve un aviso con el nombre de la extensión y el chat continúa.
Anotaciones
Las anotaciones son anotaciones normales de BoxLang sobre un método. Cada una se puede escribir de cuatro maneras, y todas las anotaciones de BxAgents aceptan las cuatro:
| Escrita | Significa | Ejemplo |
|---|---|---|
| sin valor | valores por defecto; el nombre sale del método | @Command sobre function roll() es /roll |
| simple | el valor principal | @Command( "deploy" ) |
| arreglo | varios | @Alias( [ "dice", "d" ] ) |
| struct | todas las opciones | @Command( { name : "deploy", hint : "planifica", admin : true, aliases : [ "ship" ], transports : [ "tui", "web" ] } ) |
Las anotaciones abreviadas dicen una opción cada una: @Hint( "..." ), @Admin, @Alias( ... ), @Transports( [ "tui", "web" ] ). Una opción del struct gana a una abreviada, y el conflicto se muestra en /extensions. Los nombres de anotación no distinguen mayúsculas.
transports indica dónde corre el comando: tui (el chat de terminal), web (la página web y la ruta HTTP de comandos) y gateway (plataformas de chat). Por defecto son las tres. Una plataforma de chat solo responde a los comandos que su gateway permite (gatewaySession.commands.allow), y un comando con @Admin solo lo ejecutan los administradores indicados allÃ.
Configuración
La extensión dice sus valores por defecto en configure(), igual que un módulo:
function configure() {
variables.settings = { target : "staging", environments : [ "staging", "production" ] }
}
Quienes la usan los cambian bajo el nombre de la extensión en Agent.yaml:
extensions:
deploy:
target: production
Una persona puede poner sus propios valores en ~/.bxagents/bxAgents.yml (mismo bloque extensions:); los del proyecto ganan a los suyos. Los structs anidados se fusionan clave por clave; los arreglos y los valores simples reemplazan. Dentro de la extensión, settings es el struct fusionado.
Ciclo de vida
| Método | Cuándo |
|---|---|
configure() | una vez, primero. Define variables.settings. Sin trabajo aquà |
onLoad() | tras fusionar la configuración e inyectar todo. Abre conexiones, inicia temporizadores |
onUnload() | cuando termina el chat o el servidor. Libéralos |
this.title, this.author, this.webURL, this.description, this.version y this.enabled se leen como en un módulo, y this.dependencies = [ "otra-extension" ] carga primero esa extensión. Una extensión que falla al cargar aparece como fallida en /extensions con el motivo; las demás se cargan igual.
Inyección
Toda clase de una extensión tiene $inject( name, object )`. Pone el objeto en los ámbitos `this` y `variables`. Solo privado: `$inject( name, object, "variables" ). $read( name ) lee desde fuera una entrada de variables.
BxAgents inyecta dos cosas antes de onLoad(): settings y log (log.info( "..." ), .warn(), .error(); lo que una extensión registra también aparece en /extensions). Para compartir un objeto entre los archivos de una extensión, créalo en onLoad() y hazle $inject donde haga falta. La misma llamada sirve a una prueba para entregar un doble a una extensión.
Una extensión en carpeta
Una carpeta tiene la misma forma que un proyecto de agente:
extensions/standup/
Extension.bx opcional: el module config (propiedades, configure(), onLoad(), onUnload())
commands/ una clase por comando, como los integrados
schedulers/ schedulers de BoxLang, que arrancan cuando se sirve el agente
Una clase en commands/ extiende models.chat.commands.BaseCommand@bxagents y define name(), hint(), transports() y run( context, args ). settings y log se le inyectan.
Schedulers
Una clase en schedulers/ es un scheduler de BoxLang: una clase con un configure() que define tareas mediante la propiedad scheduler que BoxLang le da, como cualquier otro scheduler de BoxLang. Cuando se sirve el agente (bxAgents serve, un .bxa, un servidor desplegado) arranca en el servicio de schedulers del runtime. Como toda clase de una extensión, recibe settings (los valores por defecto con los de Agent.yaml) y log antes de que se ejecute configure():
class {
function configure() {
scheduler.task( "standup-reminder" )
.call( () => log.info( "Hora del standup de #settings.channel#" ) )
.everyDayAt( settings.at )
.withNoOverlaps()
}
}
Los métodos de ciclo de vida de los schedulers de BoxLang (onStartup(), onShutdown(), onAnyTaskError() y el resto) funcionan como siempre. Cada scheduler se registra como extension-<extension>-<scheduler>, y uno que no arranca se informa en su extensión dentro de /extensions mientras los demás siguen. El chat de terminal no arranca schedulers: pertenecen al agente servido.
Escuchar eventos
Una extensión también es un interceptor. Un método con el nombre de un evento lo escucha, igual que un interceptor de BoxLang o de ColdBox:
class {
function onChatInput( data ) {
if ( data.text contains "password" ) {
data.cancel( "Eso parece una contraseña, asà que no se envió." )
}
}
function onAIError( data ) {
log.warn( "el modelo falló" )
}
}
Se pueden escuchar dos cosas:
- Eventos de bx-ai (
beforeAIAgentRun,afterAIModelInvoke,onAIError,beforeAIToolExecutey el resto). Se disparan donde corra el agente. - Eventos del chat, anunciados por BxAgents:
| Evento | Cuándo | data tiene | Un listener puede |
|---|---|---|---|
onChatSessionStart / onChatSessionEnd | el chat de terminal se abre o se cierra | agent, transport | |
onChatInput | antes de enviar un mensaje | text, transport | cancel( reason ), o replace( nuevoTexto ) |
onChatTurnStart / onChatTurnEnd | alrededor de un turno del agente | text, transport (reply, status al final) | |
onChatConversationChange | /new, /resume, /clear, /fork o /compact cambió la conversación | line, transport | |
onChatCommandBefore | antes de cualquier comando, en todos los transportes | line, name, arguments, transport | cancel( reason ) |
onChatCommandAfter | después de cualquier comando, en todos los transportes | line, name, reply, transport | replace( nuevaRespuesta ) |
Los eventos de sesión, entrada, turno y conversación son del chat de terminal; los dos eventos de comando se disparan igual en la terminal, la web y las plataformas de chat. Un listener que lanza una excepción se captura: nunca puede detener un turno, los listeners siguientes se ejecutan y el error aparece en /extensions.
@On asocia un método con otro nombre, o con varios eventos a la vez, y admite las cuatro formas de una anotación:
@On( [ "beforeAIToolExecute", "afterAIToolExecute" ] )
function toolCalled( data ) { log.info( "se ejecutó una herramienta" ) }
Si @On nombra un evento que el runtime no conoce, se informa en /extensions en vez de ignorarse.
interceptors y customInterceptionPoints
Como en un module config, configure() puede nombrar clases de la carpeta de la extensión para registrar, cada una con sus propiedades, y puntos propios para anunciar:
function configure() {
variables.interceptors = [ { class : "interceptors.Audit", properties : { level : "info" } } ]
variables.customInterceptionPoints = [ "onAuditRecorded" ]
}
interceptors/Audit.bx escucha con métodos que llevan el nombre del evento y recibe settings, log y properties inyectados. Cualquier extensión puede escuchar onAuditRecorded, y la dueña lo anuncia con BoxAnnounce( "onAuditRecorded", { ... } ). extensions.disable acepta extension:method para apagar un solo listener.
Los eventos de ColdBox (preProcess, postProcess y el resto) no forman parte del chat de terminal; solo se disparan en un agente servido.
De dónde vienen las extensiones
| Capa | Carpeta | Corre en |
|---|---|---|
| modules | extensions/ de un módulo de BoxLang instalado | donde esté instalado el módulo |
| user | ~/.bxagents/extensions/ | solo el chat de terminal de esa máquina |
| project | <proyecto>/extensions/ | el chat de terminal, la web, las plataformas de chat y el servidor |
Gana la capa posterior. Una extensión con el mismo nombre reemplaza a la anterior, y un comando reemplaza a uno anterior con el mismo nombre o alias, incluido uno integrado. Ambos casos se muestran en /extensions; nada se reemplaza en silencio. La compilación copia el extensions/ del proyecto a la app; un agente servido nunca lee la carpeta personal de un desarrollador.
Desactivar cosas
extensions:
disable: [ roll, "deploy:deploy", "acme-*" ]
Un nombre simple desactiva la extensión con ese nombre (y cualquier comando que se llame asÃ); extension:item desactiva un solo comando; * coincide con cualquier secuencia de caracteres. Tanto Agent.yaml como ~/.bxagents/bxAgents.yml lo aceptan.
Verlas
/extensions en el chat, o desde la terminal:
bxAgents extensions # la lista, de dónde viene cada una y qué añadió
bxAgents extensions --json
TodavÃa no
Las extensiones aún no pueden añadir atajos de teclado, elementos de estado, prefijos del compositor, fuentes @, huecos en la interfaz web, gateways, verbos de CLI, herramientas y skills. Eso viene después; el plan es EXTENSIONS-PLAN.md en el repositorio.