import type { OpenClawConfig } from "../config/config.js"; import { STATE_DIR } from "../config/paths.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import type { PluginRegistry } from "./registry.js"; import type { OpenClawPluginServiceContext, PluginLogger } from "./types.js"; const log = createSubsystemLogger("plugins"); const pluginCheckpointLogsEnabled = process.env.OPENCLAW_PLUGIN_CHECKPOINTS === "1"; function createPluginLogger(): PluginLogger { return { info: (msg) => log.info(msg), warn: (msg) => log.warn(msg), error: (msg) => log.error(msg), debug: (msg) => log.debug(msg), }; } function createServiceContext(params: { config: OpenClawConfig; workspaceDir?: string; }): OpenClawPluginServiceContext { return { config: params.config, workspaceDir: params.workspaceDir, stateDir: STATE_DIR, logger: createPluginLogger(), }; } export type PluginServicesHandle = { stop: () => Promise; }; export async function startPluginServices(params: { registry: PluginRegistry; config: OpenClawConfig; workspaceDir?: string; }): Promise { const running: Array<{ id: string; stop?: () => void | Promise; }> = []; const serviceContext = createServiceContext({ config: params.config, workspaceDir: params.workspaceDir, }); for (const entry of params.registry.services) { const service = entry.service; const typedHookCountBefore = params.registry.typedHooks.length; try { await service.start(serviceContext); if (pluginCheckpointLogsEnabled) { const newTypedHooks = params.registry.typedHooks .slice(typedHookCountBefore) .filter((hook) => hook.pluginId === entry.pluginId) .map((hook) => hook.hookName); log.warn( `[plugins][checkpoints] service started (${service.id}, plugin=${entry.pluginId}) typedHooksAdded=${newTypedHooks.length}${newTypedHooks.length > 0 ? ` hooks=${newTypedHooks.join(",")}` : ""}`, ); } running.push({ id: service.id, stop: service.stop ? () => service.stop?.(serviceContext) : undefined, }); } catch (err) { const error = err as Error; const stack = error?.stack?.trim(); log.error( `plugin service failed (${service.id}, plugin=${entry.pluginId}, root=${entry.rootDir ?? "unknown"}): ${error?.message ?? String(err)}${stack ? `\n${stack}` : ""}`, ); } } return { stop: async () => { for (const entry of running.toReversed()) { if (!entry.stop) { continue; } try { await entry.stop(); } catch (err) { log.warn(`plugin service stop failed (${entry.id}): ${String(err)}`); } } }, }; }