mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:50:42 +00:00
Twitch: align setup entry config
This commit is contained in:
@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
|
||||
- OpenAI Codex/OAuth: keep OpenClaw as the canonical owner for imported Codex CLI OAuth sessions, stop writing refreshed credentials back into `.codex`, and prefer fresher OpenClaw credentials over stale imported CLI state so refresh recovery stays stable. Thanks @vincentkoc.
|
||||
- OpenAI Codex/OAuth: treat the OpenAI TLS prerequisites probe as advisory instead of a hard blocker, so Codex sign-in can still proceed when the speculative Node/OpenSSL precheck fails but the real OAuth flow still works. Thanks @vincentkoc.
|
||||
- Models status/OAuth health: align OAuth health reporting with the same effective credential view runtime uses, so expired refreshable sessions stop showing healthy by default and fresher imported Codex CLI credentials surface correctly in `models status`, doctor, and gateway auth status. Thanks @vincentkoc.
|
||||
- Twitch/setup: load Twitch through the bundled setup-entry discovery path and keep setup/status account detection aligned with runtime config. (#68008) Thanks @gumadeiras.
|
||||
|
||||
## 2026.4.15
|
||||
|
||||
|
||||
20
extensions/twitch/index.test.ts
Normal file
20
extensions/twitch/index.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts";
|
||||
import entry from "./index.js";
|
||||
import setupEntry from "./setup-entry.js";
|
||||
|
||||
describe("twitch bundled entries", () => {
|
||||
assertBundledChannelEntries({
|
||||
entry,
|
||||
expectedId: "twitch",
|
||||
expectedName: "Twitch",
|
||||
setupEntry,
|
||||
});
|
||||
|
||||
it("loads the setup-only channel plugin", () => {
|
||||
const plugin = setupEntry.loadSetupPlugin?.();
|
||||
|
||||
expect(plugin?.id).toBe("twitch");
|
||||
expect(plugin?.setupWizard).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
promptRefreshTokenSetup,
|
||||
promptToken,
|
||||
promptUsername,
|
||||
twitchSetupPlugin,
|
||||
twitchSetupWizard,
|
||||
} from "./setup-surface.js";
|
||||
import type { TwitchAccountConfig } from "./types.js";
|
||||
@@ -31,6 +32,7 @@ const mockPrompter: WizardPrompter = {
|
||||
text: mockPromptText,
|
||||
confirm: mockPromptConfirm,
|
||||
} as unknown as WizardPrompter;
|
||||
const originalEnvToken = process.env.OPENCLAW_TWITCH_ACCESS_TOKEN;
|
||||
|
||||
const mockAccount: TwitchAccountConfig = {
|
||||
username: "testbot",
|
||||
@@ -45,6 +47,11 @@ describe("setup surface helpers", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalEnvToken === undefined) {
|
||||
delete process.env.OPENCLAW_TWITCH_ACCESS_TOKEN;
|
||||
} else {
|
||||
process.env.OPENCLAW_TWITCH_ACCESS_TOKEN = originalEnvToken;
|
||||
}
|
||||
// Don't restoreAllMocks as it breaks module-level mocks
|
||||
});
|
||||
|
||||
@@ -251,5 +258,57 @@ describe("setup surface helpers", () => {
|
||||
|
||||
expect(lines).toEqual(["Twitch (secondary): configured"]);
|
||||
});
|
||||
|
||||
it("reports env-token default account setup as configured", async () => {
|
||||
process.env.OPENCLAW_TWITCH_ACCESS_TOKEN = "oauth:fromenv";
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
username: "env-bot",
|
||||
accessToken: "",
|
||||
clientId: "env-client",
|
||||
channel: "#env",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Parameters<NonNullable<typeof twitchSetupWizard.status>["resolveConfigured"]>[0]["cfg"];
|
||||
|
||||
expect(twitchSetupWizard.status?.resolveConfigured({ cfg })).toBe(true);
|
||||
const account = twitchSetupPlugin.config.resolveAccount(cfg, "default");
|
||||
expect(await twitchSetupPlugin.config.isConfigured?.(account, cfg)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setup-only plugin config", () => {
|
||||
it("lists all configured Twitch accounts", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
twitch: {
|
||||
defaultAccount: "secondary",
|
||||
accounts: {
|
||||
default: {
|
||||
username: "default-bot",
|
||||
accessToken: "oauth:default",
|
||||
clientId: "default-client",
|
||||
channel: "#default",
|
||||
},
|
||||
secondary: {
|
||||
username: "secondary-bot",
|
||||
accessToken: "oauth:secondary",
|
||||
clientId: "secondary-client",
|
||||
channel: "#secondary",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Parameters<typeof twitchSetupPlugin.config.listAccountIds>[0];
|
||||
|
||||
expect(twitchSetupPlugin.config.listAccountIds(cfg)).toEqual(["default", "secondary"]);
|
||||
expect(twitchSetupPlugin.config.defaultAccountId?.(cfg)).toBe("secondary");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,13 @@ import {
|
||||
type OpenClawConfig,
|
||||
type WizardPrompter,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { DEFAULT_ACCOUNT_ID, getAccountConfig, resolveDefaultTwitchAccountId } from "./config.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
getAccountConfig,
|
||||
listAccountIds,
|
||||
resolveDefaultTwitchAccountId,
|
||||
resolveTwitchAccountContext,
|
||||
} from "./config.js";
|
||||
import type { TwitchAccountConfig, TwitchRole } from "./types.js";
|
||||
import { isAccountConfigured } from "./utils/twitch.js";
|
||||
|
||||
@@ -348,13 +354,11 @@ export const twitchSetupWizard: ChannelSetupWizard = {
|
||||
configuredHint: "configured",
|
||||
unconfiguredHint: "needs setup",
|
||||
resolveConfigured: ({ cfg }) => {
|
||||
const account = getAccountConfig(cfg, resolveSetupAccountId(cfg));
|
||||
return account ? isAccountConfigured(account) : false;
|
||||
return resolveTwitchAccountContext(cfg, resolveSetupAccountId(cfg)).configured;
|
||||
},
|
||||
resolveStatusLines: ({ cfg }) => {
|
||||
const accountId = resolveSetupAccountId(cfg);
|
||||
const account = getAccountConfig(cfg, accountId);
|
||||
const configured = account ? isAccountConfigured(account) : false;
|
||||
const configured = resolveTwitchAccountContext(cfg, accountId).configured;
|
||||
return [
|
||||
`Twitch${accountId !== DEFAULT_ACCOUNT_ID ? ` (${accountId})` : ""}: ${configured ? "configured" : "needs username, token, and clientId"}`,
|
||||
];
|
||||
@@ -437,25 +441,27 @@ export const twitchSetupPlugin: ChannelPlugin<ResolvedTwitchAccount> = {
|
||||
chatTypes: ["group"],
|
||||
},
|
||||
config: {
|
||||
listAccountIds: (cfg) => {
|
||||
const accountId = resolveSetupAccountId(cfg);
|
||||
return getAccountConfig(cfg, accountId) ? [accountId] : [];
|
||||
},
|
||||
listAccountIds: (cfg) => listAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) => {
|
||||
const resolvedAccountId = accountId ?? resolveSetupAccountId(cfg);
|
||||
const resolvedAccountId = accountId ?? resolveDefaultTwitchAccountId(cfg);
|
||||
const account = getAccountConfig(cfg, resolvedAccountId);
|
||||
const fallback = {
|
||||
if (!account) {
|
||||
return {
|
||||
accountId: resolvedAccountId,
|
||||
username: "",
|
||||
accessToken: "",
|
||||
clientId: "",
|
||||
channel: "",
|
||||
enabled: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
accountId: resolvedAccountId,
|
||||
username: "",
|
||||
accessToken: "",
|
||||
clientId: "",
|
||||
channel: "",
|
||||
enabled: false,
|
||||
...account,
|
||||
};
|
||||
return account ? { ...fallback, ...account } : fallback;
|
||||
},
|
||||
defaultAccountId: (cfg) => resolveSetupAccountId(cfg),
|
||||
isConfigured: (account) => isAccountConfigured(account),
|
||||
defaultAccountId: (cfg) => resolveDefaultTwitchAccountId(cfg),
|
||||
isConfigured: (account, cfg) => resolveTwitchAccountContext(cfg, account?.accountId).configured,
|
||||
isEnabled: (account) => account.enabled !== false,
|
||||
},
|
||||
setup: twitchSetupAdapter,
|
||||
|
||||
Reference in New Issue
Block a user