mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:50:43 +00:00
fix(slack): share HTTP route registry across module loads
Fixes #67955, #46245, #46246. Co-authored-by: Axel <axel@kaleidoscope.studio> Co-authored-by: Cesar Arevalo <cesar@cesararevalo.com>
This commit is contained in:
@@ -65,6 +65,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Codex harness/models: keep legacy `codex/*` harness shorthand out of model picker and `/models` choice surfaces while migrating primary legacy refs to canonical `openai/*` plus explicit Codex harness config. (#71193) Thanks @vincentkoc.
|
||||
- Plugins/runtime deps: respect explicit plugin and channel disablement when repairing bundled runtime dependencies, so doctor and health checks no longer install deps for disabled configured channels.
|
||||
- WhatsApp/plugins: support an explicit opt-in for inbound `message_received` hooks with canonical channel, conversation, session, and sender fields. Thanks @vincentkoc.
|
||||
- Slack/HTTP: keep webhook handlers in a process-global registry so HTTP mode survives plugin-loader/native-import splits and `/slack/events/<account>` no longer returns 404 after logging as active. Fixes #67955, #46245, and #46246. Thanks @chrisabad and @cesararevalo.
|
||||
- Diagnostics: harden tool and model diagnostic events against hostile errors, blocking listeners, and unsafe stability reason fields. Thanks @vincentkoc.
|
||||
- Plugins/onboarding: record local plugin install source metadata without duplicating raw absolute local paths in persisted `plugins.installs`, while preserving linked load-path cleanup. (#70970) Thanks @vincentkoc.
|
||||
- Browser/tool: tell agents not to pass per-call `timeoutMs` on existing-session type, evaluate, and other Chrome MCP actions that reject timeout overrides.
|
||||
|
||||
@@ -85,4 +85,43 @@ describe("registerSlackHttpHandler", () => {
|
||||
'slack: webhook path /slack/events already registered for account "duplicate"',
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves registered handlers across module reloads", async () => {
|
||||
const handler = vi.fn();
|
||||
unregisters.push(
|
||||
registerSlackHttpHandler({
|
||||
path: "/slack/events/reload",
|
||||
handler,
|
||||
}),
|
||||
);
|
||||
|
||||
vi.resetModules();
|
||||
const reloadedRegistry = await import("./registry.js");
|
||||
const req = { url: "/slack/events/reload" } as IncomingMessage;
|
||||
const res = {} as ServerResponse;
|
||||
|
||||
const handled = await reloadedRegistry.handleSlackHttpRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(handler).toHaveBeenCalledWith(req, res);
|
||||
});
|
||||
|
||||
it("recreates the shared registry if the global slot is corrupted", async () => {
|
||||
const globalStore = globalThis as Record<PropertyKey, unknown>;
|
||||
globalStore[Symbol.for("openclaw.slack.httpRoutes.v1")] = {};
|
||||
const handler = vi.fn();
|
||||
unregisters.push(
|
||||
registerSlackHttpHandler({
|
||||
path: "/slack/events/recovered",
|
||||
handler,
|
||||
}),
|
||||
);
|
||||
const req = { url: "/slack/events/recovered" } as IncomingMessage;
|
||||
const res = {} as ServerResponse;
|
||||
|
||||
const handled = await handleSlackHttpRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(handler).toHaveBeenCalledWith(req, res);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,18 +15,30 @@ type RegisterSlackHttpHandlerArgs = {
|
||||
accountId?: string;
|
||||
};
|
||||
|
||||
const slackHttpRoutes = new Map<string, SlackHttpRequestHandler>();
|
||||
const SLACK_HTTP_ROUTES_GLOBAL_KEY = Symbol.for("openclaw.slack.httpRoutes.v1");
|
||||
|
||||
function getSlackHttpRoutes(): Map<string, SlackHttpRequestHandler> {
|
||||
const globalStore = globalThis as Record<PropertyKey, unknown>;
|
||||
const existing = globalStore[SLACK_HTTP_ROUTES_GLOBAL_KEY];
|
||||
if (existing instanceof Map) {
|
||||
return existing as Map<string, SlackHttpRequestHandler>;
|
||||
}
|
||||
const routes = new Map<string, SlackHttpRequestHandler>();
|
||||
globalStore[SLACK_HTTP_ROUTES_GLOBAL_KEY] = routes;
|
||||
return routes;
|
||||
}
|
||||
|
||||
export function registerSlackHttpHandler(params: RegisterSlackHttpHandlerArgs): () => void {
|
||||
const normalizedPath = normalizeSlackWebhookPath(params.path);
|
||||
if (slackHttpRoutes.has(normalizedPath)) {
|
||||
const routes = getSlackHttpRoutes();
|
||||
if (routes.has(normalizedPath)) {
|
||||
const suffix = params.accountId ? ` for account "${params.accountId}"` : "";
|
||||
params.log?.(`slack: webhook path ${normalizedPath} already registered${suffix}`);
|
||||
return () => {};
|
||||
}
|
||||
slackHttpRoutes.set(normalizedPath, params.handler);
|
||||
routes.set(normalizedPath, params.handler);
|
||||
return () => {
|
||||
slackHttpRoutes.delete(normalizedPath);
|
||||
getSlackHttpRoutes().delete(normalizedPath);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +47,7 @@ export async function handleSlackHttpRequest(
|
||||
res: ServerResponse,
|
||||
): Promise<boolean> {
|
||||
const url = new URL(req.url ?? "/", "http://localhost");
|
||||
const handler = slackHttpRoutes.get(url.pathname);
|
||||
const handler = getSlackHttpRoutes().get(url.pathname);
|
||||
if (!handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user