feat(discord): add set-presence action for bot activity and status

Bridge the agent tools layer to the Discord gateway WebSocket via a new
gateway registry, allowing agents to set the bot's activity and online
status. Supports playing, streaming, listening, watching, custom, and
competing activity types. Custom type uses activityState as the sidebar
text; other types show activityName in the sidebar and activityState in
the flyout. Opt-in via channels.discord.actions.presence (default false).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michelle Tilley
2026-02-03 14:00:11 -08:00
committed by clawdinator[bot]
parent b64c1a56a1
commit 5af322f710
15 changed files with 564 additions and 2 deletions

View File

@@ -0,0 +1,55 @@
import type { GatewayPlugin } from "@buape/carbon/gateway";
import { beforeEach, describe, expect, it } from "vitest";
import {
clearGateways,
getGateway,
registerGateway,
unregisterGateway,
} from "./gateway-registry.js";
function fakeGateway(props: Partial<GatewayPlugin> = {}): GatewayPlugin {
return { isConnected: true, ...props } as unknown as GatewayPlugin;
}
describe("gateway-registry", () => {
beforeEach(() => {
clearGateways();
});
it("stores and retrieves a gateway by account", () => {
const gateway = fakeGateway();
registerGateway("account-a", gateway);
expect(getGateway("account-a")).toBe(gateway);
expect(getGateway("account-b")).toBeUndefined();
});
it("uses 'default' key when accountId is undefined", () => {
const gateway = fakeGateway();
registerGateway(undefined, gateway);
expect(getGateway(undefined)).toBe(gateway);
expect(getGateway("default")).toBe(gateway);
});
it("unregisters a gateway", () => {
const gateway = fakeGateway();
registerGateway("account-a", gateway);
unregisterGateway("account-a");
expect(getGateway("account-a")).toBeUndefined();
});
it("clears all gateways", () => {
registerGateway("a", fakeGateway());
registerGateway("b", fakeGateway());
clearGateways();
expect(getGateway("a")).toBeUndefined();
expect(getGateway("b")).toBeUndefined();
});
it("overwrites existing entry for same account", () => {
const gateway1 = fakeGateway({ isConnected: true });
const gateway2 = fakeGateway({ isConnected: false });
registerGateway("account-a", gateway1);
registerGateway("account-a", gateway2);
expect(getGateway("account-a")).toBe(gateway2);
});
});