refactor: 重构 onboarding 模块以避免循环依赖

This commit is contained in:
sliverp
2026-01-29 10:53:34 +08:00
parent e25ec64611
commit c113ef131a
2 changed files with 40 additions and 90 deletions

View File

@@ -25,7 +25,7 @@ export const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount> = {
}, },
reload: { configPrefixes: ["channels.qqbot"] }, reload: { configPrefixes: ["channels.qqbot"] },
// CLI onboarding wizard // CLI onboarding wizard
onboarding: qqbotOnboardingAdapter as any, onboarding: qqbotOnboardingAdapter,
config: { config: {
listAccountIds: (cfg) => listQQBotAccountIds(cfg), listAccountIds: (cfg) => listQQBotAccountIds(cfg),
resolveAccount: (cfg, accountId) => resolveQQBotAccount(cfg, accountId), resolveAccount: (cfg, accountId) => resolveQQBotAccount(cfg, accountId),

View File

@@ -1,12 +1,20 @@
/** /**
* QQBot CLI Onboarding Adapter * QQBot CLI Onboarding Adapter
*
* 提供 moltbot onboard 命令的交互式配置支持
*/ */
import type { ResolvedQQBotAccount } from "./types.js"; import type {
ChannelOnboardingAdapter,
ChannelOnboardingStatus,
ChannelOnboardingStatusContext,
ChannelOnboardingConfigureContext,
ChannelOnboardingResult,
} from "clawdbot/plugin-sdk";
import { listQQBotAccountIds, resolveQQBotAccount } from "./config.js"; import { listQQBotAccountIds, resolveQQBotAccount } from "./config.js";
const DEFAULT_ACCOUNT_ID = "default"; const DEFAULT_ACCOUNT_ID = "default";
// 类型定义(从 moltbot 导入会有循环依赖问题 // 内部类型(避免循环依赖)
interface MoltbotConfig { interface MoltbotConfig {
channels?: { channels?: {
qqbot?: QQBotChannelConfig; qqbot?: QQBotChannelConfig;
@@ -30,78 +38,6 @@ interface QQBotChannelConfig {
}>; }>;
} }
interface WizardPrompter {
note: (message: string, title?: string) => Promise<void>;
text: (options: {
message: string;
placeholder?: string;
initialValue?: string;
validate?: (value: string | undefined) => string | undefined;
}) => Promise<string>;
confirm: (options: {
message: string;
initialValue?: boolean;
}) => Promise<boolean>;
select: <T>(options: {
message: string;
options: Array<{ value: T; label: string; hint?: string }>;
initialValue?: T;
}) => Promise<T>;
}
interface ChannelOnboardingStatus {
channel: string;
configured: boolean;
statusLines: string[];
selectionHint?: string;
quickstartScore?: number;
}
interface ChannelOnboardingStatusContext {
cfg: MoltbotConfig;
options?: unknown;
accountOverrides: Partial<Record<string, string>>;
}
interface ChannelOnboardingConfigureContext {
cfg: MoltbotConfig;
runtime: unknown;
prompter: WizardPrompter;
options?: unknown;
accountOverrides: Partial<Record<string, string>>;
shouldPromptAccountIds: boolean;
forceAllowFrom: boolean;
}
interface ChannelOnboardingResult {
cfg: MoltbotConfig;
accountId?: string;
}
interface ChannelOnboardingAdapter {
channel: string;
getStatus: (ctx: ChannelOnboardingStatusContext) => Promise<ChannelOnboardingStatus>;
configure: (ctx: ChannelOnboardingConfigureContext) => Promise<ChannelOnboardingResult>;
disable?: (cfg: MoltbotConfig) => MoltbotConfig;
}
/**
* 显示 QQBot 配置帮助
*/
async function noteQQBotHelp(prompter: WizardPrompter): Promise<void> {
await prompter.note(
[
"1) 打开 QQ 开放平台: https://q.qq.com/",
"2) 创建机器人应用,获取 AppID 和 ClientSecret",
"3) 在「开发设置」中添加沙箱成员(测试阶段)",
"4) 你也可以设置环境变量 QQBOT_APP_ID 和 QQBOT_CLIENT_SECRET",
"",
"文档: https://bot.q.qq.com/wiki/",
].join("\n"),
"QQ Bot 配置",
);
}
/** /**
* 解析默认账户 ID * 解析默认账户 ID
*/ */
@@ -114,16 +50,17 @@ function resolveDefaultQQBotAccountId(cfg: MoltbotConfig): string {
* QQBot Onboarding Adapter * QQBot Onboarding Adapter
*/ */
export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = { export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
channel: "qqbot", channel: "qqbot" as any,
getStatus: async ({ cfg }) => { getStatus: async (ctx: ChannelOnboardingStatusContext): Promise<ChannelOnboardingStatus> => {
const configured = listQQBotAccountIds(cfg).some((accountId) => { const { cfg } = ctx;
const account = resolveQQBotAccount(cfg, accountId); const configured = listQQBotAccountIds(cfg as MoltbotConfig).some((accountId) => {
const account = resolveQQBotAccount(cfg as MoltbotConfig, accountId);
return Boolean(account.appId && account.clientSecret); return Boolean(account.appId && account.clientSecret);
}); });
return { return {
channel: "qqbot", channel: "qqbot" as any,
configured, configured,
statusLines: [`QQ Bot: ${configured ? "已配置" : "需要 AppID 和 ClientSecret"}`], statusLines: [`QQ Bot: ${configured ? "已配置" : "需要 AppID 和 ClientSecret"}`],
selectionHint: configured ? "已配置" : "支持 QQ 群聊和私聊", selectionHint: configured ? "已配置" : "支持 QQ 群聊和私聊",
@@ -131,14 +68,17 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
}; };
}, },
configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => { configure: async (ctx: ChannelOnboardingConfigureContext): Promise<ChannelOnboardingResult> => {
const qqbotOverride = accountOverrides.qqbot?.trim(); const { cfg, prompter, accountOverrides, shouldPromptAccountIds } = ctx;
const defaultAccountId = resolveDefaultQQBotAccountId(cfg); const moltbotCfg = cfg as MoltbotConfig;
const qqbotOverride = (accountOverrides as Record<string, string>).qqbot?.trim();
const defaultAccountId = resolveDefaultQQBotAccountId(moltbotCfg);
let accountId = qqbotOverride ?? defaultAccountId; let accountId = qqbotOverride ?? defaultAccountId;
// 是否需要提示选择账户 // 是否需要提示选择账户
if (shouldPromptAccountIds && !qqbotOverride) { if (shouldPromptAccountIds && !qqbotOverride) {
const existingIds = listQQBotAccountIds(cfg); const existingIds = listQQBotAccountIds(moltbotCfg);
if (existingIds.length > 1) { if (existingIds.length > 1) {
accountId = await prompter.select({ accountId = await prompter.select({
message: "选择 QQBot 账户", message: "选择 QQBot 账户",
@@ -151,7 +91,7 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
} }
} }
let next = cfg; let next = moltbotCfg;
const resolvedAccount = resolveQQBotAccount(next, accountId); const resolvedAccount = resolveQQBotAccount(next, accountId);
const accountConfigured = Boolean(resolvedAccount.appId && resolvedAccount.clientSecret); const accountConfigured = Boolean(resolvedAccount.appId && resolvedAccount.clientSecret);
const allowEnv = accountId === DEFAULT_ACCOUNT_ID; const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
@@ -165,7 +105,17 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
// 显示帮助 // 显示帮助
if (!accountConfigured) { if (!accountConfigured) {
await noteQQBotHelp(prompter); await prompter.note(
[
"1) 打开 QQ 开放平台: https://q.qq.com/",
"2) 创建机器人应用,获取 AppID 和 ClientSecret",
"3) 在「开发设置」中添加沙箱成员(测试阶段)",
"4) 你也可以设置环境变量 QQBOT_APP_ID 和 QQBOT_CLIENT_SECRET",
"",
"文档: https://bot.q.qq.com/wiki/",
].join("\n"),
"QQ Bot 配置",
);
} }
// 检测环境变量 // 检测环境变量
@@ -283,14 +233,14 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
} }
} }
return { cfg: next, accountId }; return { cfg: next as any, accountId };
}, },
disable: (cfg) => ({ disable: (cfg) => ({
...cfg, ...cfg,
channels: { channels: {
...cfg.channels, ...(cfg as MoltbotConfig).channels,
qqbot: { ...cfg.channels?.qqbot, enabled: false }, qqbot: { ...(cfg as MoltbotConfig).channels?.qqbot, enabled: false },
}, },
}), }) as any,
}; };