"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.setWecomBotConfig = setWecomBotConfig;exports.wecomOnboardingAdapter = void 0; var _pluginSdk = require("openclaw/plugin-sdk"); var _index = require("./config/index.js"); /** * WeCom 配置向导 (Onboarding) * 支持 Bot、Agent 和双模式同时启动的交互式配置流程 */const channel = "wecom"; // ============================================================ // 辅助函数 // ============================================================ function getWecomConfig(cfg) { return cfg.channels?.wecom; } function setWecomEnabled(cfg, enabled) { return { ...cfg, channels: { ...cfg.channels, wecom: { ...(cfg.channels?.wecom ?? {}), enabled } } }; } /** * 确保 cfg.bindings 中存在一条 wecom 账号到默认 agent 的路由。 * * `openclaw channels add` 流程会在插件 configure() 返回后单独提示用户绑定 agent, * 但 `openclaw onboard` 的 quickstart 路径会跳过这一步,导致消息路由缺失。 * 在插件层面主动补全 binding 可以让两种流程都能正常工作。 * * 如果 bindings 中已存在匹配 channel+accountId 的条目,则不会重复添加。 */ function ensureWecomBinding(cfg, accountId) { const existing = cfg.bindings ?? []; const alreadyBound = existing.some( (b) => b.match.channel === channel && (b.match.accountId === accountId || !b.match.accountId && accountId === _pluginSdk.DEFAULT_ACCOUNT_ID) ); if (alreadyBound) return cfg; // 默认路由到 main agent(OpenClaw 约定 defaultAgentId 为 "main") const defaultAgentId = "main"; return { ...cfg, bindings: [ ...existing, { agentId: defaultAgentId, match: { channel, accountId } }] }; } function setGatewayBindLan(cfg) { return { ...cfg, gateway: { ...(cfg.gateway ?? {}), bind: "lan" } }; } function setWecomDefaultAccount(cfg, accountId) { const wecom = getWecomConfig(cfg) ?? {}; return { ...cfg, channels: { ...cfg.channels, wecom: { ...wecom, defaultAccount: accountId } } }; } function shouldUseAccountScopedConfig(wecom, accountId) { void wecom; void accountId; return true; } function ensureMatrixAccounts(wecom) { const accounts = wecom.accounts ?? {}; if (Object.keys(accounts).length > 0) { return wecom; } if (!wecom.bot && !wecom.agent) { return wecom; } const { bot: legacyBot, agent: legacyAgent, ...rest } = wecom; const defaultAccount = { enabled: true, ...(legacyBot ? { bot: legacyBot } : {}), ...(legacyAgent ? { agent: legacyAgent } : {}) }; return { ...rest, defaultAccount: rest.defaultAccount?.trim() || _pluginSdk.DEFAULT_ACCOUNT_ID, accounts: { [_pluginSdk.DEFAULT_ACCOUNT_ID]: defaultAccount } }; } function accountWebhookPath(kind, accountId) { const recommendedBase = kind === "bot" ? "/plugins/wecom/bot" : "/plugins/wecom/agent"; return `${recommendedBase}/${accountId}`; } function setWecomBotConfig(cfg, bot, accountId) { const wecom = getWecomConfig(cfg) ?? {}; if (!shouldUseAccountScopedConfig(wecom, accountId)) { return { ...cfg, channels: { ...cfg.channels, wecom: { ...wecom, enabled: true, bot } } }; } const matrixWecom = ensureMatrixAccounts(wecom); const accounts = matrixWecom.accounts ?? {}; const existingAccount = accounts[accountId] ?? {}; return { ...cfg, channels: { ...cfg.channels, wecom: { ...matrixWecom, enabled: true, defaultAccount: matrixWecom.defaultAccount?.trim() || _pluginSdk.DEFAULT_ACCOUNT_ID, accounts: { ...accounts, [accountId]: { ...existingAccount, enabled: existingAccount.enabled ?? true, bot } } } } }; } function setWecomAgentConfig(cfg, agent, accountId) { const wecom = getWecomConfig(cfg) ?? {}; if (!shouldUseAccountScopedConfig(wecom, accountId)) { return { ...cfg, channels: { ...cfg.channels, wecom: { ...wecom, enabled: true, agent } } }; } const matrixWecom = ensureMatrixAccounts(wecom); const accounts = matrixWecom.accounts ?? {}; const existingAccount = accounts[accountId] ?? {}; return { ...cfg, channels: { ...cfg.channels, wecom: { ...matrixWecom, enabled: true, defaultAccount: matrixWecom.defaultAccount?.trim() || _pluginSdk.DEFAULT_ACCOUNT_ID, accounts: { ...accounts, [accountId]: { ...existingAccount, enabled: existingAccount.enabled ?? true, agent } } } } }; } function setWecomDmPolicy( cfg, mode, dm, accountId) { const wecom = getWecomConfig(cfg) ?? {}; if (shouldUseAccountScopedConfig(wecom, accountId)) { const matrixWecom = ensureMatrixAccounts(wecom); const accounts = matrixWecom.accounts ?? {}; const existingAccount = accounts[accountId] ?? {}; const nextAccount = mode === "bot" ? { ...existingAccount, bot: { ...existingAccount.bot, dm } } : { ...existingAccount, agent: { ...existingAccount.agent, dm } }; return { ...cfg, channels: { ...cfg.channels, wecom: { ...matrixWecom, enabled: true, defaultAccount: matrixWecom.defaultAccount?.trim() || _pluginSdk.DEFAULT_ACCOUNT_ID, accounts: { ...accounts, [accountId]: { ...nextAccount, enabled: nextAccount.enabled ?? true } } } } }; } if (mode === "bot") { return { ...cfg, channels: { ...cfg.channels, wecom: { ...wecom, bot: { ...wecom.bot, dm } } } }; } return { ...cfg, channels: { ...cfg.channels, wecom: { ...wecom, agent: { ...wecom.agent, dm } } } }; } async function resolveOnboardingAccountId(params) { const defaultAccountId = (0, _index.resolveDefaultWecomAccountId)(params.cfg); const override = params.accountOverride?.trim(); let accountId = override || defaultAccountId; if (!override && params.shouldPromptAccountIds) { accountId = await (0, _pluginSdk.promptAccountId)({ cfg: params.cfg, prompter: params.prompter, label: "WeCom", currentId: accountId, listAccountIds: (cfg) => (0, _index.listWecomAccountIds)(cfg), defaultAccountId }); } return accountId.trim() || _pluginSdk.DEFAULT_ACCOUNT_ID; } // ============================================================ // 欢迎与引导 // ============================================================ async function showWelcome(prompter) { await prompter.note( [ "🚀 欢迎使用企业微信(WeCom)接入向导", "本插件支持「智能体 Bot」与「自建应用 Agent」双模式并行。"]. join("\n"), "WeCom 配置向导" ); } // ============================================================ // 模式选择 // ============================================================ async function promptMode(prompter) { const choice = await prompter.select({ message: "请选择您要配置的接入模式:", options: [ { value: "bot", label: "Bot 模式 (智能机器人)", hint: "回调速度快,支持流式占位符,适合日常对话" }, { value: "agent", label: "Agent 模式 (自建应用)", hint: "功能最全,支持 API 主动推送、发送文件/视频、交互卡片" }, { value: "both", label: "双模式 (Bot + Agent 同时启用)", hint: "推荐:Bot 用于快速对话,Agent 用于主动推送和媒体发送" }], initialValue: "both" }); return choice; } // ============================================================ // Bot 模式配置 // ============================================================ async function configureBotMode( cfg, prompter, accountId) { // 选择接入方式 const connectionMode = await prompter.select({ message: "请选择 Bot 接入方式:", options: [ { value: "websocket", label: "WebSocket 长链接模式", hint: "无需公网 IP,SDK 主动连接企微服务器,适合内网环境" }, { value: "webhook", label: "Webhook 回调模式", hint: "需要公网 IP + 回调 URL,适合有公网服务器的环境" }], initialValue: "websocket" }); if (connectionMode === "websocket") { return configureBotWebsocket(cfg, prompter, accountId); } return configureBotWebhook(cfg, prompter, accountId); } async function configureBotWebhook( cfg, prompter, accountId) { const recommendedPath = accountWebhookPath("bot", accountId); await prompter.note( [ "正在配置 Bot 模式(Webhook 回调)...", "", "💡 操作指南: 请在企微后台【管理工具 -> 智能机器人】开启 API 模式。", `🔗 回调 URL (推荐): https://您的域名${recommendedPath}`, "", "请先在后台填入回调 URL,然后获取以下信息。"]. join("\n"), "Bot 模式配置 — Webhook" ); const token = String( await prompter.text({ message: "请输入 Token:", validate: (value) => value?.trim() ? undefined : "Token 不能为空" }) ).trim(); const encodingAESKey = String( await prompter.text({ message: "请输入 EncodingAESKey:", validate: (value) => { const v = value?.trim() ?? ""; if (!v) return "EncodingAESKey 不能为空"; if (v.length !== 43) return "EncodingAESKey 应为 43 个字符"; return undefined; } }) ).trim(); const streamPlaceholder = await prompter.text({ message: "流式占位符 (可选):", placeholder: "正在思考...", initialValue: "正在思考..." }); const welcomeText = await prompter.text({ message: "欢迎语 (可选):", placeholder: "你好!我是 AI 助手", initialValue: "你好!我是 AI 助手" }); const botConfig = { connectionMode: "webhook", token, encodingAESKey, streamPlaceholderContent: streamPlaceholder?.trim() || undefined, welcomeText: welcomeText?.trim() || undefined }; return setWecomBotConfig(cfg, botConfig, accountId); } async function configureBotWebsocket( cfg, prompter, accountId) { await prompter.note( [ "正在配置 Bot 模式(WebSocket 长链接)...", "", "💡 操作指南: 请在企微后台【管理工具 -> 智能机器人】获取 BotID 和 Secret。", "", "长链接模式无需公网 IP 和回调 URL,适合内网环境。"]. join("\n"), "Bot 模式配置 — WebSocket" ); const botId = String( await prompter.text({ message: "请输入 BotID (机器人ID):", validate: (value) => value?.trim() ? undefined : "BotID 不能为空" }) ).trim(); const secret = String( await prompter.text({ message: "请输入 Secret (机器人密钥):", validate: (value) => value?.trim() ? undefined : "Secret 不能为空" }) ).trim(); const streamPlaceholder = await prompter.text({ message: "流式占位符 (可选):", placeholder: "正在思考...", initialValue: "正在思考..." }); const welcomeText = await prompter.text({ message: "欢迎语 (可选):", placeholder: "你好!我是 AI 助手", initialValue: "你好!我是 AI 助手" }); const botConfig = { connectionMode: "websocket", botId, secret, streamPlaceholderContent: streamPlaceholder?.trim() || undefined, welcomeText: welcomeText?.trim() || undefined }; return setWecomBotConfig(cfg, botConfig, accountId); } // ============================================================ // Agent 模式配置 // ============================================================ async function configureAgentMode( cfg, prompter, accountId) { const recommendedPath = accountWebhookPath("agent", accountId); await prompter.note( [ "正在配置 Agent 模式...", "", "💡 操作指南: 请在企微后台【应用管理 -> 自建应用】创建应用。"]. join("\n"), "Agent 模式配置" ); const corpId = String( await prompter.text({ message: "请输入 CorpID (企业ID):", validate: (value) => value?.trim() ? undefined : "CorpID 不能为空" }) ).trim(); const agentIdStr = String( await prompter.text({ message: "请输入 AgentID (应用ID):", validate: (value) => { const v = value?.trim() ?? ""; if (!v) return "AgentID 不能为空"; if (!/^\d+$/.test(v)) return "AgentID 应为数字"; return undefined; } }) ).trim(); const agentId = Number(agentIdStr); const corpSecret = String( await prompter.text({ message: "请输入 Secret (应用密钥):", validate: (value) => value?.trim() ? undefined : "Secret 不能为空" }) ).trim(); await prompter.note( [ "💡 操作指南: 请在自建应用详情页进入【接收消息 -> 设置API接收】。", `🔗 回调 URL (推荐): https://您的域名${recommendedPath}`, "", "请先在后台填入回调 URL,然后获取以下信息。"]. join("\n"), "回调配置" ); const token = String( await prompter.text({ message: "请输入 Token (回调令牌):", validate: (value) => value?.trim() ? undefined : "Token 不能为空" }) ).trim(); const encodingAESKey = String( await prompter.text({ message: "请输入 EncodingAESKey (回调加密密钥):", validate: (value) => { const v = value?.trim() ?? ""; if (!v) return "EncodingAESKey 不能为空"; if (v.length !== 43) return "EncodingAESKey 应为 43 个字符"; return undefined; } }) ).trim(); const welcomeText = await prompter.text({ message: "欢迎语 (可选):", placeholder: "欢迎使用智能助手", initialValue: "欢迎使用智能助手" }); const agentConfig = { corpId, corpSecret, agentId, token, encodingAESKey, welcomeText: welcomeText?.trim() || undefined }; return setWecomAgentConfig(cfg, agentConfig, accountId); } // ============================================================ // DM 策略配置 // ============================================================ async function promptDmPolicy( cfg, prompter, modes, accountId) { const policyChoice = await prompter.select({ message: "请选择私聊 (DM) 访问策略:", options: [ { value: "pairing", label: "配对模式", hint: "推荐:安全,未知用户需授权" }, { value: "allowlist", label: "白名单模式", hint: "仅允许特定 UserID" }, { value: "open", label: "开放模式", hint: "任何人可发起" }, { value: "disabled", label: "禁用私聊", hint: "不接受私聊消息" }], initialValue: "pairing" }); const policy = policyChoice; let allowFrom; if (policy === "allowlist") { const allowFromStr = String( await prompter.text({ message: "请输入白名单 UserID (多个用逗号分隔):", placeholder: "user1,user2", validate: (value) => value?.trim() ? undefined : "请输入至少一个 UserID" }) ).trim(); allowFrom = allowFromStr.split(",").map((s) => s.trim()).filter(Boolean); } const dm = { policy, allowFrom }; let result = cfg; for (const mode of modes) { result = setWecomDmPolicy(result, mode, dm, accountId); } return result; } // ============================================================ // 配置汇总 // ============================================================ async function showSummary(cfg, prompter, accountId) { const account = (0, _index.resolveWecomAccount)({ cfg, accountId }); const lines = ["✅ 配置已保存!", ""]; if (account.bot?.configured) { if (account.bot.connectionMode === "websocket") { lines.push("📱 Bot 模式: 已配置 (WebSocket 长链接)"); lines.push(" 无需配置回调 URL,SDK 将主动连接企微服务器"); } else { lines.push("📱 Bot 模式: 已配置 (Webhook 回调)"); lines.push(` 回调 URL: https://您的域名${accountWebhookPath("bot", accountId)}`); } } if (account.agent?.configured) { lines.push("🏢 Agent 模式: 已配置"); lines.push(` 回调 URL: https://您的域名${accountWebhookPath("agent", accountId)}`); } lines.push(` 账号 ID: ${accountId}`); const hasWebhook = account.bot?.configured && account.bot.connectionMode !== "websocket" || account.agent?.configured; lines.push(""); if (hasWebhook) { lines.push("⚠️ 请确保您已在企微后台填写了正确的回调 URL,"); lines.push(" 并点击了后台的『保存』按钮完成验证。"); } else { lines.push("💡 WebSocket 模式将在服务启动时自动连接企微服务器。"); } await prompter.note(lines.join("\n"), "配置完成"); } // ============================================================ // DM Policy Adapter // ============================================================ const dmPolicy = { label: "WeCom", channel, policyKey: "channels.wecom.bot.dm.policy", allowFromKey: "channels.wecom.bot.dm.allowFrom", getCurrent: (cfg) => { const account = (0, _index.resolveWecomAccount)({ cfg }); return account.bot?.config.dm?.policy ?? "pairing"; }, setPolicy: (cfg, policy) => { const accountId = (0, _index.resolveDefaultWecomAccountId)(cfg); return setWecomDmPolicy(cfg, "bot", { policy }, accountId); }, promptAllowFrom: async ({ cfg, prompter }) => { const allowFromStr = String( await prompter.text({ message: "请输入白名单 UserID:", validate: (value) => value?.trim() ? undefined : "请输入 UserID" }) ).trim(); const allowFrom = allowFromStr.split(",").map((s) => s.trim()).filter(Boolean); const accountId = (0, _index.resolveDefaultWecomAccountId)(cfg); return setWecomDmPolicy(cfg, "bot", { policy: "allowlist", allowFrom }, accountId); } }; // ============================================================ // Onboarding Adapter // ============================================================ const wecomOnboardingAdapter = exports.wecomOnboardingAdapter = { channel, dmPolicy, getStatus: async ({ cfg }) => { const resolved = (0, _index.resolveWecomAccounts)(cfg); const accounts = Object.values(resolved.accounts).filter((account) => account.enabled !== false); const botConfigured = accounts.some((account) => Boolean(account.bot?.configured)); const agentConfigured = accounts.some((account) => Boolean(account.agent?.configured)); const configured = accounts.some((account) => account.configured); const statusParts = []; if (botConfigured) statusParts.push("Bot ✓"); if (agentConfigured) statusParts.push("Agent ✓"); const accountSuffix = accounts.length > 1 ? ` · ${accounts.length} accounts` : ""; const statusSummary = statusParts.length > 0 ? statusParts.join(" + ") : "已配置"; return { channel, configured, statusLines: [ `WeCom: ${configured ? `${statusSummary}${accountSuffix}` : "需要配置"}`], selectionHint: configured ? `configured · ${statusSummary}${accountSuffix}` : "enterprise-ready · dual-mode", quickstartScore: configured ? 1 : 8 }; }, configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => { // 1. 欢迎 await showWelcome(prompter); // 2. 账号选择 const accountId = await resolveOnboardingAccountId({ cfg, prompter, accountOverride: accountOverrides.wecom, shouldPromptAccountIds }); // 3. 模式选择 const mode = await promptMode(prompter); let next = cfg; const configuredModes = []; // 4. 配置 Bot if (mode === "bot" || mode === "both") { next = await configureBotMode(next, prompter, accountId); configuredModes.push("bot"); } // 5. 配置 Agent if (mode === "agent" || mode === "both") { next = await configureAgentMode(next, prompter, accountId); configuredModes.push("agent"); } // 6. DM 策略 next = await promptDmPolicy(next, prompter, configuredModes, accountId); // 7. 设置 defaultAccount next = setWecomDefaultAccount(next, accountId); // 8. 启用通道 next = setWecomEnabled(next, true); // 9. 设置 gateway.bind 为 lan(允许外部访问回调) next = setGatewayBindLan(next); // 10. 确保 bindings 中有默认路由(onboard quickstart 不会提示绑定) next = ensureWecomBinding(next, accountId); // 11. 汇总 await showSummary(next, prompter, accountId); return { cfg: next, accountId }; } }; /* v9-9bb6df71d57adcbf */