Files
PrismFacet/plugin/index.ts
hzhang 33fcd17746 feat(hooks): fabric-chat-injector — suggest chat workflow on channel turns
New before_prompt_build hook that appends a "next action: workflow_start
chat" hint to the system prompt whenever the agent's turn was triggered
by a message in a fabric channel.

If Meridian (`globalThis.__meridian.getChatJournalForChannel`) reports
an existing chat journal for this (agentId, channelId), the hint
includes `from="<journal-id>"` so the agent resumes the conversation
file instead of starting a fresh one each turn.

Activation:
  - ctx.agentId AND ctx.channelId present
  - ctx.messageProvider in {fabric, '' (empty/omitted by gateway)}

TODO(phase-2): once Fabric exposes per-channel type info (DM / group /
triage) via a cross-plugin API, narrow this to xType === 'dm' only.
Today we fire on any fabric channel — chat workflow is a no-op outside
DMs, so the false positives are just prompt-text noise.

Dedup via WeakSet keyed on the event object (same pattern as the
existing before-prompt-build hook) so each turn injects at most once
even when multiple harness call sites trigger the hook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:46:34 +01:00

69 lines
2.1 KiB
TypeScript

import path from "node:path";
import { loadRouters } from "./core/router-loader.js";
import { initRuleStore } from "./core/rule-store.js";
import { registerBeforePromptBuild } from "./hooks/before-prompt-build.js";
import { registerFabricChatInjector } from "./hooks/fabric-chat-injector.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<string, unknown>;
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);
registerFabricChatInjector(api);
// Tools
registerPromptRulesTool(api, routersDir);
api.logger.info("[prism-facet] plugin registered");
},
};