@@ -59,9 +59,27 @@ let botInstance: Bot<Context> | null = null;
5959let chatIdInstance : number | null = null ;
6060let commandsInitialized = false ;
6161
62+ const TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH = 1024 ;
63+ const __filename = fileURLToPath ( import . meta. url ) ;
64+ const __dirname = path . dirname ( __filename ) ;
65+ const TEMP_DIR = path . join ( __dirname , ".." , ".tmp" ) ;
66+
67+ function prepareDocumentCaption ( caption : string ) : string {
68+ const normalizedCaption = caption . trim ( ) ;
69+ if ( ! normalizedCaption ) {
70+ return "" ;
71+ }
72+
73+ if ( normalizedCaption . length <= TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH ) {
74+ return normalizedCaption ;
75+ }
76+
77+ return `${ normalizedCaption . slice ( 0 , TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH - 3 ) } ...` ;
78+ }
79+
6280const toolMessageBatcher = new ToolMessageBatcher ( {
6381 intervalSeconds : 5 ,
64- sendMessage : async ( sessionId , text ) => {
82+ sendText : async ( sessionId , text ) => {
6583 if ( ! botInstance || ! chatIdInstance ) {
6684 return ;
6785 }
@@ -75,6 +93,34 @@ const toolMessageBatcher = new ToolMessageBatcher({
7593 disable_notification : true ,
7694 } ) ;
7795 } ,
96+ sendFile : async ( sessionId , fileData ) => {
97+ if ( ! botInstance || ! chatIdInstance ) {
98+ return ;
99+ }
100+
101+ const currentSession = getCurrentSession ( ) ;
102+ if ( ! currentSession || currentSession . id !== sessionId ) {
103+ return ;
104+ }
105+
106+ const tempFilePath = path . join ( TEMP_DIR , fileData . filename ) ;
107+
108+ try {
109+ logger . debug (
110+ `[Bot] Sending code file: ${ fileData . filename } (${ fileData . buffer . length } bytes, session=${ sessionId } )` ,
111+ ) ;
112+
113+ await fs . mkdir ( TEMP_DIR , { recursive : true } ) ;
114+ await fs . writeFile ( tempFilePath , fileData . buffer ) ;
115+
116+ await botInstance . api . sendDocument ( chatIdInstance , new InputFile ( tempFilePath ) , {
117+ caption : fileData . caption ,
118+ disable_notification : true ,
119+ } ) ;
120+ } finally {
121+ await fs . unlink ( tempFilePath ) . catch ( ( ) => { } ) ;
122+ }
123+ } ,
78124} ) ;
79125
80126async function ensureCommandsInitialized ( ctx : Context , next : NextFunction ) : Promise < void > {
@@ -161,10 +207,6 @@ async function ensureEventSubscription(directory: string): Promise<void> {
161207 } ) ;
162208
163209 summaryAggregator . setOnTool ( async ( toolInfo ) => {
164- if ( config . bot . hideToolCallMessages ) {
165- return ;
166- }
167-
168210 if ( ! botInstance || ! chatIdInstance ) {
169211 logger . error ( "Bot or chat ID not available for sending tool notification" ) ;
170212 return ;
@@ -175,6 +217,14 @@ async function ensureEventSubscription(directory: string): Promise<void> {
175217 return ;
176218 }
177219
220+ const shouldIncludeToolInfoInFileCaption =
221+ toolInfo . hasFileAttachment &&
222+ ( toolInfo . tool === "write" || toolInfo . tool === "edit" || toolInfo . tool === "apply_patch" ) ;
223+
224+ if ( config . bot . hideToolCallMessages || shouldIncludeToolInfoInFileCaption ) {
225+ return ;
226+ }
227+
178228 try {
179229 const message = formatToolInfo ( toolInfo ) ;
180230 if ( message ) {
@@ -185,38 +235,25 @@ async function ensureEventSubscription(directory: string): Promise<void> {
185235 }
186236 } ) ;
187237
188- summaryAggregator . setOnToolFile ( async ( fileData ) => {
238+ summaryAggregator . setOnToolFile ( async ( fileInfo ) => {
189239 if ( ! botInstance || ! chatIdInstance ) {
190240 logger . error ( "Bot or chat ID not available for sending file" ) ;
191241 return ;
192242 }
193243
194244 const currentSession = getCurrentSession ( ) ;
195- if ( ! currentSession ) {
245+ if ( ! currentSession || currentSession . id !== fileInfo . sessionId ) {
196246 return ;
197247 }
198248
199- const __filename = fileURLToPath ( import . meta. url ) ;
200- const __dirname = path . dirname ( __filename ) ;
201- const tempDir = path . join ( __dirname , ".." , ".tmp" ) ;
202-
203249 try {
204- logger . debug (
205- `[Bot] Sending code file: ${ fileData . filename } (${ fileData . buffer . length } bytes)` ,
206- ) ;
207-
208- await fs . mkdir ( tempDir , { recursive : true } ) ;
250+ const toolMessage = formatToolInfo ( fileInfo ) ;
251+ const caption = prepareDocumentCaption ( toolMessage || fileInfo . fileData . caption ) ;
209252
210- const tempFilePath = path . join ( tempDir , fileData . filename ) ;
211- await fs . writeFile ( tempFilePath , fileData . buffer ) ;
212-
213- await botInstance . api . sendDocument ( chatIdInstance , new InputFile ( tempFilePath ) , {
214- caption : fileData . caption ,
215- disable_notification : true ,
253+ toolMessageBatcher . enqueueFile ( fileInfo . sessionId , {
254+ ...fileInfo . fileData ,
255+ caption,
216256 } ) ;
217-
218- await fs . unlink ( tempFilePath ) ;
219- logger . debug ( `[Bot] Temporary file deleted: ${ fileData . filename } ` ) ;
220257 } catch ( err ) {
221258 logger . error ( "Failed to send file to Telegram:" , err ) ;
222259 }
@@ -813,6 +850,16 @@ export function createBot(): Bot<Context> {
813850 }
814851 }
815852
853+ const promptErrorLogContext = {
854+ sessionId : currentSession . id ,
855+ directory : currentSession . directory ,
856+ agent : currentAgent || "default" ,
857+ modelProvider : storedModel . providerID || "default" ,
858+ modelId : storedModel . modelID || "default" ,
859+ variant : storedModel . variant || "default" ,
860+ promptLength : text . length ,
861+ } ;
862+
816863 logger . info ( `[Bot] Calling session.prompt (fire-and-forget) with agent=${ currentAgent } ...` ) ;
817864
818865 // CRITICAL: DO NOT wait for session.prompt to complete.
@@ -824,24 +871,26 @@ export function createBot(): Bot<Context> {
824871 task : ( ) => opencodeClient . session . prompt ( promptOptions ) ,
825872 onSuccess : ( { error } ) => {
826873 if ( error ) {
827- const details = formatErrorDetails ( error ) ;
828- logger . error ( "OpenCode API error:" , error ) ;
829- // Send the error via API directly because ctx is no longer available
830- void bot . api
831- . sendMessage (
832- ctx . chat . id ,
833- t ( "bot.prompt_send_error_detailed" , {
834- details,
835- } ) ,
836- )
837- . catch ( ( ) => { } ) ;
874+ const details = formatErrorDetails ( error , 6000 ) ;
875+ logger . error (
876+ "[Bot] OpenCode API returned an error for session.prompt" ,
877+ promptErrorLogContext ,
878+ ) ;
879+ logger . error ( "[Bot] session.prompt error details:" , details ) ;
880+ logger . error ( "[Bot] session.prompt raw API error object:" , error ) ;
881+
882+ // Send user-friendly error via API directly because ctx is no longer available
883+ void bot . api . sendMessage ( ctx . chat . id , t ( "bot.prompt_send_error" ) ) . catch ( ( ) => { } ) ;
838884 return ;
839885 }
840886
841887 logger . info ( "[Bot] session.prompt completed" ) ;
842888 } ,
843889 onError : ( error ) => {
844- logger . error ( "[Bot] session.prompt background task failed:" , error ) ;
890+ const details = formatErrorDetails ( error , 6000 ) ;
891+ logger . error ( "[Bot] session.prompt background task failed" , promptErrorLogContext ) ;
892+ logger . error ( "[Bot] session.prompt background failure details:" , details ) ;
893+ logger . error ( "[Bot] session.prompt raw background error object:" , error ) ;
845894 void bot . api . sendMessage ( ctx . chat . id , t ( "bot.prompt_send_error" ) ) . catch ( ( ) => { } ) ;
846895 } ,
847896 } ) ;
0 commit comments