refactor: restructure PrismFacet per OpenClaw plugin spec

- plugin/ directory structure: hooks/, tools/, core/
- export default { id, name, register } entry format
- globalThis state management with lifecycle protection
- WeakSet dedup on before_prompt_build hook
- Tool uses inputSchema + execute (not parameters + handler)
- additionalProperties: false in config schema
- Core logic in plugin/core/ (no plugin-sdk dependency)
- Install/uninstall script (scripts/install.mjs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhi
2026-04-18 17:22:17 +00:00
parent c4a72b13c0
commit d5c057a3f9
17 changed files with 502 additions and 306 deletions

View File

@@ -0,0 +1,106 @@
import { addRule, removeRule, listRules } from "../core/rule-store.js";
import { loadRouters, getRouterNames } from "../core/router-loader.js";
import { resolveInjection } from "../core/prompt-injector.js";
export function registerPromptRulesTool(
api: {
registerTool(def: any): void;
logger: { info(msg: string): void; warn(msg: string): void };
},
routersDir: string
): void {
api.registerTool({
name: "prompt_rules",
description:
"Manage PrismFacet prompt injection rules. " +
"Actions: add (register a rule mapping router:key to a prompt file), " +
"remove (delete a rule), list (show all rules), " +
"test (preview which prompts would be injected for an agent), " +
"reload-routers (hot-reload all router functions), " +
"list-routers (show loaded routers).",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["add", "remove", "list", "test", "reload-routers", "list-routers"],
description: "The action to perform",
},
router: {
type: "string",
description: "Router name (for add/remove)",
},
key: {
type: "string",
description: "Rule key (for add/remove)",
},
file: {
type: "string",
description: "Absolute path to prompt file (for add)",
},
agent: {
type: "string",
description: "Agent ID to test (for test)",
},
},
required: ["action"],
},
execute: async (_toolCallId: string, params: Record<string, unknown>) => {
const action = params.action as string;
const router = params.router as string | undefined;
const key = params.key as string | undefined;
const file = params.file as string | undefined;
const agent = params.agent as string | undefined;
switch (action) {
case "add": {
if (!router || !key || !file) {
return { result: "Error: add requires router, key, and file" };
}
addRule(router, key, file);
return { result: `Rule added: ${router}:${key}${file}` };
}
case "remove": {
if (!router || !key) {
return { result: "Error: remove requires router and key" };
}
const removed = removeRule(router, key);
return {
result: removed
? `Rule removed: ${router}:${key}`
: `Rule not found: ${router}:${key}`,
};
}
case "list": {
const rules = listRules();
const entries = Object.entries(rules);
if (entries.length === 0) return { result: "No rules registered." };
return { result: entries.map(([k, v]) => `${k}${v}`).join("\n") };
}
case "test": {
if (!agent) return { result: "Error: test requires agent" };
const result = await resolveInjection({ agentId: agent }, api.logger);
if (!result.appendSystemContext) {
return { result: `No prompts matched for agent: ${agent}` };
}
return { result: `Matched prompts for ${agent}:\n\n${result.appendSystemContext}` };
}
case "reload-routers": {
await loadRouters(routersDir, api.logger);
const names = getRouterNames();
return { result: `Routers reloaded: ${names.join(", ") || "(none)"}` };
}
case "list-routers": {
const names = getRouterNames();
return {
result: names.length > 0
? `Loaded routers: ${names.join(", ")}`
: "No routers loaded.",
};
}
default:
return { result: `Unknown action: ${action}` };
}
},
});
}