fix: proper Telegram/WhatsApp channel setup using config + pairing (#2605)

Telegram is a built-in channel, not a plugin. Replace broken
`openclaw plugins enable telegram` (OOM) and `openclaw channels add`
(doesn't exist) with proper setup:

- Write channel config (botToken, dmPolicy: pairing, groups) directly
  into the atomic JSON config file during setup
- After gateway starts, prompt user to pair via
  `openclaw pairing approve <channel> <CODE>`
- WhatsApp: QR scan via `openclaw channels login`, then pairing
- Bump version to 0.17.16

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ahmed Abushagur 2026-03-13 23:21:02 -07:00 committed by GitHub
parent f1f8b53dde
commit ca5fe851cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 109 additions and 12 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.17.15",
"version": "0.17.16",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -9,7 +9,7 @@ import { join } from "node:path";
import { getTmpDir } from "./paths";
import { asyncTryCatch, asyncTryCatchIf, isOperationalError, tryCatchIf } from "./result.js";
import { getErrorMessage } from "./type-guards";
import { Err, jsonEscape, logError, logInfo, logStep, logWarn, Ok, shellQuote, withRetry } from "./ui";
import { Err, jsonEscape, logError, logInfo, logStep, logWarn, Ok, prompt, shellQuote, withRetry } from "./ui";
/**
* Wrap an SSH-based async operation into a Result for use with withRetry.
@ -324,10 +324,28 @@ async function setupOpenclawConfig(
await installChromeBrowser(runner);
}
// Prompt for Telegram bot token before building the config JSON so we can
// include it in a single atomic write.
let telegramBotToken = "";
if (enabledSteps?.has("telegram")) {
logStep("Setting up Telegram...");
const envToken = process.env.TELEGRAM_BOT_TOKEN ?? process.env.SPAWN_TELEGRAM_BOT_TOKEN ?? "";
if (!envToken) {
logInfo("To get a bot token:");
logInfo(" 1. Open Telegram and search for @BotFather");
logInfo(" 2. Send /newbot and follow the prompts");
logInfo(" 3. Copy the token (looks like 123456:ABC-DEF...)");
logInfo(" Press Enter to skip if you don't have one yet.");
}
telegramBotToken = (envToken || (await prompt("Telegram bot token: "))).trim();
if (!telegramBotToken) {
logInfo("No token entered — set up Telegram via the web dashboard after launch");
}
}
const gatewayToken = token ?? crypto.randomUUID().replace(/-/g, "");
// Build config object for atomic JSON write — base config only (API key, gateway, model).
// Channel setup (Telegram, WhatsApp) is handled by `openclaw onboard` in orchestrate.ts.
// Build config object for atomic JSON write
const configObj: Record<string, unknown> = {
env: {
OPENROUTER_API_KEY: apiKey,
@ -347,6 +365,36 @@ async function setupOpenclawConfig(
},
};
// Channel config — written directly to the config file.
// Both use dmPolicy "pairing" so users must approve new senders.
const channels: Record<string, unknown> = {};
if (telegramBotToken) {
channels.telegram = {
enabled: true,
botToken: telegramBotToken,
dmPolicy: "pairing",
groups: {
"*": {
requireMention: true,
},
},
};
logInfo("Telegram bot token configured");
}
if (enabledSteps?.has("whatsapp")) {
channels.whatsapp = {
dmPolicy: "pairing",
groupPolicy: "allowlist",
sendReadReceipts: true,
};
}
if (Object.keys(channels).length > 0) {
configObj.channels = channels;
}
const config = JSON.stringify(configObj, null, 2);
await uploadConfigFile(runner, config, "$HOME/.openclaw/openclaw.json");
@ -378,7 +426,7 @@ async function setupOpenclawConfig(
logWarn("Gateway token re-assertion failed (non-fatal) — dashboard may show Unauthorized");
}
// Channel setup (Telegram, WhatsApp) is handled by `openclaw onboard` in orchestrate.ts.
// Channel pairing (Telegram/WhatsApp) happens in orchestrate.ts after the gateway starts.
// Write USER.md bootstrap file — guides users to the web dashboard for
// visual tasks like WhatsApp QR code scanning that don't work in the TUI.

View file

@ -25,6 +25,8 @@ import {
logWarn,
openBrowser,
prepareStdinForHandoff,
prompt,
shellQuote,
validateModelId,
withRetry,
} from "./ui";
@ -291,14 +293,61 @@ export async function runOrchestration(
}
}
// 11c. Channel setup — delegate to OpenClaw's built-in onboard wizard.
// `openclaw onboard` interactively guides the user through Telegram, WhatsApp,
// and other channel configuration. Runs after the gateway starts.
if (enabledSteps?.has("telegram") || enabledSteps?.has("whatsapp")) {
const ocPath = "export PATH=$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH";
logStep("Running OpenClaw channel setup...");
// 11c. Channel setup (runs after gateway is up)
const ocPath = "export PATH=$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH";
if (enabledSteps?.has("telegram")) {
logStep("Telegram pairing...");
logInfo("DM your Telegram bot to get a pairing code, then enter it below.");
logInfo("Waiting for pairing code...");
process.stderr.write("\n");
const pairingCode = (await prompt("Telegram pairing code: ")).trim();
if (pairingCode) {
const escaped = shellQuote(pairingCode);
const result = await asyncTryCatchIf(isOperationalError, () =>
cloud.runner.runServer(
`source ~/.spawnrc 2>/dev/null; ${ocPath}; openclaw pairing approve telegram ${escaped}`,
),
);
if (result.ok) {
logInfo("Telegram paired successfully");
} else {
logWarn("Pairing failed — you can pair later via: openclaw pairing approve telegram <CODE>");
}
} else {
logInfo("No code entered — pair later via: openclaw pairing approve telegram <CODE>");
}
}
if (enabledSteps?.has("whatsapp")) {
// Step 1: QR code scan to link the WhatsApp device
logStep("Linking WhatsApp — scan the QR code with your phone...");
logInfo("Open WhatsApp > Settings > Linked Devices > Link a Device");
process.stderr.write("\n");
const whatsappCmd = `source ~/.spawnrc 2>/dev/null; ${ocPath}; openclaw channels login --channel whatsapp`;
prepareStdinForHandoff();
await cloud.interactiveSession(`source ~/.spawnrc 2>/dev/null; ${ocPath}; openclaw onboard`);
await cloud.interactiveSession(whatsappCmd);
// Step 2: Pairing — approve your own number so the bot responds to you
logStep("WhatsApp pairing...");
logInfo("Send a message to your bot on WhatsApp to get a pairing code, then enter it below.");
process.stderr.write("\n");
const pairingCode = (await prompt("WhatsApp pairing code: ")).trim();
if (pairingCode) {
const escaped = shellQuote(pairingCode);
const result = await asyncTryCatchIf(isOperationalError, () =>
cloud.runner.runServer(
`source ~/.spawnrc 2>/dev/null; ${ocPath}; openclaw pairing approve whatsapp ${escaped}`,
),
);
if (result.ok) {
logInfo("WhatsApp paired successfully");
} else {
logWarn("Pairing failed — you can pair later via: openclaw pairing approve whatsapp <CODE>");
}
} else {
logInfo("No code entered — pair later via: openclaw pairing approve whatsapp <CODE>");
}
}
// 11d. Agent-specific pre-launch tip (e.g. channel setup ordering hint)