Plugins
Las implementaciones de modelos, las herramientas, las skills de runtime, los destinos de auditoría, los conectores y los servicios en segundo plano usan un único protocolo RuntimePlugin y un PluginHost por Harness. Son plugins de arquitectura de código, no servidores MCP. Un plugin puede aportar varias capacidades; el sistema no asigna categorías de plugin mutuamente excluyentes.
El bucle del agente, Session, los permisos y los registros se quedan en el motor. Los paquetes declarativos oharness.plugin.json existentes siguen siendo compatibles y nunca se importan automáticamente como módulos ejecutables.
Inicio rápido
Ejecuta la demostración de cuatro plugins sin credenciales ni llamadas de red:
npm run demo:plugins
npm run test:plugins
Los ejemplos de script/fixtures/runtime-plugins.ts implementan un modelo con guion, una herramienta local, un observador de auditoría y un conector simulado. Son demostraciones. El conector en la nube alojado se registra por defecto como connector-cloud fuera de las instancias efímeras. Registra dos herramientas sin acceso a la red al arrancar; la falta de sesión iniciada o de configuración del servidor se convierte en un error de herramienta, no en un fallo de arranque. Desactívalo con { "id": "connector-cloud", "enabled": false } en runtimePlugins de plugins.json. Quienes integran el motor pueden definir cloudConnectors: false. El adaptador directo de Composio sigue siendo una alternativa opcional para desarrolladores; no lo habilites en clientes alojados. Pipedream no está implementado.
El plugin de memoria integrado, con ID memory, ahora gestiona la herramienta de memoria, el índice del prompt por turno, la página web y el ciclo de vida de la sincronización en segundo plano. Está activo incluso en instancias efímeras de Harness (igual que el comportamiento anterior de la memoria local). Desactívalo en plugins.json para eliminar todas estas aportaciones. Esto es distinto de desactivar la sincronización en la nube conservando la memoria local.
Escribir un plugin
import type { RuntimePlugin } from "@oharness/engine";
export default {
id: "hello-tools",
version: "1.0.0",
apiVersion: 1,
parseConfig(raw) {
if (raw !== undefined) throw new Error("This plugin takes no options");
return {};
},
setup(ctx) {
ctx.tools.register({
name: "hello_greet",
description: "Return a greeting",
inputSchema: { type: "object", properties: {} },
readOnly: true,
async execute() {
return { content: [{ type: "text", text: "Hello" }] };
},
});
},
} satisfies RuntimePlugin;
setup se ejecuta una vez por instancia de Harness. Mantén clientes, temporizadores y estado mutable dentro de él, no en el ámbito del módulo. Los ID de plugin usan letras minúsculas, números, puntos, guiones bajos o guiones. La versión 1 de la API se comprueba con independencia de la versión propia del plugin.
Cargar y configurar
Una aplicación que integra el motor puede aportar explícitamente definiciones de plugins de confianza:
import { Harness } from "@oharness/engine";
import greeting from "./hello-plugin.js";
const harness = await Harness.create({
runtimePlugins: [{ plugin: greeting, required: true }],
});
try {
console.log(harness.runtimePluginStatus());
// Create sessions / agents as usual.
} finally {
await harness.close();
}
Como alternativa, añade runtimePlugins al archivo de plugins de usuario (plugins.json en OHARNESS_HOME, normalmente ~/.oharness). Se acepta una ruta de módulo absoluta o un paquete instalado que se pueda resolver desde ese directorio; no se descarga nada. Los módulos deben exportar por defecto una definición de plugin. Para distribuir, es preferible JavaScript compilado; el TypeScript fuente requiere el subconjunto de eliminación de tipos que admite Node.
{
"runtimePlugins": [
{
"id": "hello-tools",
"module": "/absolute/path/to/hello-plugin.js",
"enabled": true,
"required": true
}
]
}
config se pasa a parseConfig. Una entrada configurada con el mismo ID puede configurar o desactivar un plugin en línea, pero no puede sustituir su implementación por otro módulo. Los ID duplicados dentro de cualquiera de las listas se rechazan. enabled: false omite por completo la importación del módulo, el análisis de la configuración y el setup. required es true por defecto; los fallos opcionales se ven en el estado sin abortar la creación del Harness. La lista de configuración sustituye a una lista anterior en lugar de fusionar en profundidad las opciones de los plugins. Los cambios se aplican en la siguiente creación del Harness, no a las sesiones activas.
La configuración del proyecto no puede introducir ni reconfigurar plugins de runtime. Aportar HarnessOptions.config es una decisión de confianza explícita de la aplicación que integra el motor. No pongas secretos en la configuración del proyecto ni en los metadatos de los plugins. Cárgalos a través de tu integración de credenciales o entorno aprobados y nunca los registres en logs.
Interfaces de capacidades
| Interfaz | Aportación |
|---|---|
ctx.tools.register(tool) | Definición e implementación de herramienta; el flujo de permisos existente sigue aplicándose |
ctx.skills.register(skill) | Skill en memoria disponible durante la vida de este plugin; los nombres duplicados se rechazan |
ctx.models.register(provider, models) | Implementación de modelo junto con sus definiciones de modelos, validadas de forma atómica |
ctx.auth.register(provider) | Implementación de autenticación con el contrato AuthProvider existente |
ctx.hooks.on(event, handler) | Hook de ciclo de vida tipado existente, con baja automática de la suscripción |
ctx.audit.registerSink(sink) | Observador de eventos de permisos/ejecución en modo de mejor esfuerzo |
ctx.prompts.register(source) | Sección asíncrona del prompt de sistema que se actualiza en cada turno del agente |
ctx.pages.register(page) | Descriptor de página renderizada por el host y manejadores de lectura/acción |
ctx.onStart(fn) | Trabajo en segundo plano, iniciado cuando terminan todos los setups de plugins |
ctx.onDispose(fn) | Recursos que cerrar, en orden inverso al de adquisición |
Todas las aportaciones deben registrarse durante setup. El host las rastrea y las revierte automáticamente. El registro predeterminado rechaza los conflictos; los plugins no pueden sustituir en silencio los componentes integrados ni los registros de otros plugins. Las API de registro fluidas existentes siguen siendo compatibles. Eliminar una implementación de autenticación no borra las credenciales. Los proveedores de modelos deben aportar sus propios ID, no intentar sobrescribir entradas existentes del catálogo. Selecciona un modelo de plugin con la configuración model habitual o con la API de selección de modelos.
Una skill de runtime aporta name, description y body, más un directory absoluto opcional cuando su cuerpo hace referencia a scripts u otros archivos. Usa la misma herramienta skill de divulgación progresiva que las skills persistidas, aparece con origen runtime y se elimina automáticamente cuando su plugin se detiene. Las skills de runtime no pueden sustituir skills integradas, de usuario, de proyecto, configuradas ni otras skills de runtime con el mismo nombre.
ctx.signal anuncia el apagado del plugin. Las herramientas y los streams de modelos también reciben la señal de cancelación combinada del host y de la llamada. Mantén el trabajo en segundo plano cooperativo; registra la limpieza de recursos inmediatamente después de adquirirlos, antes de awaits posteriores que puedan fallar. onStart debe resolverse después de iniciar el trabajo, no esperar a un bucle de trabajo infinito. Guarda su promesa de finalización y espérala en onDispose.
Plugin de auditoría
El motor incluye jsonlAuditPlugin (id: "audit-jsonl"), que usa este mismo contrato. Pasa { path: "/absolute/path/to/events.jsonl" } como su configuración, ya sea a través de la API de integración o de una entrada de módulo que apunte a packages/engine/dist/runtime-plugins/builtin/jsonl-audit.js. Crea los directorios padre, abre en modo de anexado (0600 para un archivo recién creado) y vacía su cola antes de cerrar. No rota archivos ni cambia los permisos de archivos ya existentes.
Un destino de auditoría recibe RuntimeAuditEvent:
permission: registros existentes de decisión/respuesta de PermissionEngine, incluidas las decisiones de subagentes que enruta el host.tool_execution: llamadas completadas, incluidos los errores lanzados por herramientas convertidos en resultados de error; incluye tiempos, identidad de la herramienta y el plugin de runtime al que pertenece.
Los eventos de ejecución omiten los cuerpos de entrada/salida y las credenciales. Los registros de permisos conservan los campos rule/principal existentes, que pueden contener rutas o argumentos de comandos: los destinos deben tratar su sensibilidad. No es una reproducción completa de eventos ni un diario de cumplimiento; las llamadas bloqueadas antes de PermissionEngine no generan registros de decisión adicionales en esta implementación.
Cada destino recibe su propia instantánea y una cola ordenada. Un destino que lanza una excepción no bloquea a otro destino ni cambia una decisión de herramienta. Las colas se limitan a 1.000 eventos pendientes por destino y avisan y descartan los eventos sobrantes. El apagado vacía las colas antes de cerrar los recursos de los destinos, sujeto al tiempo límite de limpieza. audit: false desactiva los eventos de auditoría del runtime además del registro de permisos existente.
Ejemplo de configuración de un destino en archivo (dentro de un plugin):
const file = await open(config.path, "a", 0o600); // node:fs/promises
ctx.onDispose(() => file.close());
ctx.audit.registerSink(async (event) => {
await file.appendFile(JSON.stringify(event) + "\n");
});
Esto conserva la semántica de auditoría de mejor esfuerzo existente del motor. Hacer obligatorio un plugin de auditoría garantiza el arranque, no escrituras con fallo cerrado. Una auditoría duradera obligatoria necesitaría una política de ejecución con espera diseñada por separado.
Plugin de conector
Usa createConnectorPlugin como fábrica de conveniencia, o implementa RuntimePlugin directamente. La fábrica devuelve un RuntimePlugin normal, no otro mecanismo:
const plugin = createConnectorPlugin({
id: "my-connector",
version: "1.0.0",
parseConfig: validateConnectorConfig,
async connect(config, { signal, logger }) {
// Wrap your selected SDK. Bind a trusted account here, not from model input.
return createMyConnectorClient(config, { signal, logger });
},
});
ConnectorClient expone tools(): Promise<readonly Tool[]> y close(). Si falla el descubrimiento de herramientas, el cliente conectado se cierra igualmente. El helper fuerza readOnly y sessionLocal a desactivado en las herramientas externas, para que los metadatos del proveedor no puedan saltarse en silencio la aprobación ni las restricciones del modo plan. Expón solo herramientas aprobadas, usa espacios de nombres, valida los argumentos y propaga la señal de cada llamada al SDK. No envíes un catálogo entero del proveedor en cada prompt del modelo.
El host no implementa OAuth del proveedor, vinculación de cuentas, webhooks ni almacenamiento por inquilino. El conector en la nube aporta la creación de enlaces alojados, la interacción web y la vinculación verificada; no cambia el protocolo del host. La identidad de cuenta multiusuario debe venir de un contexto de aplicación de confianza, nunca de un ID de cuenta elegido por el modelo. El propio host de plugins no requiere cambios en la infraestructura de la nube.
Ciclo de vida y compatibilidad
- Resuelve las entradas habilitadas y valida el contrato y la configuración.
- Ejecuta los setups en el orden configurado; registra la propiedad de los registros y los recursos.
- Ejecuta los arranques una vez completados todos los setups; expone el estado activo.
- Ante un fallo, revierte el plugin fallido; aborta y revierte todo si era obligatorio.
- Al cerrar el Harness, impide nuevas ejecuciones del host, cancela o vacía las ejecuciones existentes, aborta las llamadas de los plugins, anula el registro de las aportaciones y cierra los recursos en orden inverso.
La limpieza es idempotente y continúa tras fallos individuales. El vaciado de agentes y el vaciado/limpieza de cada plugin tienen un límite de cinco segundos. Los tiempos agotados se notifican; no pueden detener por la fuerza JavaScript que no coopera, y puede quedar trabajo pendiente tras agotarse el tiempo. Una herramienta de plugin clonada y obsoleta rechaza las llamadas después de que su plugin se detenga.
/plugins en la CLI y /context en la web muestran el estado del runtime junto a los paquetes declarativos. Los errores omiten los mensajes de excepción aportados por los plugins para no filtrar secretos de configuración. Si hace falta, añade diagnósticos depurados con el logger del ámbito del plugin.
La V1 no tiene, a propósito, descarga en caliente, grafo de dependencias o servicios entre plugins, instalador de paquetes remotos, sandbox, cargador arbitrario de módulos de interfaz ni núcleo Agent/Session sustituible. Los plugins instalados en el proceso tienen la autoridad del proceso: la API de contexto es un límite de mantenibilidad, no un sandbox de seguridad.
Consulta los límites de la configuración para migrar la configuración en línea heredada. Los archivos de plugins separados son configuración de usuario de confianza y nunca se cargan desde un directorio de proyecto arbitrario.