From fd981bf95c8392b94ece2665fa48dadbb2b26dff Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sun, 15 Mar 2026 19:03:28 +0000 Subject: [PATCH] Context: extract extension host engine runtime --- src/context-engine/registry.ts | 17 +++--- .../context-engine-runtime.test.ts | 42 +++++++++++++ src/extension-host/context-engine-runtime.ts | 60 +++++++++++++++++++ .../plugin-registry-registrations.ts | 6 +- src/extension-host/registry-writes.ts | 9 ++- 5 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 src/extension-host/context-engine-runtime.test.ts create mode 100644 src/extension-host/context-engine-runtime.ts diff --git a/src/context-engine/registry.ts b/src/context-engine/registry.ts index 1701877790a..84a153a6200 100644 --- a/src/context-engine/registry.ts +++ b/src/context-engine/registry.ts @@ -1,5 +1,10 @@ -import type { OpenClawConfig } from "../config/config.js"; -import { defaultSlotIdForKey } from "../plugins/slots.js"; +import { + getExtensionHostContextEngineFactory, + listExtensionHostContextEngineIds, + registerExtensionHostContextEngine, + resolveExtensionHostContextEngine, + type ExtensionHostContextEngineFactory, +} from "../extension-host/context-engine-runtime.js"; import type { ContextEngine } from "./types.js"; /** @@ -97,18 +102,12 @@ export function registerContextEngine( return registerContextEngineForOwner(id, factory, PUBLIC_CONTEXT_ENGINE_OWNER); } -/** - * Return the factory for a registered engine, or undefined. - */ export function getContextEngineFactory(id: string): ContextEngineFactory | undefined { return getContextEngineRegistryState().engines.get(id)?.factory; } -/** - * List all registered engine ids. - */ export function listContextEngineIds(): string[] { - return [...getContextEngineRegistryState().engines.keys()]; + return listExtensionHostContextEngineIds(); } // --------------------------------------------------------------------------- diff --git a/src/extension-host/context-engine-runtime.test.ts b/src/extension-host/context-engine-runtime.test.ts new file mode 100644 index 00000000000..25a4e02c543 --- /dev/null +++ b/src/extension-host/context-engine-runtime.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import type { ContextEngine } from "../context-engine/types.js"; +import { + getExtensionHostContextEngineFactory, + listExtensionHostContextEngineIds, + registerExtensionHostContextEngine, +} from "./context-engine-runtime.js"; + +class TestContextEngine implements ContextEngine { + readonly info = { + id: "host-test", + name: "Host Test", + version: "1.0.0", + }; + + async ingest() { + return { ingested: false }; + } + + async assemble(params: { messages: [] }) { + return { messages: params.messages, estimatedTokens: 0 }; + } + + async afterTurn() {} + + async compact() { + return { ok: true, compacted: false, reason: "noop" }; + } +} + +describe("extension host context engine runtime", () => { + it("stores registered context-engine factories in the host-owned runtime", async () => { + const factory = () => new TestContextEngine(); + registerExtensionHostContextEngine("host-test", factory); + + expect(getExtensionHostContextEngineFactory("host-test")).toBe(factory); + expect(listExtensionHostContextEngineIds()).toContain("host-test"); + expect(await getExtensionHostContextEngineFactory("host-test")?.()).toBeInstanceOf( + TestContextEngine, + ); + }); +}); diff --git a/src/extension-host/context-engine-runtime.ts b/src/extension-host/context-engine-runtime.ts new file mode 100644 index 00000000000..f580cdf2a34 --- /dev/null +++ b/src/extension-host/context-engine-runtime.ts @@ -0,0 +1,60 @@ +import type { OpenClawConfig } from "../config/config.js"; +import type { ContextEngine } from "../context-engine/types.js"; +import { defaultSlotIdForKey } from "../plugins/slots.js"; + +export type ExtensionHostContextEngineFactory = () => ContextEngine | Promise; + +const CONTEXT_ENGINE_RUNTIME_STATE = Symbol.for("openclaw.contextEngineRegistryState"); + +type ExtensionHostContextEngineRuntimeState = { + engines: Map; +}; + +function getExtensionHostContextEngineRuntimeState(): ExtensionHostContextEngineRuntimeState { + const globalState = globalThis as typeof globalThis & { + [CONTEXT_ENGINE_RUNTIME_STATE]?: ExtensionHostContextEngineRuntimeState; + }; + if (!globalState[CONTEXT_ENGINE_RUNTIME_STATE]) { + globalState[CONTEXT_ENGINE_RUNTIME_STATE] = { + engines: new Map(), + }; + } + return globalState[CONTEXT_ENGINE_RUNTIME_STATE]; +} + +export function registerExtensionHostContextEngine( + id: string, + factory: ExtensionHostContextEngineFactory, +): void { + getExtensionHostContextEngineRuntimeState().engines.set(id, factory); +} + +export function getExtensionHostContextEngineFactory( + id: string, +): ExtensionHostContextEngineFactory | undefined { + return getExtensionHostContextEngineRuntimeState().engines.get(id); +} + +export function listExtensionHostContextEngineIds(): string[] { + return [...getExtensionHostContextEngineRuntimeState().engines.keys()]; +} + +export async function resolveExtensionHostContextEngine( + config?: OpenClawConfig, +): Promise { + const slotValue = config?.plugins?.slots?.contextEngine; + const engineId = + typeof slotValue === "string" && slotValue.trim() + ? slotValue.trim() + : defaultSlotIdForKey("contextEngine"); + + const factory = getExtensionHostContextEngineRuntimeState().engines.get(engineId); + if (!factory) { + throw new Error( + `Context engine "${engineId}" is not registered. ` + + `Available engines: ${listExtensionHostContextEngineIds().join(", ") || "(none)"}`, + ); + } + + return factory(); +} diff --git a/src/extension-host/plugin-registry-registrations.ts b/src/extension-host/plugin-registry-registrations.ts index 2f34c2de7c0..822dec8879a 100644 --- a/src/extension-host/plugin-registry-registrations.ts +++ b/src/extension-host/plugin-registry-registrations.ts @@ -1,6 +1,5 @@ import type { AnyAgentTool } from "../agents/tools/common.js"; import type { ChannelPlugin } from "../channels/plugins/types.js"; -import { registerContextEngine as registerLegacyContextEngine } from "../context-engine/registry.js"; import type { GatewayRequestHandler } from "../gateway/server-methods/types.js"; import { registerInternalHook } from "../hooks/internal-hooks.js"; import type { PluginRecord, PluginRegistry } from "../plugins/registry.js"; @@ -16,6 +15,7 @@ import type { OpenClawPluginToolFactory, PluginHookRegistration as TypedPluginHookRegistration, } from "../plugins/types.js"; +import { registerExtensionHostContextEngine } from "./context-engine-runtime.js"; import { applyExtensionHostTypedHookPolicy, bridgeExtensionHostLegacyHooks, @@ -307,7 +307,7 @@ export function createExtensionHostPluginRegistrationActions(params: { const registerContextEngine = ( record: PluginRecord, engineId: string, - factory: Parameters[1], + factory: Parameters[1], ) => { const result = resolveExtensionContextEngineRegistration({ engineId, @@ -325,7 +325,7 @@ export function createExtensionHostPluginRegistrationActions(params: { } addExtensionContextEngineRegistration({ entry: result.entry, - registerEngine: registerLegacyContextEngine, + registerEngine: registerExtensionHostContextEngine, }); }; diff --git a/src/extension-host/registry-writes.ts b/src/extension-host/registry-writes.ts index 6cc37e0e5e3..4f7b5f2c74b 100644 --- a/src/extension-host/registry-writes.ts +++ b/src/extension-host/registry-writes.ts @@ -1,4 +1,3 @@ -import { registerContextEngine, type ContextEngineFactory } from "../context-engine/registry.js"; import type { GatewayRequestHandler } from "../gateway/server-methods/types.js"; import type { PluginChannelRegistration, @@ -13,6 +12,10 @@ import type { PluginToolRegistration, } from "../plugins/registry.js"; import type { PluginHookRegistration as TypedPluginHookRegistration } from "../plugins/types.js"; +import { + registerExtensionHostContextEngine, + type ExtensionHostContextEngineFactory, +} from "./context-engine-runtime.js"; import type { ExtensionHostChannelRegistration, ExtensionHostCliRegistration, @@ -162,8 +165,8 @@ export function addExtensionCommandRegistration(params: { export function addExtensionContextEngineRegistration(params: { entry: ExtensionHostContextEngineRegistration; - registerEngine?: (engineId: string, factory: ContextEngineFactory) => void; + registerEngine?: (engineId: string, factory: ExtensionHostContextEngineFactory) => void; }): void { - const registerEngine = params.registerEngine ?? registerContextEngine; + const registerEngine = params.registerEngine ?? registerExtensionHostContextEngine; registerEngine(params.entry.engineId, params.entry.factory); }