🤖 codex: handle discord gateway error events (#504)

This commit is contained in:
Josh Palmer
2026-01-08 19:59:06 +01:00
committed by Peter Steinberger
parent cc94db458c
commit b7c900739e
3 changed files with 122 additions and 12 deletions

View File

@@ -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).

View 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);
});
});

View File

@@ -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)}`));
},
}); });
} }