@@ -6,17 +6,27 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSy
66import { join } from 'path' ;
77import { homedir } from 'os' ;
88import { getOpenClawResolvedDir } from './paths' ;
9+ import * as logger from './logger' ;
910
1011const OPENCLAW_DIR = join ( homedir ( ) , '.openclaw' ) ;
1112const CONFIG_FILE = join ( OPENCLAW_DIR , 'openclaw.json' ) ;
1213
14+ // Channels that are managed as plugins (config goes under plugins.entries, not channels)
15+ const PLUGIN_CHANNELS = [ 'whatsapp' ] ;
16+
1317export interface ChannelConfigData {
1418 enabled ?: boolean ;
1519 [ key : string ] : unknown ;
1620}
1721
22+ export interface PluginsConfig {
23+ entries ?: Record < string , ChannelConfigData > ;
24+ [ key : string ] : unknown ;
25+ }
26+
1827export interface OpenClawConfig {
1928 channels ?: Record < string , ChannelConfigData > ;
29+ plugins ?: PluginsConfig ;
2030 [ key : string ] : unknown ;
2131}
2232
@@ -43,6 +53,7 @@ export function readOpenClawConfig(): OpenClawConfig {
4353 const content = readFileSync ( CONFIG_FILE , 'utf-8' ) ;
4454 return JSON . parse ( content ) as OpenClawConfig ;
4555 } catch ( error ) {
56+ logger . error ( 'Failed to read OpenClaw config' , error ) ;
4657 console . error ( 'Failed to read OpenClaw config:' , error ) ;
4758 return { } ;
4859 }
@@ -57,6 +68,7 @@ export function writeOpenClawConfig(config: OpenClawConfig): void {
5768 try {
5869 writeFileSync ( CONFIG_FILE , JSON . stringify ( config , null , 2 ) , 'utf-8' ) ;
5970 } catch ( error ) {
71+ logger . error ( 'Failed to write OpenClaw config' , error ) ;
6072 console . error ( 'Failed to write OpenClaw config:' , error ) ;
6173 throw error ;
6274 }
@@ -73,6 +85,28 @@ export function saveChannelConfig(
7385) : void {
7486 const currentConfig = readOpenClawConfig ( ) ;
7587
88+ // Plugin-based channels (e.g. WhatsApp) go under plugins.entries, not channels
89+ if ( PLUGIN_CHANNELS . includes ( channelType ) ) {
90+ if ( ! currentConfig . plugins ) {
91+ currentConfig . plugins = { } ;
92+ }
93+ if ( ! currentConfig . plugins . entries ) {
94+ currentConfig . plugins . entries = { } ;
95+ }
96+ currentConfig . plugins . entries [ channelType ] = {
97+ ...currentConfig . plugins . entries [ channelType ] ,
98+ enabled : config . enabled ?? true ,
99+ } ;
100+ writeOpenClawConfig ( currentConfig ) ;
101+ logger . info ( 'Plugin channel config saved' , {
102+ channelType,
103+ configFile : CONFIG_FILE ,
104+ path : `plugins.entries.${ channelType } ` ,
105+ } ) ;
106+ console . log ( `Saved plugin channel config for ${ channelType } ` ) ;
107+ return ;
108+ }
109+
76110 if ( ! currentConfig . channels ) {
77111 currentConfig . channels = { } ;
78112 }
@@ -146,6 +180,13 @@ export function saveChannelConfig(
146180 } ;
147181
148182 writeOpenClawConfig ( currentConfig ) ;
183+ logger . info ( 'Channel config saved' , {
184+ channelType,
185+ configFile : CONFIG_FILE ,
186+ rawKeys : Object . keys ( config ) ,
187+ transformedKeys : Object . keys ( transformedConfig ) ,
188+ enabled : currentConfig . channels [ channelType ] ?. enabled ,
189+ } ) ;
149190 console . log ( `Saved channel config for ${ channelType } ` ) ;
150191}
151192
@@ -289,6 +330,23 @@ export function listConfiguredChannels(): string[] {
289330export function setChannelEnabled ( channelType : string , enabled : boolean ) : void {
290331 const currentConfig = readOpenClawConfig ( ) ;
291332
333+ // Plugin-based channels go under plugins.entries
334+ if ( PLUGIN_CHANNELS . includes ( channelType ) ) {
335+ if ( ! currentConfig . plugins ) {
336+ currentConfig . plugins = { } ;
337+ }
338+ if ( ! currentConfig . plugins . entries ) {
339+ currentConfig . plugins . entries = { } ;
340+ }
341+ if ( ! currentConfig . plugins . entries [ channelType ] ) {
342+ currentConfig . plugins . entries [ channelType ] = { } ;
343+ }
344+ currentConfig . plugins . entries [ channelType ] . enabled = enabled ;
345+ writeOpenClawConfig ( currentConfig ) ;
346+ console . log ( `Set plugin channel ${ channelType } enabled: ${ enabled } ` ) ;
347+ return ;
348+ }
349+
292350 if ( ! currentConfig . channels ) {
293351 currentConfig . channels = { } ;
294352 }
@@ -457,10 +515,16 @@ async function validateTelegramCredentials(
457515) : Promise < CredentialValidationResult > {
458516 const botToken = config . botToken ?. trim ( ) ;
459517
518+ const allowedUsers = config . allowedUsers ?. trim ( ) ;
519+
460520 if ( ! botToken ) {
461521 return { valid : false , errors : [ 'Bot token is required' ] , warnings : [ ] } ;
462522 }
463523
524+ if ( ! allowedUsers ) {
525+ return { valid : false , errors : [ 'At least one allowed user ID is required' ] , warnings : [ ] } ;
526+ }
527+
464528 try {
465529 const response = await fetch ( `https://api.telegram.org/bot${ botToken } /getMe` ) ;
466530 const data = ( await response . json ( ) ) as { ok ?: boolean ; description ?: string ; result ?: { username ?: string } } ;
@@ -553,6 +617,12 @@ export async function validateChannelConfig(channelType: string): Promise<Valida
553617 result . errors . push ( 'Telegram: Bot token is required' ) ;
554618 result . valid = false ;
555619 }
620+ // Check allowed users (stored as allowFrom array)
621+ const allowedUsers = telegramConfig ?. allowFrom as string [ ] | undefined ;
622+ if ( ! allowedUsers || allowedUsers . length === 0 ) {
623+ result . errors . push ( 'Telegram: Allowed User IDs are required' ) ;
624+ result . valid = false ;
625+ }
556626 }
557627
558628 if ( result . errors . length === 0 && result . warnings . length === 0 ) {
0 commit comments