Merge 142fa9c9198eea0ed6a9d022313a6faf61416738 into 9fb78453e088cd7b553d7779faa0de5c83708e70

This commit is contained in:
Rohit Amarnath 2026-03-20 22:00:25 -07:00 committed by GitHub
commit 81d3559a0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 120 additions and 7 deletions

View File

@ -0,0 +1,112 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest";
import { createDefaultDeps } from "../cli/deps.js";
import type { OpenClawConfig } from "../config/config.js";
import {
clearInternalHooks,
createInternalHookEvent,
triggerInternalHook,
} from "../hooks/internal-hooks.js";
import { loadOpenClawPlugins } from "../plugins/loader.js";
import { startGatewaySidecars } from "./server-startup.js";
function createSilentLogger() {
return {
info: (_msg: string) => {},
warn: (_msg: string) => {},
error: (_msg: string) => {},
debug: (_msg: string) => {},
};
}
async function writeTestPlugin(params: {
dir: string;
id: string;
message: string;
}): Promise<void> {
await fs.mkdir(params.dir, { recursive: true });
await fs.writeFile(
path.join(params.dir, "openclaw.plugin.json"),
JSON.stringify({ id: params.id, configSchema: {} }, null, 2),
"utf-8",
);
await fs.writeFile(
path.join(params.dir, "index.ts"),
[
"export default function register(api) {",
` api.registerHook("session:start", async (event) => { event.messages.push(${JSON.stringify(params.message)}); }, { name: ${JSON.stringify(`${params.id}:session-start`)} });`,
"}",
"",
].join("\n"),
"utf-8",
);
}
describe("gateway startup internal hooks", () => {
afterEach(() => {
clearInternalHooks();
});
test("does not clear plugin internal hooks when loading workspace hooks", async () => {
vi.stubEnv("OPENCLAW_SKIP_BROWSER_CONTROL_SERVER", "1");
vi.stubEnv("OPENCLAW_SKIP_GMAIL_WATCHER", "1");
vi.stubEnv("OPENCLAW_SKIP_CHANNELS", "1");
vi.useFakeTimers();
try {
clearInternalHooks();
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-hook-test-"));
try {
const workspaceDir = path.join(tmpRoot, "workspace");
const pluginDir = path.join(tmpRoot, "plugin");
const pluginId = "test-plugin-internal-hooks";
const marker = "plugin-hook-ran";
await fs.mkdir(workspaceDir, { recursive: true });
await writeTestPlugin({ dir: pluginDir, id: pluginId, message: marker });
const cfg: OpenClawConfig = {
hooks: { internal: { enabled: true } },
plugins: {
enabled: true,
allow: [pluginId],
load: { paths: [pluginDir] },
},
};
// Plugin registration may register internal hooks immediately.
const pluginRegistry = loadOpenClawPlugins({
config: cfg,
workspaceDir,
cache: false,
logger: createSilentLogger(),
});
await startGatewaySidecars({
cfg,
pluginRegistry,
defaultWorkspaceDir: workspaceDir,
deps: createDefaultDeps(),
startChannels: async () => {},
log: { warn: () => {} },
logHooks: createSilentLogger(),
logChannels: { info: () => {}, error: () => {} },
logBrowser: { error: () => {} },
});
await vi.runAllTimersAsync();
const event = createInternalHookEvent("session", "start", "test-session", {});
await triggerInternalHook(event);
expect(event.messages).toContain(marker);
} finally {
await fs.rm(tmpRoot, { recursive: true, force: true });
}
} finally {
vi.useRealTimers();
}
});
});

View File

@ -13,11 +13,7 @@ import type { CliDeps } from "../cli/deps.js";
import type { loadConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
import { startGmailWatcherWithLogs } from "../hooks/gmail-watcher-lifecycle.js";
import {
clearInternalHooks,
createInternalHookEvent,
triggerInternalHook,
} from "../hooks/internal-hooks.js";
import { createInternalHookEvent, triggerInternalHook } from "../hooks/internal-hooks.js";
import { loadInternalHooks } from "../hooks/loader.js";
import { isTruthyEnvValue } from "../infra/env.js";
import type { loadOpenClawPlugins } from "../plugins/loader.js";
@ -110,8 +106,8 @@ export async function startGatewaySidecars(params: {
// Load internal hook handlers from configuration and directory discovery.
try {
// Clear any previously registered hooks to ensure fresh loading
clearInternalHooks();
// Internal hooks are cleared once at gateway startup before plugins load.
// Do not clear here: plugins may register internal hooks during plugin registration.
const loadedCount = await loadInternalHooks(params.cfg, params.defaultWorkspaceDir);
if (loadedCount > 0) {
params.logHooks.info(

View File

@ -22,6 +22,7 @@ import {
import { formatConfigIssueLines } from "../config/issue-format.js";
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
import { resolveMainSessionKey } from "../config/sessions.js";
import { clearInternalHooks } from "../hooks/internal-hooks.js";
import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js";
import {
ensureControlUiAssetsBuilt,
@ -557,6 +558,10 @@ export async function startGatewayServer(
env: process.env,
});
const baseMethods = listGatewayMethods();
// Reset internal hook registry before plugins register internal hooks.
// Plugins may call `api.registerHook(...)` during registration, so clearing later
// (e.g. during hook discovery) would wipe plugin-registered hooks like session:start.
clearInternalHooks();
const emptyPluginRegistry = createEmptyPluginRegistry();
let pluginRegistry = emptyPluginRegistry;
let baseGatewayMethods = baseMethods;