Plugin

Implementazioni di modelli, strumenti, skill di runtime, destinazioni di audit, connettori e servizi in background usano un solo protocollo RuntimePlugin e un PluginHost per ogni Harness. Sono plugin dell'architettura del codice, non server MCP. Un plugin può fornire più funzionalità; il sistema non assegna categorie di plugin mutuamente esclusive.

Il ciclo dell'agente, la Session, i permessi e i registri restano nel motore. I bundle dichiarativi oharness.plugin.json esistenti restano supportati e non vengono mai importati automaticamente come moduli eseguibili.

Avvio rapido

Esegui la dimostrazione con quattro plugin, senza credenziali né chiamate di rete:

npm run demo:plugins
npm run test:plugins

Gli esempi in script/fixtures/runtime-plugins.ts implementano un modello scriptato, uno strumento locale, un osservatore di audit e un connettore simulato. Sono dimostrazioni. Il connettore Cloud ospitato è registrato come connector-cloud per impostazione predefinita al di fuori delle istanze effimere. Registra due strumenti senza accesso alla rete all'avvio; l'accesso mancante o la configurazione del server assente diventano un errore dello strumento, non un errore di avvio. Disattivalo con { "id": "connector-cloud", "enabled": false } in runtimePlugins di plugins.json. Chi integra OHarness può impostare cloudConnectors: false. L'adattatore Composio diretto resta un'alternativa facoltativa per sviluppatori; non attivarlo nei client ospitati. Pipedream non è implementato.

Il plugin Memoria integrato, ID memory, ora gestisce lo strumento di memoria, l'indice nel prompt per ogni turno, la pagina web e il ciclo di vita della sincronizzazione in background. È attivo anche nelle istanze effimere di Harness (come il precedente comportamento della memoria locale). Disattivalo in plugins.json per rimuovere tutti questi contributi. Questo è diverso dal disattivare la sincronizzazione cloud mantenendo la memoria locale.

Scrivere 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 viene eseguito una volta per ogni istanza di Harness. Tieni client, timer e stato mutabile al suo interno, non a livello di modulo. Gli ID dei plugin usano lettere minuscole, numeri, punti, trattini bassi o trattini. La versione API 1 viene verificata indipendentemente dalla versione del plugin.

Caricare e configurare

Un'app che integra OHarness può fornire esplicitamente definizioni di plugin attendibili:

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();
}

In alternativa, aggiungi runtimePlugins al file dei plugin a livello utente (plugins.json in OHARNESS_HOME, di solito ~/.oharness). È accettato un percorso assoluto di modulo o un pacchetto installato risolvibile da quella directory; non viene scaricato nulla. I moduli devono esportare come default una definizione di plugin. Per la distribuzione preferisci JavaScript compilato; il TypeScript sorgente richiede il sottoinsieme di type-stripping supportato da Node.

{
  "runtimePlugins": [
    {
      "id": "hello-tools",
      "module": "/absolute/path/to/hello-plugin.js",
      "enabled": true,
      "required": true
    }
  ]
}

config viene passato a parseConfig. Una voce configurata con lo stesso ID può configurare/disattivare un plugin inline, ma non può sostituirne l'implementazione con un altro modulo. Gli ID duplicati in uno dei due elenchi vengono rifiutati. enabled: false salta del tutto l'importazione del modulo, l'analisi della configurazione e il setup. required è true per impostazione predefinita; gli errori dei plugin opzionali sono visibili nello stato senza interrompere la creazione di Harness. L'elenco di configurazione sostituisce un elenco precedente invece di unire in profondità le opzioni dei plugin. Le modifiche si applicano alla successiva creazione di Harness, non alle sessioni attive.

La configurazione di progetto non può introdurre né riconfigurare plugin di runtime. Fornire HarnessOptions.config è una decisione di fiducia esplicita dell'applicazione che integra OHarness. Non mettere segreti nella configurazione di progetto o nei metadati dei plugin. Caricali tramite l'integrazione di credenziali/l'ambiente approvati e non scriverli mai nei log.

Interfacce delle funzionalità

InterfacciaContributo
ctx.tools.register(tool)Definizione e implementazione dello strumento; la pipeline dei permessi esistente resta valida
ctx.skills.register(skill)Skill in memoria disponibile per la durata del plugin; i nomi duplicati vengono rifiutati
ctx.models.register(provider, models)Implementazione del modello più le sue definizioni di modello, convalidate in modo atomico
ctx.auth.register(provider)Implementazione di autenticazione che usa il contratto AuthProvider esistente
ctx.hooks.on(event, handler)Hook del ciclo di vita tipizzato esistente, con disiscrizione automatica
ctx.audit.registerSink(sink)Osservatore best-effort di eventi di permesso/esecuzione
ctx.prompts.register(source)Sezione asincrona del prompt di sistema aggiornata a ogni turno dell'agente
ctx.pages.register(page)Descrittore di pagina reso dall'host e gestori di lettura/azione
ctx.onStart(fn)Lavoro in background, avviato dopo che tutti i setup dei plugin sono terminati
ctx.onDispose(fn)Risorse da chiudere, in ordine inverso di acquisizione

Tutti i contributi devono essere registrati durante setup. Vengono tracciati dall'host e annullati automaticamente. La registrazione predefinita rifiuta i conflitti; i plugin non possono sostituire di nascosto gli elementi integrati né le registrazioni l'uno dell'altro. Le API fluent dei registri esistenti restano compatibili. Rimuovere un'implementazione di autenticazione non cancella le credenziali. I fornitori di modelli dovrebbero fornire i propri ID, non tentare di sovrascrivere voci di catalogo esistenti. Seleziona un modello di un plugin con la normale configurazione model o l'API di selezione del modello.

Una skill di runtime fornisce name, description e body, più una directory assoluta opzionale quando il suo corpo fa riferimento a script o altri file. Usa lo stesso strumento skill a rivelazione progressiva delle skill persistenti, compare con origine runtime e viene rimossa automaticamente quando il suo plugin si ferma. Le skill di runtime non possono sostituire skill integrate, utente, di progetto, configurate o di altri runtime con lo stesso nome.

ctx.signal annuncia l'arresto del plugin. Anche gli strumenti e gli stream dei modelli ricevono il segnale di annullamento combinato host/chiamata. Mantieni cooperativo il lavoro in background; registra la pulizia delle risorse subito dopo averle acquisite, prima di await successivi che potrebbero fallire. onStart deve risolversi dopo aver avviato il lavoro, non attendere un ciclo di lavoro infinito. Conserva invece la sua promise di completamento e attendila in onDispose.

Plugin di audit

Il motore include jsonlAuditPlugin (id: "audit-jsonl"), che usa questo stesso contratto. Passa { path: "/absolute/path/to/events.jsonl" } come sua configurazione, tramite l'API di integrazione oppure con una voce di modulo che punta a packages/engine/dist/runtime-plugins/builtin/jsonl-audit.js. Crea le directory padre, apre in modalità append (0600 per un file appena creato) e svuota la propria coda prima di chiudere. Non ruota i file e non cambia i permessi dei file preesistenti.

Una destinazione di audit riceve RuntimeAuditEvent:

  • permission: i record esistenti di decisione/risposta del PermissionEngine, comprese le decisioni dei sub-agenti instradate dall'host.
  • tool_execution: le chiamate completate, compresi gli errori lanciati dagli strumenti e convertiti in risultati di errore; include tempi, identità dello strumento e plugin di runtime proprietario.

Gli eventi di esecuzione omettono i corpi di input/output e le credenziali. I record dei permessi conservano i campi esistenti di regola/principal, che possono contenere percorsi o argomenti di comandi: le destinazioni devono gestirne la sensibilità. Non è una riproduzione completa degli eventi né un registro di conformità; in questa implementazione le chiamate bloccate prima del PermissionEngine non ottengono record di decisione aggiuntivi.

Ogni destinazione riceve la propria copia e una coda ordinata. Una destinazione che lancia un'eccezione non blocca le altre né cambia una decisione su uno strumento. Le code sono limitate a 1.000 eventi in sospeso per destinazione e segnalano/scartano gli eventi in eccesso. L'arresto svuota le code prima di chiudere le risorse delle destinazioni, entro il timeout di pulizia. audit: false disattiva gli eventi di audit del runtime oltre alla traccia dei permessi esistente.

Esempio di configurazione di una destinazione su file (all'interno di 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");
});

Questo mantiene la semantica di audit best-effort esistente del motore. Rendere obbligatorio un plugin di audit ne garantisce l'avvio, non scritture fail-closed. Un audit durevole obbligatorio richiederebbe una policy di esecuzione con attesa, progettata a parte.

Plugin connettore

Usa createConnectorPlugin come factory di comodo, oppure implementa direttamente RuntimePlugin. La factory restituisce un normale RuntimePlugin, non un altro meccanismo:

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 espone tools(): Promise<readonly Tool[]> e close(). Se la scoperta degli strumenti fallisce, il client connesso viene comunque chiuso. L'helper forza readOnly e sessionLocal a off per gli strumenti esterni, così i metadati del fornitore non possono aggirare di nascosto l'approvazione o le restrizioni della modalità plan. Esponi solo strumenti approvati, usa namespace, convalida gli argomenti e propaga all'SDK il segnale di ogni chiamata. Non inviare l'intero catalogo di un fornitore in ogni prompt del modello.

L'host non implementa OAuth del fornitore, collegamento di account, webhook né archiviazione per tenant. Il connettore Cloud fornisce la creazione di link ospitati, l'interazione Web e l'associazione verificata; non cambia il protocollo dell'host. L'identità dell'account multiutente deve venire da un contesto applicativo attendibile, mai da un ID account scelto dal modello. L'host dei plugin in sé non richiede modifiche all'infrastruttura cloud.

Ciclo di vita e compatibilità

  1. Risolve le voci abilitate e convalida contratto/configurazione.
  2. Esegue i setup nell'ordine configurato; registra la proprietà di registrazioni e risorse.
  3. Esegue gli avvii quando tutti i setup sono completati; espone lo stato attivo.
  4. In caso di errore, annulla il plugin fallito; interrompe e annulla tutto se è obbligatorio.
  5. Alla chiusura di Harness, impedisce nuove esecuzioni dell'host, annulla/svuota quelle in corso, interrompe le chiamate dei plugin, rimuove i contributi, poi chiude le risorse in ordine inverso.

La pulizia è idempotente e prosegue dopo i singoli errori. Lo svuotamento dell'agente e lo svuotamento/pulizia di ogni plugin hanno un limite di cinque secondi. I timeout vengono segnalati; non possono fermare forzatamente codice JavaScript non cooperativo, e del lavoro può restare attivo dopo un timeout. Uno strumento di plugin clonato e obsoleto rifiuta le chiamate dopo l'arresto del suo plugin.

/plugins della CLI e /context del Web mostrano lo stato del runtime accanto ai bundle dichiarativi. Gli errori omettono i messaggi di eccezione forniti dai plugin per evitare di far trapelare segreti di configurazione. Se serve, aggiungi diagnostica ripulita con il logger limitato al plugin.

La V1 non ha di proposito scaricamento a caldo, grafo di dipendenze/servizi tra plugin, installatore di pacchetti remoti, sandbox, caricatore arbitrario di moduli di interfaccia né un nucleo Agent/Session sostituibile. I plugin installati nel processo hanno l'autorità del processo: l'API di contesto è un confine di manutenibilità, non una sandbox di sicurezza.

Vedi confini della configurazione per migrare la vecchia configurazione inline. I file di plugin separati sono configurazione utente attendibile, mai caricati da una directory di progetto arbitraria.