From f56a9f3b3bbeffbee70b5934634eb8a266b9fd2f Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:46:18 -0500 Subject: [PATCH] fix: honor twitch default runtime account --- extensions/twitch/src/config.test.ts | 50 +++++++++++++++++++++++++++- extensions/twitch/src/config.ts | 17 +++++++++- extensions/twitch/src/plugin.test.ts | 31 +++++++++++++++++ extensions/twitch/src/plugin.ts | 9 ++--- 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/extensions/twitch/src/config.test.ts b/extensions/twitch/src/config.test.ts index b798fd5652a..8cb7f02c468 100644 --- a/extensions/twitch/src/config.test.ts +++ b/extensions/twitch/src/config.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from "vitest"; -import { getAccountConfig, listAccountIds } from "./config.js"; +import { + getAccountConfig, + listAccountIds, + resolveDefaultTwitchAccountId, + resolveTwitchAccountContext, +} from "./config.js"; describe("getAccountConfig", () => { const mockMultiAccountConfig = { @@ -116,3 +121,46 @@ describe("listAccountIds", () => { ).toEqual(["default", "secondary"]); }); }); + +describe("resolveDefaultTwitchAccountId", () => { + it("prefers channels.twitch.defaultAccount when configured", () => { + expect( + resolveDefaultTwitchAccountId({ + channels: { + twitch: { + defaultAccount: "secondary", + accounts: { + default: { username: "default" }, + secondary: { username: "secondary" }, + }, + }, + }, + } as Parameters[0]), + ).toBe("secondary"); + }); +}); + +describe("resolveTwitchAccountContext", () => { + it("uses configured defaultAccount when accountId is omitted", () => { + const context = resolveTwitchAccountContext({ + channels: { + twitch: { + defaultAccount: "secondary", + accounts: { + default: { + username: "default-bot", + accessToken: "oauth:default-token", + }, + secondary: { + username: "second-bot", + accessToken: "oauth:second-token", + }, + }, + }, + }, + } as Parameters[0]); + + expect(context.accountId).toBe("secondary"); + expect(context.account?.username).toBe("second-bot"); + }); +}); diff --git a/extensions/twitch/src/config.ts b/extensions/twitch/src/config.ts index 4f3e956594f..56aa4fa4ba2 100644 --- a/extensions/twitch/src/config.ts +++ b/extensions/twitch/src/config.ts @@ -118,11 +118,26 @@ export function listAccountIds(cfg: OpenClawConfig): string[] { }); } +export function resolveDefaultTwitchAccountId(cfg: OpenClawConfig): string { + const preferred = + typeof cfg.channels?.twitch?.defaultAccount === "string" + ? cfg.channels.twitch.defaultAccount.trim() + : ""; + const ids = listAccountIds(cfg); + if (preferred && ids.includes(preferred)) { + return preferred; + } + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } + return ids[0] ?? DEFAULT_ACCOUNT_ID; +} + export function resolveTwitchAccountContext( cfg: OpenClawConfig, accountId?: string | null, ): ResolvedTwitchAccountContext { - const resolvedAccountId = accountId?.trim() || DEFAULT_ACCOUNT_ID; + const resolvedAccountId = accountId?.trim() || resolveDefaultTwitchAccountId(cfg); const account = getAccountConfig(cfg, resolvedAccountId); const tokenResolution = resolveTwitchToken(cfg, { accountId: resolvedAccountId }); return { diff --git a/extensions/twitch/src/plugin.test.ts b/extensions/twitch/src/plugin.test.ts index 51bba82f477..a44b4c58aae 100644 --- a/extensions/twitch/src/plugin.test.ts +++ b/extensions/twitch/src/plugin.test.ts @@ -44,3 +44,34 @@ describe("twitchPlugin.status.buildAccountSnapshot", () => { 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"); + }); +}); diff --git a/extensions/twitch/src/plugin.ts b/extensions/twitch/src/plugin.ts index 630fa8caa87..9e22463de85 100644 --- a/extensions/twitch/src/plugin.ts +++ b/extensions/twitch/src/plugin.ts @@ -25,6 +25,7 @@ import { DEFAULT_ACCOUNT_ID, getAccountConfig, listAccountIds, + resolveDefaultTwitchAccountId, resolveTwitchAccountContext, resolveTwitchSnapshotAccountId, } from "./config.js"; @@ -81,7 +82,7 @@ export const twitchPlugin: ChannelPlugin = config: { listAccountIds: (cfg: OpenClawConfig): string[] => listAccountIds(cfg), resolveAccount: (cfg: OpenClawConfig, accountId?: string | null): ResolvedTwitchAccount => { - const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID; + const resolvedAccountId = accountId ?? resolveDefaultTwitchAccountId(cfg); const account = getAccountConfig(cfg, resolvedAccountId); if (!account) { return { @@ -98,9 +99,9 @@ export const twitchPlugin: ChannelPlugin = ...account, }; }, - defaultAccountId: (): string => DEFAULT_ACCOUNT_ID, + defaultAccountId: (cfg: OpenClawConfig): string => resolveDefaultTwitchAccountId(cfg), isConfigured: (_account: unknown, cfg: OpenClawConfig): boolean => - resolveTwitchAccountContext(cfg, DEFAULT_ACCOUNT_ID).configured, + resolveTwitchAccountContext(cfg).configured, isEnabled: (account: ResolvedTwitchAccount | undefined): boolean => account?.enabled !== false, describeAccount: (account: TwitchAccountConfig | undefined) => @@ -130,7 +131,7 @@ export const twitchPlugin: ChannelPlugin = kind: ChannelResolveKind; runtime: import("openclaw/plugin-sdk/runtime-env").RuntimeEnv; }): Promise => { - const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID); + const account = getAccountConfig(cfg, accountId ?? resolveDefaultTwitchAccountId(cfg)); if (!account) { return inputs.map((input) => ({ input,