@@ -78,19 +78,38 @@ function getTextFromRequest(request: LlmRequest): string {
7878}
7979
8080/**
81- * Bot commands to register with Telegram
81+ * Public bot commands visible to all users (including unapproved)
8282 */
83- const BOT_COMMANDS = [
83+ const PUBLIC_BOT_COMMANDS = [
84+ { command : "start" , description : "Start the bot and pair" } ,
85+ { command : "help" , description : "Show available commands" } ,
86+ ] ;
87+
88+ /**
89+ * Full bot commands visible only to approved/paired users
90+ */
91+ const FULL_BOT_COMMANDS = [
8492 { command : "start" , description : "Start the bot and pair" } ,
8593 { command : "new" , description : "Save session and start fresh" } ,
8694 { command : "reset" , description : "Clear session without saving" } ,
8795 { command : "help" , description : "Show available commands" } ,
8896] ;
8997
9098/**
91- * Register bot commands with Telegram API
99+ * Track chatIds that have had full commands registered
100+ * so we only register once per session per chat
92101 */
93- async function registerBotCommands ( botToken : string ) : Promise < void > {
102+ const registeredChats = new Set < string > ( ) ;
103+
104+ /**
105+ * Register bot commands with Telegram API at global scopes.
106+ * Called at startup with public commands so all users (including unapproved)
107+ * can see /start and /help in the command menu.
108+ */
109+ async function registerBotCommands (
110+ botToken : string ,
111+ commandList : typeof PUBLIC_BOT_COMMANDS = PUBLIC_BOT_COMMANDS ,
112+ ) : Promise < void > {
94113 const api = `https://api.telegram.org/bot${ botToken } ` ;
95114 const headers = { "Content-Type" : "application/json" } ;
96115 const scopes = [
@@ -113,7 +132,7 @@ async function registerBotCommands(botToken: string): Promise<void> {
113132 const response = await fetch ( `${ api } /setMyCommands` , {
114133 method : "POST" ,
115134 headers,
116- body : JSON . stringify ( { commands : BOT_COMMANDS , scope } ) ,
135+ body : JSON . stringify ( { commands : commandList , scope } ) ,
117136 } ) ;
118137 const result = ( await response . json ( ) ) as {
119138 ok : boolean ;
@@ -126,13 +145,57 @@ async function registerBotCommands(botToken: string): Promise<void> {
126145 }
127146 }
128147 log . info (
129- `Registered ${ BOT_COMMANDS . length } bot commands: ${ BOT_COMMANDS . map ( ( c ) => `/${ c . command } ` ) . join ( ", " ) } ` ,
148+ `Registered ${ commandList . length } bot commands: ${ commandList . map ( ( c ) => `/${ c . command } ` ) . join ( ", " ) } ` ,
130149 ) ;
131150 } catch ( error ) {
132151 log . warn ( `Could not register bot commands: ${ error } ` ) ;
133152 }
134153}
135154
155+ /**
156+ * Register full bot commands for a specific chat (approved user).
157+ * Uses Telegram's per-chat scope so the user sees all commands
158+ * including /new and /reset in their command menu.
159+ */
160+ async function registerUserCommands (
161+ botToken : string ,
162+ chatId : string ,
163+ ) : Promise < void > {
164+ const api = `https://api.telegram.org/bot${ botToken } ` ;
165+ const headers = { "Content-Type" : "application/json" } ;
166+ const scope = { type : "chat" , chat_id : Number ( chatId ) } ;
167+
168+ try {
169+ // Clear existing per-chat commands first
170+ await fetch ( `${ api } /deleteMyCommands` , {
171+ method : "POST" ,
172+ headers,
173+ body : JSON . stringify ( { scope } ) ,
174+ } ) ;
175+
176+ // Set full command list for this specific chat
177+ const response = await fetch ( `${ api } /setMyCommands` , {
178+ method : "POST" ,
179+ headers,
180+ body : JSON . stringify ( { commands : FULL_BOT_COMMANDS , scope } ) ,
181+ } ) ;
182+ const result = ( await response . json ( ) ) as {
183+ ok : boolean ;
184+ description ?: string ;
185+ } ;
186+ if ( ! result . ok ) {
187+ log . warn (
188+ `Failed to register user commands for chat ${ chatId } : ${ result . description } ` ,
189+ ) ;
190+ } else {
191+ registeredChats . add ( chatId ) ;
192+ log . info ( `Registered full commands for chat ${ chatId } ` ) ;
193+ }
194+ } catch ( error ) {
195+ log . warn ( `Could not register user commands for chat ${ chatId } : ${ error } ` ) ;
196+ }
197+ }
198+
136199/**
137200 * Command handlers
138201 */
@@ -259,17 +322,29 @@ export async function startTelegramBot(): Promise<void> {
259322 return getPairingResponse ( userId , undefined , chatId ) ;
260323 }
261324
325+ // Register full commands for newly approved users on first message
326+ if ( ! registeredChats . has ( chatId ) ) {
327+ registerUserCommands ( config . telegramBotToken , chatId ) ;
328+ }
329+
262330 // Track in session
263331 sessionManager . getOrCreate ( chatId , userId ) ;
264332 sessionManager . addMessage ( chatId , "user" , messageText ) ;
265333
266334 // Get response from agent
267- const response = await runner . ask ( messageText ) ;
335+ try {
336+ const response = await runner . ask ( messageText ) ;
268337
269- // Track response
270- sessionManager . addMessage ( chatId , "assistant" , response ) ;
338+ // Track response
339+ sessionManager . addMessage ( chatId , "assistant" , response ) ;
271340
272- return response ;
341+ return response ;
342+ } catch ( error ) {
343+ log . error (
344+ `Failed to get response for chatId=${ chatId } : ${ error instanceof Error ? error . message : error } ` ,
345+ ) ;
346+ return "⚠️ Sorry, I'm having trouble right now. Please try again in a moment." ;
347+ }
273348 } ,
274349 ) ;
275350
0 commit comments