mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 15:24:32 +00:00
discord: expose EventQueue listenerTimeout as configurable option (fixes #24458)
This commit is contained in:
committed by
Peter Steinberger
parent
8629b996a1
commit
a25a73e707
@@ -302,6 +302,20 @@ export type DiscordAccountConfig = {
|
|||||||
activityType?: 0 | 1 | 2 | 3 | 4 | 5;
|
activityType?: 0 | 1 | 2 | 3 | 4 | 5;
|
||||||
/** Streaming URL (Twitch/YouTube). Required when activityType=1. */
|
/** Streaming URL (Twitch/YouTube). Required when activityType=1. */
|
||||||
activityUrl?: string;
|
activityUrl?: string;
|
||||||
|
/**
|
||||||
|
* Carbon EventQueue configuration. Controls how Discord gateway events are processed.
|
||||||
|
* The most important option is `listenerTimeout` which defaults to 30s in Carbon --
|
||||||
|
* too short for LLM calls with extended thinking. Set a higher value (e.g. 120000)
|
||||||
|
* to prevent the event queue from killing long-running message handlers.
|
||||||
|
*/
|
||||||
|
eventQueue?: {
|
||||||
|
/** Max time (ms) a single listener can run before being killed. Default: 120000. */
|
||||||
|
listenerTimeout?: number;
|
||||||
|
/** Max events queued before backpressure is applied. Default: 10000. */
|
||||||
|
maxQueueSize?: number;
|
||||||
|
/** Max concurrent event processing operations. Default: 50. */
|
||||||
|
maxConcurrency?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiscordConfig = {
|
export type DiscordConfig = {
|
||||||
|
|||||||
@@ -517,6 +517,14 @@ export const DiscordAccountSchema = z
|
|||||||
.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5)])
|
.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5)])
|
||||||
.optional(),
|
.optional(),
|
||||||
activityUrl: z.string().url().optional(),
|
activityUrl: z.string().url().optional(),
|
||||||
|
eventQueue: z
|
||||||
|
.object({
|
||||||
|
listenerTimeout: z.number().int().positive().optional(),
|
||||||
|
maxQueueSize: z.number().int().positive().optional(),
|
||||||
|
maxConcurrency: z.number().int().positive().optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.superRefine((value, ctx) => {
|
.superRefine((value, ctx) => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { RuntimeEnv } from "../../runtime.js";
|
|||||||
const {
|
const {
|
||||||
clientFetchUserMock,
|
clientFetchUserMock,
|
||||||
clientGetPluginMock,
|
clientGetPluginMock,
|
||||||
|
clientConstructorOptionsMock,
|
||||||
createDiscordNativeCommandMock,
|
createDiscordNativeCommandMock,
|
||||||
createNoopThreadBindingManagerMock,
|
createNoopThreadBindingManagerMock,
|
||||||
createThreadBindingManagerMock,
|
createThreadBindingManagerMock,
|
||||||
@@ -21,6 +22,7 @@ const {
|
|||||||
} = vi.hoisted(() => {
|
} = vi.hoisted(() => {
|
||||||
const createdBindingManagers: Array<{ stop: ReturnType<typeof vi.fn> }> = [];
|
const createdBindingManagers: Array<{ stop: ReturnType<typeof vi.fn> }> = [];
|
||||||
return {
|
return {
|
||||||
|
clientConstructorOptionsMock: vi.fn(),
|
||||||
clientFetchUserMock: vi.fn(async (_target: string) => ({ id: "bot-1" })),
|
clientFetchUserMock: vi.fn(async (_target: string) => ({ id: "bot-1" })),
|
||||||
clientGetPluginMock: vi.fn<(_name: string) => unknown>(() => undefined),
|
clientGetPluginMock: vi.fn<(_name: string) => unknown>(() => undefined),
|
||||||
createDiscordNativeCommandMock: vi.fn(() => ({ name: "mock-command" })),
|
createDiscordNativeCommandMock: vi.fn(() => ({ name: "mock-command" })),
|
||||||
@@ -69,9 +71,12 @@ vi.mock("@buape/carbon", () => {
|
|||||||
class Client {
|
class Client {
|
||||||
listeners: unknown[];
|
listeners: unknown[];
|
||||||
rest: { put: ReturnType<typeof vi.fn> };
|
rest: { put: ReturnType<typeof vi.fn> };
|
||||||
constructor(_options: unknown, handlers: { listeners?: unknown[] }) {
|
options: unknown;
|
||||||
|
constructor(options: unknown, handlers: { listeners?: unknown[] }) {
|
||||||
|
this.options = options;
|
||||||
this.listeners = handlers.listeners ?? [];
|
this.listeners = handlers.listeners ?? [];
|
||||||
this.rest = { put: vi.fn(async () => undefined) };
|
this.rest = { put: vi.fn(async () => undefined) };
|
||||||
|
clientConstructorOptionsMock(options);
|
||||||
}
|
}
|
||||||
async handleDeployRequest() {
|
async handleDeployRequest() {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -254,6 +259,7 @@ describe("monitorDiscordProvider", () => {
|
|||||||
}) as OpenClawConfig;
|
}) as OpenClawConfig;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
clientConstructorOptionsMock.mockClear();
|
||||||
clientFetchUserMock.mockClear().mockResolvedValue({ id: "bot-1" });
|
clientFetchUserMock.mockClear().mockResolvedValue({ id: "bot-1" });
|
||||||
clientGetPluginMock.mockClear().mockReturnValue(undefined);
|
clientGetPluginMock.mockClear().mockReturnValue(undefined);
|
||||||
createDiscordNativeCommandMock.mockClear().mockReturnValue({ name: "mock-command" });
|
createDiscordNativeCommandMock.mockClear().mockReturnValue({ name: "mock-command" });
|
||||||
@@ -334,4 +340,47 @@ describe("monitorDiscordProvider", () => {
|
|||||||
expect(lifecycleArgs.pendingGatewayErrors).toHaveLength(1);
|
expect(lifecycleArgs.pendingGatewayErrors).toHaveLength(1);
|
||||||
expect(String(lifecycleArgs.pendingGatewayErrors?.[0])).toContain("4014");
|
expect(String(lifecycleArgs.pendingGatewayErrors?.[0])).toContain("4014");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes default eventQueue.listenerTimeout of 120s to Carbon Client", async () => {
|
||||||
|
const { monitorDiscordProvider } = await import("./provider.js");
|
||||||
|
|
||||||
|
await monitorDiscordProvider({
|
||||||
|
config: baseConfig(),
|
||||||
|
runtime: baseRuntime(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(clientConstructorOptionsMock).toHaveBeenCalledTimes(1);
|
||||||
|
const opts = clientConstructorOptionsMock.mock.calls[0]?.[0] as {
|
||||||
|
eventQueue?: { listenerTimeout?: number };
|
||||||
|
};
|
||||||
|
expect(opts.eventQueue).toBeDefined();
|
||||||
|
expect(opts.eventQueue?.listenerTimeout).toBe(120_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forwards custom eventQueue config from discord config to Carbon Client", async () => {
|
||||||
|
const { monitorDiscordProvider } = await import("./provider.js");
|
||||||
|
|
||||||
|
resolveDiscordAccountMock.mockImplementation(() => ({
|
||||||
|
accountId: "default",
|
||||||
|
token: "cfg-token",
|
||||||
|
config: {
|
||||||
|
commands: { native: true, nativeSkills: false },
|
||||||
|
voice: { enabled: false },
|
||||||
|
agentComponents: { enabled: false },
|
||||||
|
execApprovals: { enabled: false },
|
||||||
|
eventQueue: { listenerTimeout: 300_000 },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
await monitorDiscordProvider({
|
||||||
|
config: baseConfig(),
|
||||||
|
runtime: baseRuntime(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(clientConstructorOptionsMock).toHaveBeenCalledTimes(1);
|
||||||
|
const opts = clientConstructorOptionsMock.mock.calls[0]?.[0] as {
|
||||||
|
eventQueue?: { listenerTimeout?: number };
|
||||||
|
};
|
||||||
|
expect(opts.eventQueue?.listenerTimeout).toBe(300_000);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -517,6 +517,12 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
if (voiceEnabled) {
|
if (voiceEnabled) {
|
||||||
clientPlugins.push(new VoicePlugin());
|
clientPlugins.push(new VoicePlugin());
|
||||||
}
|
}
|
||||||
|
// Pass eventQueue config to Carbon so the listener timeout can be tuned.
|
||||||
|
// Default listenerTimeout is 120s (Carbon defaults to 30s which is too short for LLM calls).
|
||||||
|
const eventQueueOpts = {
|
||||||
|
listenerTimeout: 120_000,
|
||||||
|
...discordCfg.eventQueue,
|
||||||
|
};
|
||||||
const client = new Client(
|
const client = new Client(
|
||||||
{
|
{
|
||||||
baseUrl: "http://localhost",
|
baseUrl: "http://localhost",
|
||||||
@@ -525,6 +531,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
publicKey: "a",
|
publicKey: "a",
|
||||||
token,
|
token,
|
||||||
autoDeploy: false,
|
autoDeploy: false,
|
||||||
|
eventQueue: eventQueueOpts,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
commands,
|
commands,
|
||||||
|
|||||||
Reference in New Issue
Block a user