mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:40:49 +00:00
fix(discord): hand off interactions asynchronously
This commit is contained in:
@@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Discord: own the Carbon interaction listener and hand off Discord slash/component handling asynchronously, so compaction or long session locks no longer trip `InteractionEventListener` listener timeouts. Fixes #73204. Thanks @slideshow-dingo.
|
||||
- Gateway/startup: keep value-option foreground starts on the gateway fast path and skip proxy bootstrap unless proxy env is configured, reducing normal gateway startup RSS and avoiding full CLI graph loading. Thanks @vincentkoc.
|
||||
- Heartbeat/models: show heartbeat model bleed guidance on context-overflow resets when the last runtime model matches configured `heartbeat.model`, so smaller local heartbeat models point users to `isolatedSession` or `lightContext` instead of only compaction-buffer tuning. Fixes #67314. Thanks @Knightmare6890.
|
||||
- Subagents/models: persist `sessions_spawn.model` and configured subagent models as child-session model overrides before the first turn, so spawned subagents actually run on the requested provider/model instead of reverting to the target agent default. Fixes #73180. Thanks @danielzinhu99.
|
||||
|
||||
@@ -117,6 +117,14 @@ describe("SafeGatewayPlugin.connect()", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("leaves Carbon autoInteractions disabled so OpenClaw owns interaction handoff", () => {
|
||||
const plugin = createPlugin();
|
||||
|
||||
expect((plugin as unknown as { options?: { autoInteractions?: boolean } }).options).toEqual(
|
||||
expect.objectContaining({ autoInteractions: false }),
|
||||
);
|
||||
});
|
||||
|
||||
it("clears stale firstHeartbeatTimeout before delegating to super when isConnecting=true", () => {
|
||||
const plugin = createPlugin();
|
||||
|
||||
|
||||
@@ -485,7 +485,9 @@ export function createDiscordGatewayPlugin(params: {
|
||||
const options = {
|
||||
reconnect: { maxAttempts: 50 },
|
||||
intents,
|
||||
autoInteractions: true,
|
||||
// OpenClaw registers its own async interaction listener. Carbon's default
|
||||
// InteractionEventListener awaits the full handler on the critical event lane.
|
||||
autoInteractions: false,
|
||||
};
|
||||
|
||||
if (!proxy) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
let DiscordMessageListener: typeof import("./listeners.js").DiscordMessageListener;
|
||||
let DiscordInteractionListener: typeof import("./listeners.js").DiscordInteractionListener;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ DiscordMessageListener } = await import("./listeners.js"));
|
||||
({ DiscordMessageListener, DiscordInteractionListener } = await import("./listeners.js"));
|
||||
});
|
||||
|
||||
function createLogger() {
|
||||
@@ -150,3 +151,50 @@ describe("DiscordMessageListener", () => {
|
||||
expect(onEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DiscordInteractionListener", () => {
|
||||
it("returns immediately without awaiting Carbon interaction handling", async () => {
|
||||
const handlerDone = createDeferred();
|
||||
const handleInteraction = vi.fn(async () => {
|
||||
await handlerDone.promise;
|
||||
});
|
||||
const logger = createLogger();
|
||||
const listener = new DiscordInteractionListener(logger as never);
|
||||
|
||||
await expect(
|
||||
listener.handle({ id: "interaction-1" } as never, { handleInteraction } as never),
|
||||
).resolves.toBeUndefined();
|
||||
await flushAsyncWork();
|
||||
expect(handleInteraction).toHaveBeenCalledTimes(1);
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
|
||||
handlerDone.resolve?.();
|
||||
await flushAsyncWork();
|
||||
});
|
||||
|
||||
it("logs async interaction failures", async () => {
|
||||
const handleInteraction = vi.fn(async () => {
|
||||
throw new Error("interaction boom");
|
||||
});
|
||||
const logger = createLogger();
|
||||
const listener = new DiscordInteractionListener(logger as never);
|
||||
|
||||
await listener.handle({ id: "interaction-1" } as never, { handleInteraction } as never);
|
||||
await flushAsyncWork();
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("discord interaction handler failed: Error: interaction boom"),
|
||||
);
|
||||
});
|
||||
|
||||
it("calls onEvent callback for each interaction", async () => {
|
||||
const handleInteraction = vi.fn(async () => {});
|
||||
const onEvent = vi.fn();
|
||||
const listener = new DiscordInteractionListener(undefined, onEvent);
|
||||
|
||||
await listener.handle({ id: "interaction-1" } as never, { handleInteraction } as never);
|
||||
await listener.handle({ id: "interaction-2" } as never, { handleInteraction } as never);
|
||||
|
||||
expect(onEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ChannelType,
|
||||
type Client,
|
||||
InteractionCreateListener,
|
||||
MessageCreateListener,
|
||||
MessageReactionAddListener,
|
||||
MessageReactionRemoveListener,
|
||||
@@ -44,6 +45,7 @@ type RuntimeEnv = import("openclaw/plugin-sdk/runtime-env").RuntimeEnv;
|
||||
type Logger = ReturnType<typeof import("openclaw/plugin-sdk/runtime-env").createSubsystemLogger>;
|
||||
|
||||
export type DiscordMessageEvent = Parameters<MessageCreateListener["handle"]>[0];
|
||||
export type DiscordInteractionEvent = Parameters<InteractionCreateListener["handle"]>[0];
|
||||
|
||||
export type DiscordMessageHandler = (
|
||||
data: DiscordMessageEvent,
|
||||
@@ -231,6 +233,28 @@ export class DiscordMessageListener extends MessageCreateListener {
|
||||
}
|
||||
}
|
||||
|
||||
export class DiscordInteractionListener extends InteractionCreateListener {
|
||||
constructor(
|
||||
private logger?: Logger,
|
||||
private onEvent?: () => void,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async handle(data: DiscordInteractionEvent, client: Client) {
|
||||
this.onEvent?.();
|
||||
// Carbon awaits interaction listeners on its critical gateway lane. Hand off
|
||||
// immediately so slash/component handling can wait on session locks or compaction
|
||||
// without tripping Carbon's listener timeout and dropping later gateway events.
|
||||
void Promise.resolve()
|
||||
.then(() => client.handleInteraction(data as Parameters<Client["handleInteraction"]>[0], {}))
|
||||
.catch((err) => {
|
||||
const logger = this.logger ?? discordEventQueueLog;
|
||||
logger.error(danger(`discord interaction handler failed: ${String(err)}`));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class DiscordReactionListener extends MessageReactionAddListener {
|
||||
constructor(private params: DiscordReactionListenerParams) {
|
||||
super();
|
||||
|
||||
@@ -61,6 +61,7 @@ vi.mock("./gateway-supervisor.js", () => ({
|
||||
|
||||
vi.mock("./listeners.js", () => ({
|
||||
DiscordMessageListener: function DiscordMessageListener() {},
|
||||
DiscordInteractionListener: function DiscordInteractionListener() {},
|
||||
DiscordPresenceListener: function DiscordPresenceListener() {},
|
||||
DiscordReactionListener: function DiscordReactionListener() {},
|
||||
DiscordReactionRemoveListener: function DiscordReactionRemoveListener() {},
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { createDiscordGatewaySupervisor } from "./gateway-supervisor.js";
|
||||
import {
|
||||
DiscordMessageListener,
|
||||
DiscordInteractionListener,
|
||||
DiscordPresenceListener,
|
||||
DiscordReactionListener,
|
||||
DiscordReactionRemoveListener,
|
||||
@@ -254,6 +255,10 @@ export function registerDiscordMonitorListeners(params: {
|
||||
trackInboundEvent?: () => void;
|
||||
eventQueueListenerTimeoutMs?: number;
|
||||
}) {
|
||||
registerDiscordListener(
|
||||
params.client.listeners,
|
||||
new DiscordInteractionListener(params.logger, params.trackInboundEvent),
|
||||
);
|
||||
registerDiscordListener(
|
||||
params.client.listeners,
|
||||
new DiscordMessageListener(params.messageHandler, params.logger, params.trackInboundEvent, {
|
||||
|
||||
@@ -483,6 +483,7 @@ vi.mock(buildDiscordSourceModuleId("monitor/gateway-plugin.js"), () => ({
|
||||
}));
|
||||
|
||||
vi.mock(buildDiscordSourceModuleId("monitor/listeners.js"), () => ({
|
||||
DiscordInteractionListener: function DiscordInteractionListener() {},
|
||||
DiscordMessageListener: function DiscordMessageListener() {},
|
||||
DiscordPresenceListener: function DiscordPresenceListener() {},
|
||||
DiscordReactionListener: function DiscordReactionListener() {},
|
||||
|
||||
Reference in New Issue
Block a user