Plugins: add host-owned CLI and service storage

This commit is contained in:
Gustavo Madeira Santana 2026-03-15 18:40:10 +00:00
parent 542d17de04
commit daad214a3a
No known key found for this signature in database
3 changed files with 123 additions and 17 deletions

View File

@ -25,7 +25,9 @@ import type {
ExtensionHostToolRegistration,
} from "./runtime-registrations.js";
import {
addExtensionHostCliRegistration,
addExtensionHostHttpRoute,
addExtensionHostServiceRegistration,
replaceExtensionHostHttpRoute,
setExtensionHostGatewayHandler,
} from "./runtime-registry.js";
@ -131,7 +133,7 @@ export function addExtensionCliRegistration(params: {
entry: ExtensionHostCliRegistration;
}): void {
params.record.cliCommands.push(...params.commands);
params.registry.cliRegistrars.push(params.entry as PluginCliRegistration);
addExtensionHostCliRegistration(params.registry, params.entry as PluginCliRegistration);
}
export function addExtensionServiceRegistration(params: {
@ -141,7 +143,7 @@ export function addExtensionServiceRegistration(params: {
entry: ExtensionHostServiceRegistration;
}): void {
params.record.services.push(params.serviceId);
params.registry.services.push(params.entry as PluginServiceRegistration);
addExtensionHostServiceRegistration(params.registry, params.entry as PluginServiceRegistration);
}
export function addExtensionCommandRegistration(params: {

View File

@ -1,7 +1,9 @@
import { describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import {
addExtensionHostCliRegistration,
addExtensionHostHttpRoute,
addExtensionHostServiceRegistration,
getExtensionHostGatewayHandlers,
hasExtensionHostRuntimeEntries,
listExtensionHostCliRegistrations,
@ -46,6 +48,26 @@ describe("extension host runtime registry accessors", () => {
handler: vi.fn(),
});
expect(hasExtensionHostRuntimeEntries(gatewayRegistry)).toBe(true);
const cliRegistry = createEmptyPluginRegistry();
addExtensionHostCliRegistration(cliRegistry, {
pluginId: "cli-demo",
source: "test",
commands: ["demo"],
register: () => undefined,
});
expect(hasExtensionHostRuntimeEntries(cliRegistry)).toBe(true);
const serviceRegistry = createEmptyPluginRegistry();
addExtensionHostServiceRegistration(serviceRegistry, {
pluginId: "svc-demo",
source: "test",
service: {
id: "svc-demo",
start: () => undefined,
},
});
expect(hasExtensionHostRuntimeEntries(serviceRegistry)).toBe(true);
});
it("returns stable empty views for missing registries", () => {
@ -74,7 +96,7 @@ describe("extension host runtime registry accessors", () => {
},
}),
});
registry.services.push({
addExtensionHostServiceRegistration(registry, {
pluginId: "svc-demo",
source: "test",
service: {
@ -82,7 +104,7 @@ describe("extension host runtime registry accessors", () => {
start: () => undefined,
},
});
registry.cliRegistrars.push({
addExtensionHostCliRegistration(registry, {
pluginId: "cli-demo",
source: "test",
commands: ["demo"],
@ -104,8 +126,8 @@ describe("extension host runtime registry accessors", () => {
});
expect(listExtensionHostToolRegistrations(registry)).toBe(registry.tools);
expect(listExtensionHostServiceRegistrations(registry)).toBe(registry.services);
expect(listExtensionHostCliRegistrations(registry)).toBe(registry.cliRegistrars);
expect(listExtensionHostServiceRegistrations(registry)).toEqual(registry.services);
expect(listExtensionHostCliRegistrations(registry)).toEqual(registry.cliRegistrars);
expect(listExtensionHostHttpRoutes(registry)).toEqual(registry.httpRoutes);
expect(getExtensionHostGatewayHandlers(registry)).toEqual(registry.gatewayHandlers);
expect(getExtensionHostGatewayHandlers(registry)["demo.echo"]).toBe(handler);
@ -141,4 +163,30 @@ describe("extension host runtime registry accessors", () => {
expect(registry.httpRoutes[0]?.handler).toBe(secondHandler);
expect(getExtensionHostGatewayHandlers(registry)).toEqual(registry.gatewayHandlers);
});
it("keeps legacy CLI and service mirrors synchronized with host-owned state", () => {
const registry = createEmptyPluginRegistry();
const service = {
id: "svc-demo",
start: () => undefined,
};
const register = () => undefined;
addExtensionHostServiceRegistration(registry, {
pluginId: "svc-demo",
source: "test",
service,
});
addExtensionHostCliRegistration(registry, {
pluginId: "cli-demo",
source: "test",
commands: ["demo"],
register,
});
expect(listExtensionHostServiceRegistrations(registry)).toEqual(registry.services);
expect(listExtensionHostCliRegistrations(registry)).toEqual(registry.cliRegistrars);
expect(registry.services[0]?.service).toBe(service);
expect(registry.cliRegistrars[0]?.register).toBe(register);
});
});

View File

@ -17,6 +17,10 @@ const EMPTY_GATEWAY_HANDLERS: Readonly<GatewayRequestHandlers> = Object.freeze({
const EXTENSION_HOST_RUNTIME_REGISTRY_STATE = Symbol.for("openclaw.extensionHostRuntimeRegistry");
type ExtensionHostRuntimeRegistryState = {
cliRegistrars: PluginCliRegistration[];
legacyCliRegistrars: PluginCliRegistration[];
services: PluginServiceRegistration[];
legacyServices: PluginServiceRegistration[];
httpRoutes: PluginHttpRouteRegistration[];
legacyHttpRoutes: PluginHttpRouteRegistration[];
gatewayHandlers: GatewayRequestHandlers;
@ -25,7 +29,7 @@ type ExtensionHostRuntimeRegistryState = {
type RuntimeRegistryBackedPluginRegistry = Pick<
PluginRegistry,
"httpRoutes" | "gatewayHandlers"
"cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers"
> & {
[EXTENSION_HOST_RUNTIME_REGISTRY_STATE]?: ExtensionHostRuntimeRegistryState;
};
@ -41,8 +45,16 @@ function ensureExtensionHostRuntimeRegistryState(
registry.httpRoutes = legacyHttpRoutes;
const legacyGatewayHandlers = registry.gatewayHandlers ?? {};
registry.gatewayHandlers = legacyGatewayHandlers;
const legacyCliRegistrars = registry.cliRegistrars ?? [];
registry.cliRegistrars = legacyCliRegistrars;
const legacyServices = registry.services ?? [];
registry.services = legacyServices;
const state: ExtensionHostRuntimeRegistryState = {
cliRegistrars: [...legacyCliRegistrars],
legacyCliRegistrars,
services: [...legacyServices],
legacyServices,
httpRoutes: [...legacyHttpRoutes],
legacyHttpRoutes,
gatewayHandlers: { ...legacyGatewayHandlers },
@ -52,6 +64,14 @@ function ensureExtensionHostRuntimeRegistryState(
return state;
}
function syncLegacyCliRegistrars(state: ExtensionHostRuntimeRegistryState): void {
state.legacyCliRegistrars.splice(0, state.legacyCliRegistrars.length, ...state.cliRegistrars);
}
function syncLegacyServices(state: ExtensionHostRuntimeRegistryState): void {
state.legacyServices.splice(0, state.legacyServices.length, ...state.services);
}
function syncLegacyHttpRoutes(state: ExtensionHostRuntimeRegistryState): void {
state.legacyHttpRoutes.splice(0, state.legacyHttpRoutes.length, ...state.httpRoutes);
}
@ -94,8 +114,8 @@ export function hasExtensionHostRuntimeEntries(
registry.providers.length > 0 ||
Object.keys(getExtensionHostGatewayHandlers(registry)).length > 0 ||
listExtensionHostHttpRoutes(registry).length > 0 ||
registry.cliRegistrars.length > 0 ||
registry.services.length > 0 ||
listExtensionHostCliRegistrations(registry).length > 0 ||
listExtensionHostServiceRegistrations(registry).length > 0 ||
registry.commands.length > 0 ||
registry.hooks.length > 0 ||
registry.typedHooks.length > 0
@ -115,15 +135,29 @@ export function listExtensionHostToolRegistrations(
}
export function listExtensionHostServiceRegistrations(
registry: Pick<PluginRegistry, "services"> | null | undefined,
registry:
| Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">
| null
| undefined,
): readonly PluginServiceRegistration[] {
return registry?.services ?? EMPTY_SERVICES;
if (!registry) {
return EMPTY_SERVICES;
}
return ensureExtensionHostRuntimeRegistryState(registry as RuntimeRegistryBackedPluginRegistry)
.services;
}
export function listExtensionHostCliRegistrations(
registry: Pick<PluginRegistry, "cliRegistrars"> | null | undefined,
registry:
| Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">
| null
| undefined,
): readonly PluginCliRegistration[] {
return registry?.cliRegistrars ?? EMPTY_CLI_REGISTRARS;
if (!registry) {
return EMPTY_CLI_REGISTRARS;
}
return ensureExtensionHostRuntimeRegistryState(registry as RuntimeRegistryBackedPluginRegistry)
.cliRegistrars;
}
export function listExtensionHostHttpRoutes(
@ -147,7 +181,7 @@ export function getExtensionHostGatewayHandlers(
}
export function addExtensionHostHttpRoute(
registry: Pick<PluginRegistry, "httpRoutes" | "gatewayHandlers">,
registry: Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">,
entry: PluginHttpRouteRegistration,
): void {
const state = ensureExtensionHostRuntimeRegistryState(
@ -158,7 +192,7 @@ export function addExtensionHostHttpRoute(
}
export function replaceExtensionHostHttpRoute(params: {
registry: Pick<PluginRegistry, "httpRoutes" | "gatewayHandlers">;
registry: Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">;
index: number;
entry: PluginHttpRouteRegistration;
}): void {
@ -170,7 +204,7 @@ export function replaceExtensionHostHttpRoute(params: {
}
export function removeExtensionHostHttpRoute(
registry: Pick<PluginRegistry, "httpRoutes" | "gatewayHandlers">,
registry: Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">,
entry: PluginHttpRouteRegistration,
): void {
const state = ensureExtensionHostRuntimeRegistryState(
@ -185,7 +219,7 @@ export function removeExtensionHostHttpRoute(
}
export function setExtensionHostGatewayHandler(params: {
registry: Pick<PluginRegistry, "httpRoutes" | "gatewayHandlers">;
registry: Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">;
method: string;
handler: GatewayRequestHandlers[string];
}): void {
@ -195,3 +229,25 @@ export function setExtensionHostGatewayHandler(params: {
state.gatewayHandlers[params.method] = params.handler;
syncLegacyGatewayHandlers(state);
}
export function addExtensionHostCliRegistration(
registry: Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">,
entry: PluginCliRegistration,
): void {
const state = ensureExtensionHostRuntimeRegistryState(
registry as RuntimeRegistryBackedPluginRegistry,
);
state.cliRegistrars.push(entry);
syncLegacyCliRegistrars(state);
}
export function addExtensionHostServiceRegistration(
registry: Pick<PluginRegistry, "cliRegistrars" | "services" | "httpRoutes" | "gatewayHandlers">,
entry: PluginServiceRegistration,
): void {
const state = ensureExtensionHostRuntimeRegistryState(
registry as RuntimeRegistryBackedPluginRegistry,
);
state.services.push(entry);
syncLegacyServices(state);
}