import { getAcpRuntimeBackend, registerAcpRuntimeBackend, unregisterAcpRuntimeBackend, type AcpRuntime, } from "openclaw/plugin-sdk/acp-runtime-backend"; import type { OpenClawPluginService, OpenClawPluginServiceContext } from "openclaw/plugin-sdk/core"; import { lazyStartRuntimeTurn } from "./src/runtime-turn.js"; const ACPX_BACKEND_ID = "acpx"; type RealAcpxServiceModule = typeof import("./src/service.js"); type CreateAcpxRuntimeServiceParams = NonNullable< Parameters[0] >; type DeferredServiceState = { ctx: OpenClawPluginServiceContext | null; params: CreateAcpxRuntimeServiceParams; realRuntime: AcpRuntime | null; realService: OpenClawPluginService | null; startPromise: Promise | null; }; let serviceModulePromise: Promise | null = null; function loadServiceModule(): Promise { serviceModulePromise ??= import("./src/service.js"); return serviceModulePromise; } async function startRealService(state: DeferredServiceState): Promise { if (state.realRuntime) { return state.realRuntime; } if (!state.ctx) { throw new Error("ACPX runtime service is not started"); } state.startPromise ??= (async () => { const { createAcpxRuntimeService } = await loadServiceModule(); const service = createAcpxRuntimeService(state.params); state.realService = service; await service.start(state.ctx as OpenClawPluginServiceContext); const backend = getAcpRuntimeBackend(ACPX_BACKEND_ID); if (!backend?.runtime) { throw new Error("ACPX runtime service did not register an ACP backend"); } state.realRuntime = backend.runtime; return state.realRuntime; })(); try { return await state.startPromise; } catch (error) { state.startPromise = null; state.realService = null; throw error; } } function createDeferredRuntime(state: DeferredServiceState): AcpRuntime { const resolveRuntime = () => startRealService(state); return { async ensureSession(input) { return await (await resolveRuntime()).ensureSession(input); }, startTurn(input) { return lazyStartRuntimeTurn(resolveRuntime, input); }, async *runTurn(input) { yield* (await resolveRuntime()).runTurn(input); }, async getCapabilities(input) { return (await (await resolveRuntime()).getCapabilities?.(input)) ?? { controls: [] }; }, async getStatus(input) { return (await (await resolveRuntime()).getStatus?.(input)) ?? {}; }, async setMode(input) { await (await resolveRuntime()).setMode?.(input); }, async setConfigOption(input) { await (await resolveRuntime()).setConfigOption?.(input); }, async doctor() { return (await (await resolveRuntime()).doctor?.()) ?? { ok: true, message: "ok" }; }, async prepareFreshSession(input) { await (await resolveRuntime()).prepareFreshSession?.(input); }, async cancel(input) { await (await resolveRuntime()).cancel(input); }, async close(input) { await (await resolveRuntime()).close(input); }, }; } export function createAcpxRuntimeService( params: CreateAcpxRuntimeServiceParams = {}, ): OpenClawPluginService { const state: DeferredServiceState = { ctx: null, params, realRuntime: null, realService: null, startPromise: null, }; return { id: "acpx-runtime", async start(ctx) { if (process.env.OPENCLAW_SKIP_ACPX_RUNTIME === "1") { ctx.logger.info("skipping embedded acpx runtime backend (OPENCLAW_SKIP_ACPX_RUNTIME=1)"); return; } state.ctx = ctx; registerAcpRuntimeBackend({ id: ACPX_BACKEND_ID, runtime: createDeferredRuntime(state), }); ctx.logger.info("embedded acpx runtime backend registered lazily"); }, async stop(ctx) { if (state.realService) { await state.realService.stop?.(ctx); } else { unregisterAcpRuntimeBackend(ACPX_BACKEND_ID); } state.ctx = null; state.realRuntime = null; state.realService = null; state.startPromise = null; }, }; }