fix: honor twitch default setup account

This commit is contained in:
Tak Hoffman
2026-04-03 14:52:26 -05:00
parent d007559c38
commit f8a0f9ffd3
2 changed files with 92 additions and 21 deletions

View File

@@ -201,5 +201,60 @@ describe("setup surface helpers", () => {
expect(result?.cfg.channels?.twitch?.accounts?.default?.username).toBe("testbot"); expect(result?.cfg.channels?.twitch?.accounts?.default?.username).toBe("testbot");
expect(result?.cfg.channels?.twitch?.accounts?.default?.clientId).toBe("test-client-id"); expect(result?.cfg.channels?.twitch?.accounts?.default?.clientId).toBe("test-client-id");
}); });
it("writes env-token setup to the configured default account", async () => {
const { configureWithEnvToken } = await import("./setup-surface.js");
mockPromptConfirm.mockReset().mockResolvedValue(true as never);
mockPromptText
.mockReset()
.mockResolvedValueOnce("secondary-bot" as never)
.mockResolvedValueOnce("secondary-client" as never);
const result = await configureWithEnvToken(
{
channels: {
twitch: {
defaultAccount: "secondary",
},
},
} as Parameters<typeof configureWithEnvToken>[0],
mockPrompter,
null,
"oauth:fromenv",
false,
{} as Parameters<typeof configureWithEnvToken>[5],
);
expect(result?.cfg.channels?.twitch?.accounts?.secondary?.username).toBe("secondary-bot");
expect(result?.cfg.channels?.twitch?.accounts?.secondary?.clientId).toBe("secondary-client");
expect(result?.cfg.channels?.twitch?.accounts?.default).toBeUndefined();
});
});
describe("defaultAccount setup resolution", () => {
it("reports status for the configured default account", async () => {
const { twitchSetupWizard } = await import("./setup-surface.js");
const lines = twitchSetupWizard.status?.resolveStatusLines?.({
cfg: {
channels: {
twitch: {
defaultAccount: "secondary",
accounts: {
secondary: {
username: "secondary-bot",
accessToken: "oauth:secondary",
clientId: "secondary-client",
channel: "#secondary",
},
},
},
},
},
} as never);
expect(lines).toEqual(["Twitch (secondary): configured"]);
});
}); });
}); });

View File

@@ -10,17 +10,27 @@ import {
type OpenClawConfig, type OpenClawConfig,
type WizardPrompter, type WizardPrompter,
} from "openclaw/plugin-sdk/setup"; } from "openclaw/plugin-sdk/setup";
import { DEFAULT_ACCOUNT_ID, getAccountConfig } from "./config.js"; import {
DEFAULT_ACCOUNT_ID,
getAccountConfig,
resolveDefaultTwitchAccountId,
} from "./config.js";
import type { TwitchAccountConfig, TwitchRole } from "./types.js"; import type { TwitchAccountConfig, TwitchRole } from "./types.js";
import { isAccountConfigured } from "./utils/twitch.js"; import { isAccountConfigured } from "./utils/twitch.js";
const channel = "twitch" as const; const channel = "twitch" as const;
function resolveSetupAccountId(cfg: OpenClawConfig): string {
const preferred = cfg.channels?.twitch?.defaultAccount?.trim();
return preferred || resolveDefaultTwitchAccountId(cfg);
}
export function setTwitchAccount( export function setTwitchAccount(
cfg: OpenClawConfig, cfg: OpenClawConfig,
account: Partial<TwitchAccountConfig>, account: Partial<TwitchAccountConfig>,
accountId: string = resolveSetupAccountId(cfg),
): OpenClawConfig { ): OpenClawConfig {
const existing = getAccountConfig(cfg, DEFAULT_ACCOUNT_ID); const existing = getAccountConfig(cfg, accountId);
const merged: TwitchAccountConfig = { const merged: TwitchAccountConfig = {
username: account.username ?? existing?.username ?? "", username: account.username ?? existing?.username ?? "",
accessToken: account.accessToken ?? existing?.accessToken ?? "", accessToken: account.accessToken ?? existing?.accessToken ?? "",
@@ -49,7 +59,7 @@ export function setTwitchAccount(
...(( ...((
(cfg.channels as Record<string, unknown>)?.twitch as Record<string, unknown> | undefined (cfg.channels as Record<string, unknown>)?.twitch as Record<string, unknown> | undefined
)?.accounts as Record<string, unknown> | undefined), )?.accounts as Record<string, unknown> | undefined),
[DEFAULT_ACCOUNT_ID]: merged, [accountId]: merged,
}, },
}, },
}, },
@@ -217,7 +227,8 @@ function setTwitchAccessControl(
allowedRoles: TwitchRole[], allowedRoles: TwitchRole[],
requireMention: boolean, requireMention: boolean,
): OpenClawConfig { ): OpenClawConfig {
const account = getAccountConfig(cfg, DEFAULT_ACCOUNT_ID); const accountId = resolveSetupAccountId(cfg);
const account = getAccountConfig(cfg, accountId);
if (!account) { if (!account) {
return cfg; return cfg;
} }
@@ -226,11 +237,11 @@ function setTwitchAccessControl(
...account, ...account,
allowedRoles, allowedRoles,
requireMention, requireMention,
}); }, accountId);
} }
function resolveTwitchGroupPolicy(cfg: OpenClawConfig): "open" | "allowlist" | "disabled" { function resolveTwitchGroupPolicy(cfg: OpenClawConfig): "open" | "allowlist" | "disabled" {
const account = getAccountConfig(cfg, DEFAULT_ACCOUNT_ID); const account = getAccountConfig(cfg, resolveSetupAccountId(cfg));
if (account?.allowedRoles?.includes("all")) { if (account?.allowedRoles?.includes("all")) {
return "open"; return "open";
} }
@@ -253,9 +264,9 @@ const twitchDmPolicy: ChannelSetupDmPolicy = {
label: "Twitch", label: "Twitch",
channel, channel,
policyKey: "channels.twitch.allowedRoles", policyKey: "channels.twitch.allowedRoles",
allowFromKey: "channels.twitch.accounts.default.allowFrom", allowFromKey: "channels.twitch.accounts.<default>.allowFrom",
getCurrent: (cfg) => { getCurrent: (cfg) => {
const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); const account = getAccountConfig(cfg as OpenClawConfig, resolveSetupAccountId(cfg as OpenClawConfig));
if (account?.allowedRoles?.includes("all")) { if (account?.allowedRoles?.includes("all")) {
return "open"; return "open";
} }
@@ -270,7 +281,8 @@ const twitchDmPolicy: ChannelSetupDmPolicy = {
return setTwitchAccessControl(cfg as OpenClawConfig, allowedRoles, true); return setTwitchAccessControl(cfg as OpenClawConfig, allowedRoles, true);
}, },
promptAllowFrom: async ({ cfg, prompter }) => { promptAllowFrom: async ({ cfg, prompter }) => {
const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); const accountId = resolveSetupAccountId(cfg as OpenClawConfig);
const account = getAccountConfig(cfg as OpenClawConfig, accountId);
const existingAllowFrom = account?.allowFrom ?? []; const existingAllowFrom = account?.allowFrom ?? [];
const entry = await prompter.text({ const entry = await prompter.text({
@@ -287,7 +299,7 @@ const twitchDmPolicy: ChannelSetupDmPolicy = {
return setTwitchAccount(cfg as OpenClawConfig, { return setTwitchAccount(cfg as OpenClawConfig, {
...(account ?? undefined), ...(account ?? undefined),
allowFrom, allowFrom,
}); }, accountId);
}, },
}; };
@@ -297,11 +309,11 @@ const twitchGroupAccess: NonNullable<ChannelSetupWizard["groupAccess"]> = {
skipAllowlistEntries: true, skipAllowlistEntries: true,
currentPolicy: ({ cfg }) => resolveTwitchGroupPolicy(cfg as OpenClawConfig), currentPolicy: ({ cfg }) => resolveTwitchGroupPolicy(cfg as OpenClawConfig),
currentEntries: ({ cfg }) => { currentEntries: ({ cfg }) => {
const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); const account = getAccountConfig(cfg as OpenClawConfig, resolveSetupAccountId(cfg as OpenClawConfig));
return account?.allowFrom ?? []; return account?.allowFrom ?? [];
}, },
updatePrompt: ({ cfg }) => { updatePrompt: ({ cfg }) => {
const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); const account = getAccountConfig(cfg as OpenClawConfig, resolveSetupAccountId(cfg as OpenClawConfig));
return Boolean(account?.allowedRoles?.length || account?.allowFrom?.length); return Boolean(account?.allowedRoles?.length || account?.allowFrom?.length);
}, },
setPolicy: ({ cfg, policy }) => setTwitchGroupPolicy(cfg as OpenClawConfig, policy), setPolicy: ({ cfg, policy }) => setTwitchGroupPolicy(cfg as OpenClawConfig, policy),
@@ -310,16 +322,16 @@ const twitchGroupAccess: NonNullable<ChannelSetupWizard["groupAccess"]> = {
}; };
export const twitchSetupAdapter: ChannelSetupAdapter = { export const twitchSetupAdapter: ChannelSetupAdapter = {
resolveAccountId: () => DEFAULT_ACCOUNT_ID, resolveAccountId: ({ cfg }) => resolveSetupAccountId(cfg as OpenClawConfig),
applyAccountConfig: ({ cfg }) => applyAccountConfig: ({ cfg, accountId }) =>
setTwitchAccount(cfg, { setTwitchAccount(cfg, {
enabled: true, enabled: true,
}), }, accountId),
}; };
export const twitchSetupWizard: ChannelSetupWizard = { export const twitchSetupWizard: ChannelSetupWizard = {
channel, channel,
resolveAccountIdForConfigure: () => DEFAULT_ACCOUNT_ID, resolveAccountIdForConfigure: ({ defaultAccountId }) => defaultAccountId,
resolveShouldPromptAccountIds: () => false, resolveShouldPromptAccountIds: () => false,
status: { status: {
configuredLabel: "configured", configuredLabel: "configured",
@@ -327,18 +339,22 @@ export const twitchSetupWizard: ChannelSetupWizard = {
configuredHint: "configured", configuredHint: "configured",
unconfiguredHint: "needs setup", unconfiguredHint: "needs setup",
resolveConfigured: ({ cfg }) => { resolveConfigured: ({ cfg }) => {
const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); const account = getAccountConfig(cfg as OpenClawConfig, resolveSetupAccountId(cfg as OpenClawConfig));
return account ? isAccountConfigured(account) : false; return account ? isAccountConfigured(account) : false;
}, },
resolveStatusLines: ({ cfg }) => { resolveStatusLines: ({ cfg }) => {
const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); const accountId = resolveSetupAccountId(cfg as OpenClawConfig);
const account = getAccountConfig(cfg as OpenClawConfig, accountId);
const configured = account ? isAccountConfigured(account) : false; const configured = account ? isAccountConfigured(account) : false;
return [`Twitch: ${configured ? "configured" : "needs username, token, and clientId"}`]; return [
`Twitch${accountId !== DEFAULT_ACCOUNT_ID ? ` (${accountId})` : ""}: ${configured ? "configured" : "needs username, token, and clientId"}`,
];
}, },
}, },
credentials: [], credentials: [],
finalize: async ({ cfg, prompter, forceAllowFrom }) => { finalize: async ({ cfg, prompter, forceAllowFrom }) => {
const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); const accountId = resolveSetupAccountId(cfg as OpenClawConfig);
const account = getAccountConfig(cfg as OpenClawConfig, accountId);
if (!account || !isAccountConfigured(account)) { if (!account || !isAccountConfigured(account)) {
await noteTwitchSetupHelp(prompter); await noteTwitchSetupHelp(prompter);
@@ -374,7 +390,7 @@ export const twitchSetupWizard: ChannelSetupWizard = {
clientSecret, clientSecret,
refreshToken, refreshToken,
enabled: true, enabled: true,
}); }, accountId);
const cfgWithAllowFrom = const cfgWithAllowFrom =
forceAllowFrom && twitchDmPolicy.promptAllowFrom forceAllowFrom && twitchDmPolicy.promptAllowFrom