mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
2c8ed54ddb
Unset heartbeat.target now resolves "owner": elected heartbeat notifications deliver to the operator's DM resolved from commands.ownerAllowFrom or the channel allowFrom (first concrete entry; wildcards and channel-scoped wildcards excluded; configured owners exhausted across channels before any channel-local fallback). Delivery requires the channel's own classifier to positively prove a direct destination — every bundled messaging plugin now ships an inferTargetChatType contract — and unproven or group-shaped destinations fail closed to the visible no-route state. The first implicitly-routed delivery carries a one-line self-explanation naming the target: "none" opt-out. Explicit target "last" remains as the follow-the-conversation opt-in. Refines the unreleased #121892 default before it ships; refs #121880. Co-authored-by: Ayaan Zaidi <hi@obviy.us> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
188 lines
5.6 KiB
TypeScript
188 lines
5.6 KiB
TypeScript
// Twitch tests cover plugin plugin behavior.
|
|
import { describe, expect, it } from "vitest";
|
|
import type { OpenClawConfig } from "../api.js";
|
|
import { twitchPlugin } from "./plugin.js";
|
|
import { twitchSetupPlugin } from "./setup-surface.js";
|
|
|
|
describe("twitchPlugin pairing", () => {
|
|
it("classifies only channel targets as groups", () => {
|
|
expect(twitchPlugin.messaging?.inferTargetChatType?.({ to: "twitch:openclaw" })).toBe("group");
|
|
expect(
|
|
twitchPlugin.messaging?.inferTargetChatType?.({ to: "twitch:user:operator" }),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it("normalizes trimmed twitch user prefixes in allow entries", () => {
|
|
expect(twitchPlugin.pairing?.normalizeAllowEntry?.(" twitch:user:123456 ")).toBe("123456");
|
|
expect(twitchPlugin.pairing?.normalizeAllowEntry?.(" user789012 ")).toBe("789012");
|
|
});
|
|
});
|
|
|
|
describe("twitchPlugin outbound session routing", () => {
|
|
it("reproduces the canonical inbound channel session", async () => {
|
|
const route = await twitchPlugin.messaging?.resolveOutboundSessionRoute?.({
|
|
cfg: {},
|
|
agentId: "ops",
|
|
accountId: "stream",
|
|
target: "twitch:channel:OpenClaw",
|
|
});
|
|
|
|
expect(route).toMatchObject({
|
|
sessionKey: "agent:ops:twitch:group:openclaw",
|
|
baseSessionKey: "agent:ops:twitch:group:openclaw",
|
|
recipientSessionExact: true,
|
|
peer: { kind: "group", id: "openclaw" },
|
|
chatType: "group",
|
|
to: "openclaw",
|
|
});
|
|
});
|
|
|
|
it.each(["twitch:user:alice", "twitch:dm:alice"])(
|
|
"rejects unsupported direct target %s",
|
|
async (target) => {
|
|
const route = await twitchPlugin.messaging?.resolveOutboundSessionRoute?.({
|
|
cfg: {},
|
|
agentId: "ops",
|
|
target,
|
|
});
|
|
|
|
expect(route).toBeNull();
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("twitchPlugin.status.buildAccountSnapshot", () => {
|
|
it("uses the resolved account ID for multi-account configs", async () => {
|
|
const secondary = {
|
|
channel: "secondary-channel",
|
|
username: "secondary",
|
|
accessToken: "oauth:secondary-token",
|
|
clientId: "secondary-client",
|
|
enabled: true,
|
|
};
|
|
|
|
const cfg = {
|
|
channels: {
|
|
twitch: {
|
|
accounts: {
|
|
default: {
|
|
channel: "default-channel",
|
|
username: "default",
|
|
accessToken: "oauth:default-token",
|
|
clientId: "default-client",
|
|
enabled: true,
|
|
},
|
|
secondary,
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const snapshot = await twitchPlugin.status?.buildAccountSnapshot?.({
|
|
account: twitchPlugin.config.resolveAccount(cfg, "secondary"),
|
|
cfg,
|
|
});
|
|
|
|
expect(snapshot?.accountId).toBe("secondary");
|
|
});
|
|
});
|
|
|
|
describe("twitchPlugin.config", () => {
|
|
it("uses configured defaultAccount for omitted-account plugin resolution", () => {
|
|
const cfg = {
|
|
channels: {
|
|
twitch: {
|
|
defaultAccount: "secondary",
|
|
accounts: {
|
|
default: {
|
|
channel: "default-channel",
|
|
username: "default",
|
|
accessToken: "oauth:default-token",
|
|
clientId: "default-client",
|
|
enabled: true,
|
|
},
|
|
secondary: {
|
|
channel: "secondary-channel",
|
|
username: "secondary",
|
|
accessToken: "oauth:secondary-token",
|
|
clientId: "secondary-client",
|
|
enabled: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
expect(twitchPlugin.config.defaultAccountId?.(cfg)).toBe("secondary");
|
|
expect(twitchPlugin.config.resolveAccount(cfg).accountId).toBe("secondary");
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "does not borrow the configured default account's token",
|
|
defaultToken: "oauth:default-token",
|
|
selectedToken: "",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "does not reject a configured named account because the default lacks a token",
|
|
defaultToken: "",
|
|
selectedToken: "oauth:secondary-token",
|
|
expected: true,
|
|
},
|
|
])("$name", ({ defaultToken, selectedToken, expected }) => {
|
|
const cfg = {
|
|
channels: {
|
|
twitch: {
|
|
accounts: {
|
|
default: {
|
|
username: "default",
|
|
accessToken: defaultToken,
|
|
clientId: "default-client",
|
|
channel: "default-channel",
|
|
},
|
|
secondary: {
|
|
username: "secondary",
|
|
accessToken: selectedToken,
|
|
clientId: "secondary-client",
|
|
channel: "secondary-channel",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
for (const config of [twitchPlugin.config, twitchSetupPlugin.config]) {
|
|
const account = config.resolveAccount(cfg, "secondary");
|
|
expect(account.accountId).toBe("secondary");
|
|
expect(config.isConfigured?.(account, cfg)).toBe(expected);
|
|
}
|
|
});
|
|
|
|
it("normalizes named accounts consistently across runtime and setup", () => {
|
|
const cfg = {
|
|
channels: {
|
|
twitch: {
|
|
accounts: {
|
|
Secondary: {
|
|
username: "secondary",
|
|
accessToken: "oauth:secondary-token",
|
|
clientId: "secondary-client",
|
|
channel: "secondary-channel",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
for (const config of [twitchPlugin.config, twitchSetupPlugin.config]) {
|
|
const account = config.resolveAccount(cfg, "SECONDARY\r\n");
|
|
expect(account).toMatchObject({
|
|
accountId: "secondary",
|
|
username: "secondary",
|
|
});
|
|
expect(config.isConfigured?.(account, cfg)).toBe(true);
|
|
}
|
|
});
|
|
});
|