From e91aeabdebd9831c7237b7ca8ca2f111d7fa15d4 Mon Sep 17 00:00:00 2001 From: lsamc <2322176165@qq.com> Date: Fri, 5 Sep 2025 15:22:53 +0800 Subject: [PATCH] wierd one. --- .claude/settings.local.json | 9 ++ claude-hook-notify.js | 0 claude-remote.js | 0 fix-telegram.sh | 0 setup-telegram.sh | 0 src/channels/telegram/telegram.js | 240 ++++++++++++++++++++++++++++++ src/daemon/taskping-daemon.js | 0 src/data/session-map.json | 90 +++++++++++ src/utils/tmux-monitor.js | 90 +++++++++-- start-all-webhooks.js | 0 start-line-webhook.js | 0 start-relay-pty.js | 0 start-telegram-webhook.js | 0 test-complete-flow.sh | 0 test-injection.js | 0 test-long-email.js | 0 test-telegram-notification.js | 0 test-telegram-setup.sh | 0 18 files changed, 418 insertions(+), 11 deletions(-) create mode 100644 .claude/settings.local.json mode change 100755 => 100644 claude-hook-notify.js mode change 100755 => 100644 claude-remote.js mode change 100755 => 100644 fix-telegram.sh mode change 100755 => 100644 setup-telegram.sh mode change 100755 => 100644 src/daemon/taskping-daemon.js mode change 100755 => 100644 start-all-webhooks.js mode change 100755 => 100644 start-line-webhook.js mode change 100755 => 100644 start-relay-pty.js mode change 100755 => 100644 start-telegram-webhook.js mode change 100755 => 100644 test-complete-flow.sh mode change 100755 => 100644 test-injection.js mode change 100755 => 100644 test-long-email.js mode change 100755 => 100644 test-telegram-notification.js mode change 100755 => 100644 test-telegram-setup.sh diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..a8cfd97 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:github.com)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/claude-hook-notify.js b/claude-hook-notify.js old mode 100755 new mode 100644 diff --git a/claude-remote.js b/claude-remote.js old mode 100755 new mode 100644 diff --git a/fix-telegram.sh b/fix-telegram.sh old mode 100755 new mode 100644 diff --git a/setup-telegram.sh b/setup-telegram.sh old mode 100755 new mode 100644 diff --git a/src/channels/telegram/telegram.js b/src/channels/telegram/telegram.js index 4573112..687f886 100644 --- a/src/channels/telegram/telegram.js +++ b/src/channels/telegram/telegram.js @@ -254,6 +254,23 @@ class TelegramChannel extends NotificationChannel { } }; + // Check if we need to use multi-message sending based on original response length + const fullResponse = notification.metadata?.claudeResponse || ''; + const shouldUseMultiMessage = fullResponse.length > 1500; // Lower threshold for better experience + + if (shouldUseMultiMessage) { + console.log(`[DEBUG] Using multi-message sending (original response: ${fullResponse.length} chars)`); + const success = await this._sendMultipleMessages(notification, sessionId, token, chatId, buttons); + if (!success) { + // Clean up failed session + await this._removeSession(sessionId); + } + return success; + } + + // Use single message sending + console.log(`[DEBUG] Using single message sending (${messageText.length} chars)`); + try { // Log the message details for debugging console.log(`[DEBUG] =====================================================`); @@ -446,6 +463,229 @@ class TelegramChannel extends NotificationChannel { return `${emoji} Claude Task ${status}\nToken: ${token}\nUse: /cmd ${token} `; } + /** + * Send multiple messages for long responses + * @param {Object} notification - Notification data + * @param {string} sessionId - Session ID + * @param {string} token - Session token + * @param {string} chatId - Telegram chat ID + * @param {Array} buttons - Inline keyboard buttons + * @returns {Promise} - Success status + */ + async _sendMultipleMessages(notification, sessionId, token, chatId, buttons) { + const type = notification.type; + const emoji = type === 'completed' ? 'āœ…' : 'ā³'; + const status = type === 'completed' ? 'Completed' : 'Waiting for Input'; + + try { + const fullResponse = notification.metadata?.claudeResponse || ''; + const userQuestion = notification.metadata?.userQuestion || ''; + + console.log(`[DEBUG] Multi-message sending: Response length ${fullResponse.length} chars`); + + // Split the response content into optimal chunks + const responseChunks = this._splitTextIntoChunks(fullResponse, 3200); // Slightly smaller for better formatting + const totalParts = Math.max(1, responseChunks.length); + + console.log(`[DEBUG] Split response into ${totalParts} parts`); + + // Create the first message with basic info + let firstMessage = `${emoji} *Claude Task ${status}* \\[1/${totalParts}\\]\n`; + firstMessage += `*Project:* ${this._escapeMarkdown(notification.project || 'Unknown')}\n`; + firstMessage += `*Token:* \`${token}\`\n\n`; + + if (userQuestion) { + const question = userQuestion.substring(0, 150); + firstMessage += `šŸ“ *Question:* ${this._escapeMarkdown(question)}${question.length < userQuestion.length ? '...' : ''}\n\n`; + } + + // Add response header and first part + firstMessage += `šŸ¤– *Claude Response:*\n`; + + // Calculate space for first response part + const commandInstructions = `\n\nšŸ’¬ *Commands:* \`/cmd ${token} \``; + const baseLength = firstMessage.length + commandInstructions.length; + const maxFirstResponseLength = 3800 - baseLength; + + // Add first part of response + let firstResponsePart = ''; + if (responseChunks.length > 0) { + firstResponsePart = responseChunks[0]; + if (firstResponsePart.length > maxFirstResponseLength) { + // Further truncate first part if needed + firstResponsePart = firstResponsePart.substring(0, maxFirstResponseLength - 50) + '...'; + } + } + + firstMessage += this._escapeMarkdown(firstResponsePart); + + // Add continuation indicator + if (totalParts > 1) { + firstMessage += `\n\n*\\[Continued in next message\\]*`; + } + + firstMessage += commandInstructions; + + // Send the first message + console.log(`[DEBUG] Sending first message (${firstMessage.length} chars)`); + const firstMessageResult = await this._sendSingleMessage(chatId, firstMessage, buttons, sessionId, 'Markdown'); + + if (!firstMessageResult.success) { + console.log(`[DEBUG] Failed to send first message, trying fallback`); + // Try plain text fallback for first message + const plainFirstMessage = firstMessage + .replace(/\*([^*]+)\*/g, '$1') // Remove bold + .replace(/\`([^`]+)\`/g, '$1') // Remove code + .replace(/\\(.)/g, '$1'); // Remove escapes + + const fallbackResult = await this._sendSingleMessage(chatId, plainFirstMessage, buttons, sessionId, null); + if (!fallbackResult.success) { + return false; + } + } + + const firstMessageId = firstMessageResult.messageId; + let successfulParts = 1; + + // Send remaining parts + for (let i = 1; i < responseChunks.length; i++) { + const partNumber = i + 1; + + let continuationMessage = `*\\[Part ${partNumber}/${totalParts}\\]*\n\n`; + continuationMessage += this._escapeMarkdown(responseChunks[i]); + + console.log(`[DEBUG] Sending part ${partNumber}/${totalParts} (${continuationMessage.length} chars)`); + + const success = await this._sendSingleMessage( + chatId, + continuationMessage, + null, // No buttons for continuation messages + sessionId, + 'Markdown', + firstMessageId // Reply to first message to create thread + ); + + if (success.success) { + successfulParts++; + } else { + console.log(`[DEBUG] Failed to send part ${partNumber}/${totalParts}, trying plain text`); + // Try plain text fallback + const plainMessage = continuationMessage.replace(/\*([^*]+)\*/g, '$1').replace(/\\(.)/g, '$1'); + const fallbackSuccess = await this._sendSingleMessage( + chatId, plainMessage, null, sessionId, null, firstMessageId + ); + if (fallbackSuccess.success) { + successfulParts++; + } + } + + // Add delay to prevent rate limiting + await new Promise(resolve => setTimeout(resolve, 800)); + } + + console.log(`[DEBUG] Multi-message sending completed: ${successfulParts}/${totalParts} parts sent successfully`); + + // Send a final summary if not all parts were sent + if (successfulParts < totalParts) { + const summaryMessage = `āš ļø *Message Status:* ${successfulParts}/${totalParts} parts sent successfully\n\nSome parts may have been skipped due to formatting issues\\.`; + await this._sendSingleMessage(chatId, summaryMessage, null, sessionId, 'Markdown', firstMessageId); + } + + return successfulParts > 0; // Success if at least first part was sent + + } catch (error) { + console.log(`[DEBUG] Error in _sendMultipleMessages: ${error.message}`); + console.log(`[DEBUG] Stack trace:`, error.stack); + this.logger.error('Failed to send multiple messages:', error.message); + return false; + } + } + + /** + * Send a single message with enhanced error handling + * @param {string} chatId - Telegram chat ID + * @param {string} messageText - Message text + * @param {Array} buttons - Inline keyboard buttons (optional) + * @param {string} sessionId - Session ID for logging + * @param {string} parseMode - Parse mode (optional) + * @param {number} replyToMessageId - Reply to message ID (optional) + * @returns {Promise} - {success: boolean, messageId: number} + */ + async _sendSingleMessage(chatId, messageText, buttons = null, sessionId = 'unknown', parseMode = null, replyToMessageId = null) { + const requestData = { + chat_id: chatId, + text: messageText + }; + + if (parseMode) { + requestData.parse_mode = parseMode; + } + + if (buttons) { + requestData.reply_markup = { + inline_keyboard: buttons + }; + } + + if (replyToMessageId) { + requestData.reply_to_message_id = replyToMessageId; + } + + try { + console.log(`[DEBUG] Sending message (${messageText.length} chars) to chat ${chatId}`); + + const response = await axios.post( + `${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`, + requestData, + { + ...this._getNetworkOptions(), + timeout: 15000 + } + ); + + const messageId = response.data.result.message_id; + console.log(`[DEBUG] āœ… Message sent successfully, ID: ${messageId}`); + + return { success: true, messageId: messageId }; + + } catch (error) { + const errorData = error.response?.data; + const errorMessage = errorData?.description || error.message; + const errorCode = errorData?.error_code; + + console.log(`[DEBUG] āŒ Failed to send message: ${errorCode} - ${errorMessage}`); + + // Try fallback without formatting + if (parseMode && errorCode === 400) { + console.log(`[DEBUG] Retrying without parse mode...`); + try { + const fallbackData = { ...requestData }; + delete fallbackData.parse_mode; + fallbackData.text = this._createSafeText(messageText); + + const fallbackResponse = await axios.post( + `${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`, + fallbackData, + { + ...this._getNetworkOptions(), + timeout: 15000 + } + ); + + const fallbackMessageId = fallbackResponse.data.result.message_id; + console.log(`[DEBUG] āœ… Fallback message sent successfully, ID: ${fallbackMessageId}`); + + return { success: true, messageId: fallbackMessageId }; + + } catch (fallbackError) { + console.log(`[DEBUG] āŒ Fallback also failed: ${fallbackError.response?.data?.description || fallbackError.message}`); + } + } + + return { success: false, messageId: null }; + } + } + async _createSession(sessionId, notification, token) { const session = { id: sessionId, diff --git a/src/daemon/taskping-daemon.js b/src/daemon/taskping-daemon.js old mode 100755 new mode 100644 diff --git a/src/data/session-map.json b/src/data/session-map.json index 7494634..940a4c8 100644 --- a/src/data/session-map.json +++ b/src/data/session-map.json @@ -907,5 +907,95 @@ "sessionId": "197cd53a-4a64-42a9-8262-8e3b429f08b8", "tmuxSession": "claude-session", "description": "completed - ecllipse" + }, + "TJ5VIF92": { + "type": "pty", + "createdAt": 1756992584, + "expiresAt": 1757078984, + "cwd": "/home/lsamc/.local/src/Claude-Code-Remote", + "sessionId": "019ab1c3-bde2-4d00-b21e-23167fe52c16", + "tmuxSession": "claude-session", + "description": "completed - Claude-Code-Remote" + }, + "J56R0D84": { + "type": "pty", + "createdAt": 1757031172, + "expiresAt": 1757117572, + "cwd": "/home/lsamc/.local/src/Claude-Code-Remote", + "sessionId": "cb99ffc3-ee1f-44be-9438-666e9b7fcd54", + "tmuxSession": "claude-session", + "description": "completed - Claude-Code-Remote" + }, + "2A26K43N": { + "type": "pty", + "createdAt": 1757031544, + "expiresAt": 1757117944, + "cwd": "/home/lsamc/.local/src/Claude-Code-Remote", + "sessionId": "f930e41f-eb86-49d0-85ab-2fcc06aa27e9", + "tmuxSession": "claude-session", + "description": "completed - Claude-Code-Remote" + }, + "30W8P8Y1": { + "type": "pty", + "createdAt": 1757031609, + "expiresAt": 1757118009, + "cwd": "/home/lsamc/develop/ecllipse", + "sessionId": "945a07d5-5403-433e-8f90-951e639e0c0c", + "tmuxSession": "claude-session", + "description": "completed - ecllipse" + }, + "ZNE082NM": { + "type": "pty", + "createdAt": 1757031634, + "expiresAt": 1757118034, + "cwd": "/home/lsamc/develop/ecllipse", + "sessionId": "c30b0f1f-1d4f-438f-a7c8-81fb9d8ddd7c", + "tmuxSession": "claude-session", + "description": "completed - ecllipse" + }, + "G73CZLO4": { + "type": "pty", + "createdAt": 1757031693, + "expiresAt": 1757118093, + "cwd": "/home/lsamc/develop/ecllipse", + "sessionId": "c4d9c060-83f4-4fc1-b83a-41e3045df66a", + "tmuxSession": "claude-session", + "description": "completed - ecllipse" + }, + "6GAO026F": { + "type": "pty", + "createdAt": 1757031822, + "expiresAt": 1757118222, + "cwd": "/home/lsamc/develop/ecllipse", + "sessionId": "6feee05d-cdf5-41ab-9eb7-3e2b39be97cd", + "tmuxSession": "claude-session", + "description": "completed - ecllipse" + }, + "0ZDCUMDH": { + "type": "pty", + "createdAt": 1757031847, + "expiresAt": 1757118247, + "cwd": "/home/lsamc/.local/src/Claude-Code-Remote", + "sessionId": "6b330566-d096-4862-9afe-aa32e038e9e9", + "tmuxSession": "claude-session", + "description": "completed - Claude-Code-Remote" + }, + "RJMOLNH2": { + "type": "pty", + "createdAt": 1757032064, + "expiresAt": 1757118464, + "cwd": "/home/lsamc/.local/src/Claude-Code-Remote", + "sessionId": "f394b1b0-9ea2-4106-b27a-75842319ed31", + "tmuxSession": "claude-session", + "description": "completed - Claude-Code-Remote" + }, + "E1E1RJD2": { + "type": "pty", + "createdAt": 1757032743, + "expiresAt": 1757119143, + "cwd": "/home/lsamc/.local/src/Claude-Code-Remote", + "sessionId": "f0acac1d-9b96-4baa-8622-cb030c300767", + "tmuxSession": "claude-session", + "description": "completed - Claude-Code-Remote" } } \ No newline at end of file diff --git a/src/utils/tmux-monitor.js b/src/utils/tmux-monitor.js index 3b2056b..1173f40 100644 --- a/src/utils/tmux-monitor.js +++ b/src/utils/tmux-monitor.js @@ -613,6 +613,12 @@ class TmuxMonitor extends EventEmitter { console.log(`[DEBUG] Total lines in buffer: ${lines.length}`); console.log(`[DEBUG] Last 5 lines:`, lines.slice(-5)); + // Save full buffer for debugging (first 50 and last 50 lines) + if (lines.length > 10) { + console.log(`[DEBUG] Buffer preview - First 10 lines:`, lines.slice(0, 10)); + console.log(`[DEBUG] Buffer preview - Last 10 lines:`, lines.slice(-10)); + } + let userQuestion = ''; let claudeResponse = ''; let responseLines = []; @@ -636,13 +642,17 @@ class TmuxMonitor extends EventEmitter { for (let i = lastUserIndex + 1; i < lines.length; i++) { const line = lines[i].trim(); - // Skip empty lines and system lines + // Skip empty lines and system lines, but be more selective if (!line || line.includes('? for shortcuts') || line.match(/^[╭╰│─]+$/) || line.startsWith('$') || line.startsWith('#') || - line.startsWith('[')) { + line.match(/^\[.*\]$/) || // System messages in brackets + line.includes('(esc to interrupt)') || // Claude Code interrupt message + line.match(/^\+\s*Cascading/) || // Cascading status message + line.match(/^_{3,}$/)) { // Multiple underscores (display artifacts) + console.log(`[DEBUG] Skipping system line: ${line}`); continue; } @@ -664,29 +674,87 @@ class TmuxMonitor extends EventEmitter { responseLines = [line.startsWith('āŗ ') ? line.substring(2).trim() : line]; console.log(`[DEBUG] Found response start at line ${i}: ${line}`); } else if (foundResponseStart) { - // Stop if we hit another user input or prompt - if (line.startsWith('> ') || line.includes('│ > │') || line.startsWith('ā•­')) { - console.log(`[DEBUG] Stopping response collection at line ${i}: ${line}`); + // Only stop for clear prompt indicators, not for special characters or formatting + if (line.startsWith('> ') && line.length > 2) { // New user input + console.log(`[DEBUG] Stopping response collection at new user input: ${line}`); break; } + // Stop at command prompt box start (new interaction) + if (line.startsWith('ā•­') && line.includes('─')) { + console.log(`[DEBUG] Stopping response collection at prompt box: ${line}`); + break; + } + // Continue collecting response, ignoring formatting artifacts responseLines.push(line); + console.log(`[DEBUG] Added response line ${responseLines.length}: ${line.substring(0, 50)}...`); } } } - // Fallback: Look for any Claude response pattern in the last 20 lines + // Fallback: Look for any Claude response pattern in a larger range if (responseLines.length === 0) { console.log(`[DEBUG] No response found after user input, trying fallback method`); - const recentLines = lines.slice(-20); + const recentLines = lines.slice(-40); // Increased range + let inFallbackResponse = false; for (let i = 0; i < recentLines.length; i++) { const line = recentLines[i].trim(); - if (line.startsWith('āŗ ') || - (line.length > 10 && !line.startsWith('> ') && !line.includes('? for shortcuts'))) { - responseLines.push(line.startsWith('āŗ ') ? line.substring(2).trim() : line); - console.log(`[DEBUG] Fallback found response line: ${line}`); + // Skip problematic lines even in fallback + if (!line || + line.includes('(esc to interrupt)') || + line.match(/^\+\s*Cascading/) || + line.match(/^_{3,}$/) || + line.includes('? for shortcuts')) { + continue; } + + // Look for Claude response indicators + if (line.startsWith('āŗ ') || + (line.length > 5 && + !line.startsWith('> ') && + !line.startsWith('$') && + !line.startsWith('#') && + !line.match(/^\[.*\]$/))) { + + const cleanLine = line.startsWith('āŗ ') ? line.substring(2).trim() : line; + responseLines.push(cleanLine); + inFallbackResponse = true; + console.log(`[DEBUG] Fallback found response line: ${cleanLine.substring(0, 50)}...`); + } else if (inFallbackResponse && line.length > 0) { + // Continue collecting if we're in a response and line isn't a clear break + responseLines.push(line); + console.log(`[DEBUG] Fallback continued response: ${line.substring(0, 50)}...`); + } + } + } + + // Super fallback: Get any meaningful content if still empty + if (responseLines.length === 0) { + console.log(`[DEBUG] Still no response, trying super fallback - scanning all meaningful lines`); + const meaningfulLines = []; + + for (let i = Math.max(0, lastUserIndex + 1); i < lines.length; i++) { + const line = lines[i].trim(); + + // Collect any line that seems like content (not system/formatting) + if (line.length > 5 && + !line.includes('? for shortcuts') && + !line.match(/^[╭╰│─\s]+$/) && + !line.includes('(esc to interrupt)') && + !line.match(/^\+\s*Cascading/) && + !line.match(/^_{3,}$/) && + !line.startsWith('> ') && + !line.startsWith('$') && + !line.startsWith('#')) { + + meaningfulLines.push(line); + } + } + + if (meaningfulLines.length > 0) { + responseLines = meaningfulLines; + console.log(`[DEBUG] Super fallback collected ${meaningfulLines.length} meaningful lines`); } } diff --git a/start-all-webhooks.js b/start-all-webhooks.js old mode 100755 new mode 100644 diff --git a/start-line-webhook.js b/start-line-webhook.js old mode 100755 new mode 100644 diff --git a/start-relay-pty.js b/start-relay-pty.js old mode 100755 new mode 100644 diff --git a/start-telegram-webhook.js b/start-telegram-webhook.js old mode 100755 new mode 100644 diff --git a/test-complete-flow.sh b/test-complete-flow.sh old mode 100755 new mode 100644 diff --git a/test-injection.js b/test-injection.js old mode 100755 new mode 100644 diff --git a/test-long-email.js b/test-long-email.js old mode 100755 new mode 100644 diff --git a/test-telegram-notification.js b/test-telegram-notification.js old mode 100755 new mode 100644 diff --git a/test-telegram-setup.sh b/test-telegram-setup.sh old mode 100755 new mode 100644