/** * PrismFacet — prompt-rule registration framework. * * Provides: * - router loader (file-based: routersDir + programmatic: __prismFacet.addRouter) * - rule store (persistent rules.json + in-memory external rules) * - before_prompt_build hook that resolves all loaded routers/rules * and appends the matching prompt fragments to the agent's system * prompt * - prompt-rules admin tool to inspect / mutate persistent rules * - cross-plugin API installed at module load time (globalThis.__prismFacet) * * PrismFacet ships NO content of its own — the routers/, prompts/, and * rules.json directories are empty placeholders. Hangman-Lab-specific * routers and rules live in the ClawPrompts plugin, which registers * them at startup via the cross-plugin API. */ import path from "node:path"; // Importing core/cross-plugin-api.ts has a side-effect: it installs // globalThis.__prismFacet at module-import time. Doing this side-effect // import early — before any other plugin's register() — means a // consumer plugin (ClawPrompts) loaded before us still finds a working // __prismFacet to call into. import "./core/cross-plugin-api.js"; import { loadRouters } from "./core/router-loader.js"; import { initRuleStore } from "./core/rule-store.js"; import { registerBeforePromptBuild } from "./hooks/before-prompt-build.js"; import { registerPromptRulesTool } from "./tools/prompt-rules.js"; interface PluginConfig { routersDir?: string; rulesFile?: string; } interface OpenClawPluginApi { logger: { info(msg: string): void; warn(msg: string): void; error(msg: string): void; }; on(hook: string, handler: (...args: any[]) => any): void; registerTool(def: any): void; config?: PluginConfig; } const _G = globalThis as Record; const LIFECYCLE_KEY = "_prismFacetGatewayLifecycleRegistered"; function normalizeConfig(api: OpenClawPluginApi): PluginConfig { const raw = (api as any).config ?? {}; return { routersDir: typeof raw.routersDir === "string" ? raw.routersDir : undefined, rulesFile: typeof raw.rulesFile === "string" ? raw.rulesFile : undefined, }; } export default { id: "prism-facet", name: "PrismFacet", register(api: OpenClawPluginApi) { const config = normalizeConfig(api); const pluginDir = path.dirname(new URL(import.meta.url).pathname); const routersDir = config.routersDir || path.resolve(pluginDir, "routers"); const rulesFile = config.rulesFile || path.resolve(pluginDir, "rules.json"); // Gateway lifecycle: init once if (!_G[LIFECYCLE_KEY]) { _G[LIFECYCLE_KEY] = true; initRuleStore(rulesFile); loadRouters(routersDir, api.logger).catch((err) => { api.logger.error(`[prism-facet] failed to load routers: ${String(err)}`); }); api.on("gateway_stop", () => { _G[LIFECYCLE_KEY] = false; }); } // Agent session hooks: register every time (dedup inside handler) registerBeforePromptBuild(api); // Tools registerPromptRulesTool(api, routersDir); api.logger.info("[prism-facet] plugin registered (framework only — content via ClawPrompts)"); }, };