refactor: unify onboarding secret-input prompt state wiring

This commit is contained in:
Peter Steinberger
2026-03-07 21:44:58 +00:00
parent 6b1c82c4f1
commit 5eba663c38
15 changed files with 172 additions and 60 deletions

View File

@@ -20,6 +20,7 @@ import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onb
import { configureChannelAccessWithAllowlist } from "./channel-access-configure.js";
import {
applySingleTokenPromptResult,
buildSingleChannelSecretPromptState,
parseMentionOrPrefixedId,
noteChannelLookupFailure,
noteChannelLookupSummary,
@@ -177,12 +178,15 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
cfg: next,
accountId: discordAccountId,
});
const hasConfigToken = hasConfiguredSecretInput(resolvedAccount.config.token);
const accountConfigured = Boolean(resolvedAccount.token) || hasConfigToken;
const allowEnv = discordAccountId === DEFAULT_ACCOUNT_ID;
const canUseEnv = allowEnv && !hasConfigToken && Boolean(process.env.DISCORD_BOT_TOKEN?.trim());
const tokenPromptState = buildSingleChannelSecretPromptState({
accountConfigured: Boolean(resolvedAccount.token),
hasConfigToken: hasConfiguredSecretInput(resolvedAccount.config.token),
allowEnv,
envValue: process.env.DISCORD_BOT_TOKEN,
});
if (!accountConfigured) {
if (!tokenPromptState.accountConfigured) {
await noteDiscordTokenHelp(prompter);
}
@@ -192,9 +196,9 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
providerHint: "discord",
credentialLabel: "Discord bot token",
secretInputMode: options?.secretInputMode,
accountConfigured,
canUseEnv,
hasConfigToken,
accountConfigured: tokenPromptState.accountConfigured,
canUseEnv: tokenPromptState.canUseEnv,
hasConfigToken: tokenPromptState.hasConfigToken,
envPrompt: "DISCORD_BOT_TOKEN detected. Use env var?",
keepPrompt: "Discord token already configured. Keep it?",
inputPrompt: "Enter Discord bot token",

View File

@@ -9,6 +9,7 @@ vi.mock("../../../plugin-sdk/onboarding.js", () => ({
import {
applySingleTokenPromptResult,
buildSingleChannelSecretPromptState,
normalizeAllowFromEntries,
noteChannelLookupFailure,
noteChannelLookupSummary,
@@ -104,6 +105,38 @@ async function runPromptSingleToken(params: {
});
}
describe("buildSingleChannelSecretPromptState", () => {
it("enables env path only when env is present and no config token exists", () => {
expect(
buildSingleChannelSecretPromptState({
accountConfigured: false,
hasConfigToken: false,
allowEnv: true,
envValue: "token-from-env",
}),
).toEqual({
accountConfigured: false,
hasConfigToken: false,
canUseEnv: true,
});
});
it("disables env path when config token already exists", () => {
expect(
buildSingleChannelSecretPromptState({
accountConfigured: true,
hasConfigToken: true,
allowEnv: true,
envValue: "token-from-env",
}),
).toEqual({
accountConfigured: true,
hasConfigToken: true,
canUseEnv: false,
});
});
});
async function runPromptLegacyAllowFrom(params: {
cfg?: OpenClawConfig;
channel: "discord" | "slack";

View File

@@ -452,6 +452,23 @@ export function applySingleTokenPromptResult(params: {
return next;
}
export function buildSingleChannelSecretPromptState(params: {
accountConfigured: boolean;
hasConfigToken: boolean;
allowEnv: boolean;
envValue?: string;
}): {
accountConfigured: boolean;
hasConfigToken: boolean;
canUseEnv: boolean;
} {
return {
accountConfigured: params.accountConfigured,
hasConfigToken: params.hasConfigToken,
canUseEnv: params.allowEnv && Boolean(params.envValue?.trim()) && !params.hasConfigToken,
};
}
export async function promptSingleChannelToken(params: {
prompter: Pick<WizardPrompter, "confirm" | "text">;
accountConfigured: boolean;

View File

@@ -14,6 +14,7 @@ import type { WizardPrompter } from "../../../wizard/prompts.js";
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
import { configureChannelAccessWithAllowlist } from "./channel-access-configure.js";
import {
buildSingleChannelSecretPromptState,
parseMentionOrPrefixedId,
noteChannelLookupFailure,
noteChannelLookupSummary,
@@ -234,10 +235,18 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
const accountConfigured =
Boolean(resolvedAccount.botToken && resolvedAccount.appToken) || hasConfigTokens;
const allowEnv = slackAccountId === DEFAULT_ACCOUNT_ID;
const canUseBotEnv =
allowEnv && !hasConfiguredBotToken && Boolean(process.env.SLACK_BOT_TOKEN?.trim());
const canUseAppEnv =
allowEnv && !hasConfiguredAppToken && Boolean(process.env.SLACK_APP_TOKEN?.trim());
const botPromptState = buildSingleChannelSecretPromptState({
accountConfigured: Boolean(resolvedAccount.botToken) || hasConfiguredBotToken,
hasConfigToken: hasConfiguredBotToken,
allowEnv,
envValue: process.env.SLACK_BOT_TOKEN,
});
const appPromptState = buildSingleChannelSecretPromptState({
accountConfigured: Boolean(resolvedAccount.appToken) || hasConfiguredAppToken,
hasConfigToken: hasConfiguredAppToken,
allowEnv,
envValue: process.env.SLACK_APP_TOKEN,
});
let resolvedBotTokenForAllowlist = resolvedAccount.botToken;
const slackBotName = String(
await prompter.text({
@@ -254,9 +263,9 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
providerHint: "slack-bot",
credentialLabel: "Slack bot token",
secretInputMode: options?.secretInputMode,
accountConfigured: Boolean(resolvedAccount.botToken) || hasConfiguredBotToken,
canUseEnv: canUseBotEnv,
hasConfigToken: hasConfiguredBotToken,
accountConfigured: botPromptState.accountConfigured,
canUseEnv: botPromptState.canUseEnv,
hasConfigToken: botPromptState.hasConfigToken,
envPrompt: "SLACK_BOT_TOKEN detected. Use env var?",
keepPrompt: "Slack bot token already configured. Keep it?",
inputPrompt: "Enter Slack bot token (xoxb-...)",
@@ -280,9 +289,9 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
providerHint: "slack-app",
credentialLabel: "Slack app token",
secretInputMode: options?.secretInputMode,
accountConfigured: Boolean(resolvedAccount.appToken) || hasConfiguredAppToken,
canUseEnv: canUseAppEnv,
hasConfigToken: hasConfiguredAppToken,
accountConfigured: appPromptState.accountConfigured,
canUseEnv: appPromptState.canUseEnv,
hasConfigToken: appPromptState.hasConfigToken,
envPrompt: "SLACK_APP_TOKEN detected. Use env var?",
keepPrompt: "Slack app token already configured. Keep it?",
inputPrompt: "Enter Slack app token (xapp-...)",

View File

@@ -14,6 +14,7 @@ import { fetchTelegramChatId } from "../../telegram/api.js";
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
import {
applySingleTokenPromptResult,
buildSingleChannelSecretPromptState,
patchChannelConfigForAccount,
promptSingleChannelSecretInput,
promptResolvedAllowFrom,
@@ -192,12 +193,15 @@ export const telegramOnboardingAdapter: ChannelOnboardingAdapter = {
const hasConfiguredBotToken = hasConfiguredSecretInput(resolvedAccount.config.botToken);
const hasConfigToken =
hasConfiguredBotToken || Boolean(resolvedAccount.config.tokenFile?.trim());
const accountConfigured = Boolean(resolvedAccount.token) || hasConfigToken;
const allowEnv = telegramAccountId === DEFAULT_ACCOUNT_ID;
const canUseEnv =
allowEnv && !hasConfigToken && Boolean(process.env.TELEGRAM_BOT_TOKEN?.trim());
const tokenPromptState = buildSingleChannelSecretPromptState({
accountConfigured: Boolean(resolvedAccount.token) || hasConfigToken,
hasConfigToken,
allowEnv,
envValue: process.env.TELEGRAM_BOT_TOKEN,
});
if (!accountConfigured) {
if (!tokenPromptState.accountConfigured) {
await noteTelegramTokenHelp(prompter);
}
@@ -207,9 +211,9 @@ export const telegramOnboardingAdapter: ChannelOnboardingAdapter = {
providerHint: "telegram",
credentialLabel: "Telegram bot token",
secretInputMode: options?.secretInputMode,
accountConfigured,
canUseEnv,
hasConfigToken,
accountConfigured: tokenPromptState.accountConfigured,
canUseEnv: tokenPromptState.canUseEnv,
hasConfigToken: tokenPromptState.hasConfigToken,
envPrompt: "TELEGRAM_BOT_TOKEN detected. Use env var?",
keepPrompt: "Telegram token already configured. Keep it?",
inputPrompt: "Enter Telegram bot token",