diff --git a/CHANGELOG.md b/CHANGELOG.md index ed1d616a94a..b9abd541143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Docs/tool-loop detection config keys: align `docs/tools/loop-detection.md` examples and field names with the current `tools.loopDetection` schema to prevent copy-paste validation failures from outdated keys. (#33182) Thanks @Mylszd. +- Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow. - Telegram/DM draft finalization reliability: require verified final-text draft emission before treating preview finalization as delivered, and fall back to normal payload send when final draft delivery is not confirmed (preventing missing final responses and preserving media/button delivery). (#32118) Thanks @OpenCils. - Discord/audit wildcard warnings: ignore "\*" wildcard keys when counting unresolved guild channels so doctor/status no longer warns on allow-all configs. (#33125) Thanks @thewilloftheshadow. - Discord/channel resolution: default bare numeric recipients to channels, harden allowlist numeric ID handling with safe fallbacks, and avoid inbound WS heartbeat stalls. (#33142) Thanks @thewilloftheshadow. diff --git a/src/discord/monitor/presence.test.ts b/src/discord/monitor/presence.test.ts new file mode 100644 index 00000000000..1ea06f9dc28 --- /dev/null +++ b/src/discord/monitor/presence.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; +import { resolveDiscordPresenceUpdate } from "./presence.js"; + +describe("resolveDiscordPresenceUpdate", () => { + it("returns online presence when no config is provided", () => { + const result = resolveDiscordPresenceUpdate({}); + expect(result).not.toBeNull(); + expect(result!.status).toBe("online"); + expect(result!.activities).toEqual([]); + }); + + it("uses configured status", () => { + const result = resolveDiscordPresenceUpdate({ status: "dnd" }); + expect(result!.status).toBe("dnd"); + }); + + it("includes activity when configured", () => { + const result = resolveDiscordPresenceUpdate({ activity: "Helping humans" }); + expect(result!.status).toBe("online"); + expect(result!.activities).toHaveLength(1); + expect(result!.activities[0].state).toBe("Helping humans"); + }); + + it("uses custom activity type by default", () => { + const result = resolveDiscordPresenceUpdate({ activity: "test" }); + expect(result!.activities[0].type).toBe(4); + expect(result!.activities[0].name).toBe("Custom Status"); + }); + + it("respects explicit activityType", () => { + const result = resolveDiscordPresenceUpdate({ activity: "test", activityType: 3 }); + expect(result!.activities[0].type).toBe(3); + expect(result!.activities[0].name).toBe("test"); + }); + + it("sets streaming URL for type 1", () => { + const result = resolveDiscordPresenceUpdate({ + activity: "Live", + activityType: 1, + activityUrl: "https://twitch.tv/test", + }); + expect(result!.activities[0].url).toBe("https://twitch.tv/test"); + }); +}); diff --git a/src/discord/monitor/presence.ts b/src/discord/monitor/presence.ts index 85da7c0d5bc..ed52ea7b014 100644 --- a/src/discord/monitor/presence.ts +++ b/src/discord/monitor/presence.ts @@ -21,7 +21,7 @@ export function resolveDiscordPresenceUpdate( const hasStatus = Boolean(status); if (!hasActivity && !hasStatus) { - return null; + return { since: null, activities: [], status: "online", afk: false }; } const activities: Activity[] = [];