🤖 codex: handle discord gateway error events (#504)
This commit is contained in:
committed by
Peter Steinberger
parent
cc94db458c
commit
b7c900739e
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Discord: stop provider when gateway reconnects are exhausted and surface errors. (#504)
|
||||||
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
|
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
|
||||||
- WhatsApp: group `/model list` output by provider for scannability. (#456) - thanks @mcinteerj
|
- WhatsApp: group `/model list` output by provider for scannability. (#456) - thanks @mcinteerj
|
||||||
- Hooks: allow per-hook model overrides for webhook/Gmail runs (e.g. GPT 5 Mini).
|
- Hooks: allow per-hook model overrides for webhook/Gmail runs (e.g. GPT 5 Mini).
|
||||||
|
|||||||
49
src/discord/monitor.gateway.test.ts
Normal file
49
src/discord/monitor.gateway.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { EventEmitter } from "node:events";
|
||||||
|
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { waitForDiscordGatewayStop } from "./monitor.js";
|
||||||
|
|
||||||
|
describe("waitForDiscordGatewayStop", () => {
|
||||||
|
it("resolves on abort and disconnects gateway", async () => {
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
const disconnect = vi.fn();
|
||||||
|
const abort = new AbortController();
|
||||||
|
|
||||||
|
const promise = waitForDiscordGatewayStop({
|
||||||
|
gateway: { emitter, disconnect },
|
||||||
|
abortSignal: abort.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(emitter.listenerCount("error")).toBe(1);
|
||||||
|
abort.abort();
|
||||||
|
|
||||||
|
await expect(promise).resolves.toBeUndefined();
|
||||||
|
expect(disconnect).toHaveBeenCalledTimes(1);
|
||||||
|
expect(emitter.listenerCount("error")).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects on gateway error and disconnects", async () => {
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
const disconnect = vi.fn();
|
||||||
|
const onGatewayError = vi.fn();
|
||||||
|
const abort = new AbortController();
|
||||||
|
const err = new Error("boom");
|
||||||
|
|
||||||
|
const promise = waitForDiscordGatewayStop({
|
||||||
|
gateway: { emitter, disconnect },
|
||||||
|
abortSignal: abort.signal,
|
||||||
|
onGatewayError,
|
||||||
|
});
|
||||||
|
|
||||||
|
emitter.emit("error", err);
|
||||||
|
|
||||||
|
await expect(promise).rejects.toThrow("boom");
|
||||||
|
expect(onGatewayError).toHaveBeenCalledWith(err);
|
||||||
|
expect(disconnect).toHaveBeenCalledTimes(1);
|
||||||
|
expect(emitter.listenerCount("error")).toBe(0);
|
||||||
|
|
||||||
|
abort.abort();
|
||||||
|
expect(disconnect).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { EventEmitter } from "node:events";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChannelType,
|
ChannelType,
|
||||||
Client,
|
Client,
|
||||||
@@ -82,6 +84,62 @@ type DiscordMediaInfo = {
|
|||||||
placeholder: string;
|
placeholder: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DiscordGatewayHandle = {
|
||||||
|
emitter?: Pick<EventEmitter, "on" | "removeListener">;
|
||||||
|
disconnect?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function waitForDiscordGatewayStop(params: {
|
||||||
|
gateway?: DiscordGatewayHandle;
|
||||||
|
abortSignal?: AbortSignal;
|
||||||
|
onGatewayError?: (err: unknown) => void;
|
||||||
|
}): Promise<void> {
|
||||||
|
const { gateway, abortSignal, onGatewayError } = params;
|
||||||
|
const emitter = gateway?.emitter;
|
||||||
|
return await new Promise<void>((resolve, reject) => {
|
||||||
|
let settled = false;
|
||||||
|
const cleanup = () => {
|
||||||
|
abortSignal?.removeEventListener("abort", onAbort);
|
||||||
|
emitter?.removeListener("error", onGatewayErrorEvent);
|
||||||
|
};
|
||||||
|
const finishResolve = () => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
cleanup();
|
||||||
|
try {
|
||||||
|
gateway?.disconnect?.();
|
||||||
|
} finally {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const finishReject = (err: unknown) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
cleanup();
|
||||||
|
try {
|
||||||
|
gateway?.disconnect?.();
|
||||||
|
} finally {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onAbort = () => {
|
||||||
|
finishResolve();
|
||||||
|
};
|
||||||
|
const onGatewayErrorEvent = (err: unknown) => {
|
||||||
|
onGatewayError?.(err);
|
||||||
|
finishReject(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (abortSignal?.aborted) {
|
||||||
|
onAbort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
||||||
|
emitter?.on("error", onGatewayErrorEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
type DiscordHistoryEntry = {
|
type DiscordHistoryEntry = {
|
||||||
sender: string;
|
sender: string;
|
||||||
body: string;
|
body: string;
|
||||||
@@ -402,18 +460,20 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
|
|
||||||
runtime.log?.(`logged in to discord${botUserId ? ` as ${botUserId}` : ""}`);
|
runtime.log?.(`logged in to discord${botUserId ? ` as ${botUserId}` : ""}`);
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
const gateway = client.getPlugin<GatewayPlugin>("gateway");
|
||||||
const onAbort = async () => {
|
const gatewayEmitter = (gateway as unknown as { emitter?: EventEmitter })
|
||||||
try {
|
?.emitter;
|
||||||
const gateway = client.getPlugin<GatewayPlugin>("gateway");
|
await waitForDiscordGatewayStop({
|
||||||
gateway?.disconnect();
|
gateway: gateway
|
||||||
} finally {
|
? {
|
||||||
resolve();
|
emitter: gatewayEmitter,
|
||||||
}
|
disconnect: () => gateway.disconnect(),
|
||||||
};
|
}
|
||||||
opts.abortSignal?.addEventListener("abort", () => {
|
: undefined,
|
||||||
void onAbort();
|
abortSignal: opts.abortSignal,
|
||||||
});
|
onGatewayError: (err) => {
|
||||||
|
runtime.error?.(danger(`discord gateway error: ${String(err)}`));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user