Plugins

Modellimplementierungen, Tools, Runtime-Skills, Audit-Ziele, Connectors und Hintergrunddienste nutzen ein RuntimePlugin-Protokoll und einen PluginHost pro Harness. Das sind Plugins der Code-Architektur, keine MCP-Server. Ein Plugin kann mehrere Fähigkeiten beisteuern; das System vergibt keine einander ausschließenden Plugin-Kategorien.

Agent-Schleife, Sitzung, Berechtigungen und Registries bleiben in der Engine. Bestehende deklarative oharness.plugin.json-Bundles werden weiterhin unterstützt und nie automatisch als ausführbare Module importiert.

Schnellstart

Führen Sie die Demonstration mit vier Plugins ohne Anmeldedaten und ohne Netzwerkaufrufe aus:

npm run demo:plugins
npm run test:plugins

Die Beispiele in script/fixtures/runtime-plugins.ts implementieren ein geskriptetes Modell, ein lokales Tool, einen Audit-Beobachter und einen Mock-Connector. Sie dienen der Demonstration. Der gehostete Cloud-Connector wird außerhalb kurzlebiger Instanzen standardmäßig als connector-cloud registriert. Er registriert beim Start zwei Tools ohne Netzwerkzugriff; fehlende Anmeldung oder Serverkonfiguration wird zu einem Tool-Fehler, nicht zu einem Startfehler. Deaktivieren Sie ihn mit { "id": "connector-cloud", "enabled": false } in runtimePlugins der plugins.json. Einbettende Anwendungen können cloudConnectors: false setzen. Der direkte Composio-Adapter bleibt eine optionale Alternative für Entwickler; aktivieren Sie ihn nicht in gehosteten Clients. Pipedream ist nicht implementiert.

Das eingebaute Memory-Plugin mit der ID memory verantwortet jetzt das Memory-Tool, den Prompt-Index pro Zug, die Webseite und den Lebenszyklus der Hintergrundsynchronisierung. Es ist auch in kurzlebigen Harness-Instanzen aktiv (wie das bisherige lokale Memory-Verhalten). Deaktivieren Sie es in plugins.json, um all diese Beiträge zu entfernen. Das ist etwas anderes, als die Cloud-Synchronisierung abzuschalten und das lokale Memory zu behalten.

Ein Plugin schreiben

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 läuft einmal pro Harness-Instanz. Halten Sie Clients, Timer und veränderlichen Zustand darin, nicht auf Modulebene. Plugin-IDs bestehen aus Kleinbuchstaben, Ziffern, Punkten, Unterstrichen oder Bindestrichen. API-Version 1 wird unabhängig von der eigenen Version des Plugins geprüft.

Laden und konfigurieren

Eine einbettende App kann vertrauenswürdige Plugin-Definitionen explizit übergeben:

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

Alternativ fügen Sie runtimePlugins zur Plugin-Datei des Nutzers hinzu (plugins.json in OHARNESS_HOME, normalerweise ~/.oharness). Akzeptiert werden ein absoluter Modulpfad oder ein installiertes Paket, das von diesem Verzeichnis aus auflösbar ist; heruntergeladen wird nichts. Module müssen eine Plugin-Definition als Default exportieren. Für die Verteilung ist gebautes JavaScript vorzuziehen; TypeScript-Quellcode setzt die von Node unterstützte Teilmenge für Type-Stripping voraus.

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

config wird an parseConfig übergeben. Ein konfigurierter Eintrag mit derselben ID kann ein Inline-Plugin konfigurieren oder deaktivieren, seine Implementierung aber nicht durch ein anderes Modul ersetzen. Doppelte IDs innerhalb einer der beiden Listen werden abgelehnt. enabled: false überspringt Modulimport, Konfigurationsparsing und Setup vollständig. required ist standardmäßig true; Fehler optionaler Plugins sind im Status sichtbar, ohne die Harness-Erstellung abzubrechen. Die Konfigurationsliste ersetzt eine frühere Liste, statt Plugin-Optionen tief zusammenzuführen. Änderungen gelten bei der nächsten Harness-Erstellung, nicht für aktive Sitzungen.

Projektkonfiguration kann keine Runtime-Plugins einführen oder umkonfigurieren. Die Übergabe von HarnessOptions.config ist eine ausdrückliche Vertrauensentscheidung der einbettenden Anwendung. Legen Sie keine Secrets in Projektkonfiguration oder Plugin-Metadaten ab. Laden Sie sie über Ihre freigegebene Anmeldedaten-Integration bzw. Umgebung und protokollieren Sie sie nie.

Schnittstellen für Fähigkeiten

SchnittstelleBeitrag
ctx.tools.register(tool)Tool-Definition und -Implementierung; die bestehende Berechtigungs-Pipeline gilt weiterhin
ctx.skills.register(skill)Skill im Arbeitsspeicher, verfügbar für die Lebensdauer dieses Plugins; doppelte Namen werden abgelehnt
ctx.models.register(provider, models)Modellimplementierung samt ihren Modelldefinitionen, atomar validiert
ctx.auth.register(provider)Authentifizierungsimplementierung über den bestehenden AuthProvider-Vertrag
ctx.hooks.on(event, handler)Bestehender typisierter Lebenszyklus-Hook, mit automatischer Abmeldung
ctx.audit.registerSink(sink)Beobachter für Berechtigungs- und Ausführungsereignisse nach bestem Bemühen
ctx.prompts.register(source)Asynchroner System-Prompt-Abschnitt, der für jeden Agent-Zug aktualisiert wird
ctx.pages.register(page)Vom Host gerenderter Seitendeskriptor sowie Lese- und Aktions-Handler
ctx.onStart(fn)Hintergrundarbeit, gestartet, nachdem alle Plugin-Setups abgeschlossen sind
ctx.onDispose(fn)Zu schließende Ressourcen, in umgekehrter Reihenfolge ihrer Belegung

Alle Beiträge müssen während setup registriert werden. Der Host verfolgt sie und macht sie automatisch rückgängig. Die Standardregistrierung lehnt Konflikte ab; Plugins können eingebaute Komponenten oder Registrierungen anderer Plugins nicht stillschweigend ersetzen. Bestehende Fluent-Registry-APIs bleiben kompatibel. Das Entfernen einer Auth-Implementierung löscht keine Anmeldedaten. Modellanbieter sollten eigene IDs beisteuern und nicht versuchen, bestehende Katalogeinträge zu überschreiben. Wählen Sie ein Plugin-Modell über die normale model-Konfiguration oder die Modellauswahl-API.

Ein Runtime-Skill liefert name, description und body, dazu optional ein absolutes directory, wenn sein Inhalt auf Skripte oder andere Dateien verweist. Er nutzt dasselbe skill-Tool mit schrittweiser Offenlegung wie gespeicherte Skills, erscheint mit der Quelle runtime und wird automatisch entfernt, wenn sein Plugin stoppt. Runtime-Skills können eingebaute, Nutzer-, Projekt-, konfigurierte oder andere Runtime-Skills gleichen Namens nicht ersetzen.

ctx.signal kündigt das Herunterfahren des Plugins an. Tools und Modell-Streams erhalten zusätzlich das kombinierte Abbruchsignal von Host und Aufruf. Halten Sie Hintergrundarbeit kooperativ; registrieren Sie das Aufräumen einer Ressource direkt nach ihrer Belegung, vor späteren awaits, die fehlschlagen könnten. onStart muss nach dem Starten der Arbeit auflösen und darf nicht auf eine endlose Worker-Schleife warten. Speichern Sie stattdessen das Promise für den Abschluss und warten Sie in onDispose darauf.

Audit-Plugin

Die Engine liefert jsonlAuditPlugin (id: "audit-jsonl") mit, das genau diesen Vertrag nutzt. Übergeben Sie { path: "/absolute/path/to/events.jsonl" } als Konfiguration, entweder über die Einbettungs-API oder über einen Moduleintrag, der auf packages/engine/dist/runtime-plugins/builtin/jsonl-audit.js zeigt. Es legt übergeordnete Verzeichnisse an, öffnet im Anhängemodus (0600 für eine neu angelegte Datei) und leert seine Warteschlange vor dem Schließen. Es rotiert keine Dateien und ändert keine Rechte an bereits vorhandenen Dateien.

Ein Audit-Ziel erhält RuntimeAuditEvent:

  • permission: bestehende Entscheidungs- und Antwortdatensätze der PermissionEngine, einschließlich der vom Host weitergeleiteten Entscheidungen von Subagenten.
  • tool_execution: abgeschlossene Aufrufe, einschließlich geworfener Tool-Fehler, die in Fehlerergebnisse umgewandelt wurden; mit Zeitmessung, Tool-Identität und Zugehörigkeit zum Runtime-Plugin.

Ausführungsereignisse lassen Ein- und Ausgabeinhalte sowie Anmeldedaten weg. Berechtigungsdatensätze behalten die bestehenden Felder für Regel und Principal, die Pfade oder Befehlsargumente enthalten können: Audit-Ziele müssen mit deren Sensibilität umgehen. Das ist weder eine vollständige Ereigniswiedergabe noch ein Compliance-Journal; Aufrufe, die vor der PermissionEngine blockiert werden, erhalten in dieser Implementierung keine zusätzlichen Entscheidungsdatensätze.

Jedes Ziel erhält einen eigenen Snapshot und eine geordnete Warteschlange. Ein Ziel, das eine Ausnahme wirft, blockiert kein anderes Ziel und ändert keine Tool-Entscheidung. Warteschlangen sind auf 1.000 ausstehende Ereignisse pro Ziel begrenzt; überzählige Ereignisse werden mit Warnung verworfen. Beim Herunterfahren werden die Warteschlangen geleert, bevor die Ressourcen der Ziele geschlossen werden, vorbehaltlich des Aufräum-Timeouts. audit: false deaktiviert sowohl die Runtime-Audit-Ereignisse als auch die bestehende Berechtigungsspur.

Beispiel für ein Dateiziel (innerhalb eines Plugins):

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

Das bewahrt die bestehende Audit-Semantik der Engine nach bestem Bemühen. Ein als erforderlich markiertes Audit-Plugin garantiert den Start, nicht Fail-closed-Schreibvorgänge. Ein verpflichtendes, dauerhaftes Audit bräuchte eine eigens entworfene Ausführungsrichtlinie mit abgewarteten Schreibvorgängen.

Connector-Plugin

Verwenden Sie createConnectorPlugin als bequeme Factory oder implementieren Sie RuntimePlugin direkt. Die Factory gibt ein gewöhnliches RuntimePlugin zurück, keinen weiteren Mechanismus:

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 stellt tools(): Promise<readonly Tool[]> und close() bereit. Schlägt die Tool-Erkennung fehl, wird der verbundene Client trotzdem geschlossen. Der Helfer schaltet readOnly und sessionLocal für externe Tools zwangsweise ab, sodass Anbieter-Metadaten Freigaben oder Einschränkungen des Plan-Modus nicht stillschweigend umgehen können. Stellen Sie nur freigegebene Tools bereit, verwenden Sie Namensräume, validieren Sie Argumente und reichen Sie das Signal jedes Aufrufs an das SDK weiter. Senden Sie nicht den gesamten Katalog eines Anbieters in jeden Modell-Prompt.

Der Host implementiert weder OAuth von Anbietern noch Kontoverknüpfung, Webhooks oder Mandantenspeicher. Der Cloud-Connector liefert das Erstellen gehosteter Links, Web-Interaktion und verifizierte Bindung; am Host-Protokoll ändert er nichts. Die Kontoidentität bei mehreren Nutzern muss aus vertrauenswürdigem Anwendungskontext stammen, nie aus einer vom Modell gewählten Konto-ID. Für den Plugin-Host selbst sind keine Änderungen an der Cloud-Infrastruktur nötig.

Lebenszyklus und Kompatibilität

  1. Aktivierte Einträge auflösen und Vertrag sowie Konfiguration validieren.
  2. Setups in der konfigurierten Reihenfolge ausführen; Registrierungen und Ressourcenzugehörigkeit festhalten.
  3. Starts ausführen, sobald alle Setups abgeschlossen sind; aktiven Status bereitstellen.
  4. Bei einem Fehler das fehlgeschlagene Plugin zurückrollen; ist es erforderlich, alles abbrechen und zurückrollen.
  5. Beim Schließen des Harness neue Host-Läufe verhindern, bestehende Läufe abbrechen bzw. auslaufen lassen, Plugin-Aufrufe abbrechen, Beiträge abmelden und dann Ressourcen in umgekehrter Reihenfolge schließen.

Das Aufräumen ist idempotent und läuft nach einzelnen Fehlern weiter. Das Auslaufen des Agenten sowie Auslaufen und Aufräumen jedes Plugins sind auf fünf Sekunden begrenzt. Timeouts werden gemeldet; sie können unkooperatives JavaScript nicht gewaltsam stoppen, und nach einem Timeout kann Arbeit weiterlaufen. Ein veraltetes, geklontes Plugin-Tool lehnt Aufrufe ab, nachdem sein Plugin gestoppt wurde.

CLI /plugins und Web /context zeigen den Laufzeitzustand neben den deklarativen Bundles. Fehler lassen vom Plugin gelieferte Ausnahmemeldungen weg, damit keine Konfigurations-Secrets durchsickern. Fügen Sie bei Bedarf bereinigte Diagnosen über den Plugin-eigenen Logger hinzu.

V1 hat bewusst kein Entladen zur Laufzeit, keinen Abhängigkeits- oder Dienstgraphen zwischen Plugins, keinen Installer für entfernte Pakete, keine Sandbox, keinen beliebigen Frontend-Modullader und keinen austauschbaren Agent-/Sitzungskern. Installierte In-Process-Plugins haben die Befugnisse des Prozesses: Die Kontext-API ist eine Grenze für die Wartbarkeit, keine Sicherheits-Sandbox.

Unter Konfigurationsgrenzen erfahren Sie, wie Sie ältere Inline-Konfiguration migrieren. Separate Plugin-Dateien sind vertrauenswürdige Nutzerkonfiguration und werden nie aus einem beliebigen Projektverzeichnis geladen.