mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 17:51:22 +00:00
* memory-core: add dreaming promotion flow with weighted thresholds * docs(memory): mark dreaming as experimental * memory-core: address dreaming promotion review feedback * memory-core: harden short-term promotion concurrency * acpx: make abort-process test timer-independent * memory-core: simplify dreaming config with mode presets * memory-core: add /dreaming command and tighten recall tracking * ui: add Dreams tab with sleeping lobster animation Adds a new Dreams tab to the gateway UI under the Agent group. The tab is gated behind the memory-core dreaming config — it only appears in the sidebar when dreaming.mode is not 'off'. Features: - Sleeping vector lobster with breathing animation - Floating Z's, twinkling starfield, moon glow - Rotating dream phrase bubble (17 whimsical phrases) - Memory stats bar (short-term, long-term, promoted) - Active/idle visual states - 14 unit tests * plugins: fix --json stdout pollution from hook runner log The hook runner initialization message was using log.info() which writes to stdout via console.log, breaking JSON.parse() in the Docker smoke test for 'openclaw plugins list --json'. Downgrade to log.debug() so it only appears when debugging is enabled. * ui: keep Dreams tab visible when dreaming is off * tests: fix contracts and stabilize extension shards * memory-core: harden dreaming recall persistence and locking * fix: stabilize dreaming PR gates (#60569) (thanks @vignesh07) * test: fix rebase drift in telegram and plugin guards
106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
/**
|
|
* Global Plugin Hook Runner
|
|
*
|
|
* Singleton hook runner that's initialized when plugins are loaded
|
|
* and can be called from anywhere in the codebase.
|
|
*/
|
|
|
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
|
|
import { createHookRunner, type HookRunner } from "./hooks.js";
|
|
import type { PluginRegistry } from "./registry.js";
|
|
import type { PluginHookGatewayContext, PluginHookGatewayStopEvent } from "./types.js";
|
|
|
|
type HookRunnerGlobalState = {
|
|
hookRunner: HookRunner | null;
|
|
registry: PluginRegistry | null;
|
|
};
|
|
|
|
const hookRunnerGlobalStateKey = Symbol.for("openclaw.plugins.hook-runner-global-state");
|
|
const getState = () =>
|
|
resolveGlobalSingleton<HookRunnerGlobalState>(hookRunnerGlobalStateKey, () => ({
|
|
hookRunner: null,
|
|
registry: null,
|
|
}));
|
|
|
|
const getLog = () => createSubsystemLogger("plugins");
|
|
|
|
/**
|
|
* Initialize the global hook runner with a plugin registry.
|
|
* Called once when plugins are loaded during gateway startup.
|
|
*/
|
|
export function initializeGlobalHookRunner(registry: PluginRegistry): void {
|
|
const state = getState();
|
|
const log = getLog();
|
|
state.registry = registry;
|
|
state.hookRunner = createHookRunner(registry, {
|
|
logger: {
|
|
debug: (msg) => log.debug(msg),
|
|
warn: (msg) => log.warn(msg),
|
|
error: (msg) => log.error(msg),
|
|
},
|
|
catchErrors: true,
|
|
failurePolicyByHook: {
|
|
before_tool_call: "fail-closed",
|
|
},
|
|
});
|
|
|
|
const hookCount = registry.hooks.length;
|
|
if (hookCount > 0) {
|
|
log.debug(`hook runner initialized with ${hookCount} registered hooks`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the global hook runner.
|
|
* Returns null if plugins haven't been loaded yet.
|
|
*/
|
|
export function getGlobalHookRunner(): HookRunner | null {
|
|
return getState().hookRunner;
|
|
}
|
|
|
|
/**
|
|
* Get the global plugin registry.
|
|
* Returns null if plugins haven't been loaded yet.
|
|
*/
|
|
export function getGlobalPluginRegistry(): PluginRegistry | null {
|
|
return getState().registry;
|
|
}
|
|
|
|
/**
|
|
* Check if any hooks are registered for a given hook name.
|
|
*/
|
|
export function hasGlobalHooks(hookName: Parameters<HookRunner["hasHooks"]>[0]): boolean {
|
|
return getState().hookRunner?.hasHooks(hookName) ?? false;
|
|
}
|
|
|
|
export async function runGlobalGatewayStopSafely(params: {
|
|
event: PluginHookGatewayStopEvent;
|
|
ctx: PluginHookGatewayContext;
|
|
onError?: (err: unknown) => void;
|
|
}): Promise<void> {
|
|
const log = getLog();
|
|
const hookRunner = getGlobalHookRunner();
|
|
if (!hookRunner?.hasHooks("gateway_stop")) {
|
|
return;
|
|
}
|
|
try {
|
|
await hookRunner.runGatewayStop(params.event, params.ctx);
|
|
} catch (err) {
|
|
if (params.onError) {
|
|
params.onError(err);
|
|
return;
|
|
}
|
|
log.warn(`gateway_stop hook failed: ${String(err)}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset the global hook runner (for testing).
|
|
*/
|
|
export function resetGlobalHookRunner(): void {
|
|
const state = getState();
|
|
state.hookRunner = null;
|
|
state.registry = null;
|
|
}
|