diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a83d1843ea..cc493178efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,7 +98,7 @@ Docs: https://docs.openclaw.ai - WhatsApp/Cron: keep DM pairing-store approvals out of implicit cron and heartbeat recipient fallback, so scheduled automation only uses explicit targets, active configured recipients, or configured `allowFrom` entries. Fixes #62339. Thanks @kelvinisly-collab. - Google Meet: keep the agent-facing `google_meet` tool visible on non-macOS hosts but block local Chrome realtime actions with guidance, so Linux agents can still use transcribe, Twilio, chrome-node, and artifact flows without choosing the macOS-only BlackHole path. Refs #75950. Thanks @actual-software-inc. - macOS/settings: keep opening General from rewriting `openclaw.json` during Tailscale settings hydration, preserving `gateway`, `auth`, `meta`, and `wizard` until the user changes a setting. Fixes #59545. Thanks @Tengdw. -- Discord: prioritize interaction callbacks ahead of stale background REST work, validate oversized gateway payloads and member-intent requests before send, and forward explicit component payloads from message actions. (#75363) +- Discord: prioritize interaction callbacks ahead of stale background REST work without polling active REST buckets, validate oversized gateway payloads and member-intent requests before send, and forward explicit component payloads from message actions. (#75363) - Active Memory: use the configured recall timeout as the blocking prompt-build hook budget by default and move cold-start setup grace behind explicit `setupGraceTimeoutMs` config, so the plugin no longer silently extends 15000 ms configs to 45000 ms on the main lane. Fixes #75843. Thanks @vishutdhar. - Plugins/web-provider: reuse the active gateway plugin registry for runtime web provider resolution after deriving the same candidate plugin ids as the loader path, avoiding a redundant `loadOpenClawPlugins` call on every request while preserving origin and scope filters. Fixes #75513. Thanks @jochen. - Crestodian/CLI: exit non-zero when interactive Crestodian is invoked without a TTY, so scripts and CI no longer treat the setup error as success. Fixes #73646 and supersedes #73928 and #74059. Thanks @bittoby, @luyao618, and @Linux2010. diff --git a/extensions/discord/src/internal/rest-scheduler.ts b/extensions/discord/src/internal/rest-scheduler.ts index c342e144421..2d68abd16f6 100644 --- a/extensions/discord/src/internal/rest-scheduler.ts +++ b/extensions/discord/src/internal/rest-scheduler.ts @@ -408,7 +408,6 @@ export class RestScheduler { continue; } if (bucket.active > 0) { - nextDelayMs = Math.min(nextDelayMs ?? 5, 5); continue; } const waitMs = this.getBucketWaitMs(bucket, now); diff --git a/extensions/discord/src/internal/rest.test.ts b/extensions/discord/src/internal/rest.test.ts index d954600dafd..ef58e940487 100644 --- a/extensions/discord/src/internal/rest.test.ts +++ b/extensions/discord/src/internal/rest.test.ts @@ -165,6 +165,41 @@ describe("RequestClient", () => { ); }); + it("drains same-bucket requests when the active request finishes without polling", async () => { + vi.useFakeTimers(); + vi.setSystemTime(0); + const firstResponse = createDeferred(); + const fetchSpy = vi.fn(async () => + fetchSpy.mock.calls.length === 1 + ? await firstResponse.promise + : createJsonResponse({ id: "second" }), + ); + const client = new RequestClient("test-token", { + fetch: fetchSpy, + scheduler: { maxConcurrency: 2 }, + }); + + const first = client.get("/channels/c1/messages"); + await Promise.resolve(); + expect(fetchSpy).toHaveBeenCalledTimes(1); + + const second = client.get("/channels/c1/messages"); + await Promise.resolve(); + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(vi.getTimerCount()).toBe(1); + + await vi.advanceTimersByTimeAsync(20); + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(vi.getTimerCount()).toBe(1); + + firstResponse.resolve(createJsonResponse({ id: "first" })); + + await expect(first).resolves.toEqual({ id: "first" }); + await expect(second).resolves.toEqual({ id: "second" }); + expect(fetchSpy).toHaveBeenCalledTimes(2); + expect(vi.getTimerCount()).toBe(0); + }); + it("runs independent route buckets concurrently", async () => { const channelResponse = createDeferred(); const guildResponse = createDeferred();