@link-assistant/hive-mind 0.54.3 ā 0.54.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/package.json +1 -1
- package/src/telegram-bot.mjs +62 -62
- package/src/telegram-markdown.lib.mjs +76 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 0.54.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix duplicate APT sources warning in installation script
|
|
8
|
+
- Add `cleanup_duplicate_apt_sources()` function to detect and remove duplicate APT source files
|
|
9
|
+
- Clean up duplicate Microsoft Edge sources (`microsoft-edge.list` vs `microsoft-edge-stable.list`)
|
|
10
|
+
- Clean up duplicate Google Chrome sources (`google-chrome.list` vs `google-chrome-stable.list`)
|
|
11
|
+
- Run cleanup before `apt update` to prevent "Target Packages configured multiple times" warnings
|
|
12
|
+
- Ensures script supports clean upgrade mode when run on previously installed systems
|
|
13
|
+
|
|
14
|
+
Improve Telegram bot error messages for better user experience (issue #1070)
|
|
15
|
+
- Enhanced URL validation to provide specific, actionable error messages based on URL type (issues list, pulls list, repository)
|
|
16
|
+
- Added step-by-step fix instructions with examples when users provide wrong URL formats
|
|
17
|
+
- Improved global error handler to properly escape Markdown special characters, preventing "400: Bad Request: can't parse entities" errors
|
|
18
|
+
- Added special handling for Telegram API parsing errors with clearer messaging
|
|
19
|
+
- Added `cleanNonPrintableChars()` to automatically remove invisible Unicode characters from user input
|
|
20
|
+
- Added `makeSpecialCharsVisible()` to show users exactly where problematic special characters are in their input
|
|
21
|
+
- Enhanced error messages to display user input with special characters made visible for easier debugging
|
|
22
|
+
- Refactored telegram-bot.mjs to meet 1500 line limit requirement
|
|
23
|
+
- Created comprehensive test suites to verify URL validation improvements and special character handling
|
|
24
|
+
- Documented case study analysis in docs/case-studies/issue-1070/ANALYSIS.md
|
|
25
|
+
|
|
26
|
+
## 0.54.4
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- 4e53d67: fix: resolve TypeError in telegram-bot when using --tokens-budget-stats
|
|
31
|
+
|
|
32
|
+
Fixed type safety bug that prevented the --tokens-budget-stats option from working via telegram bot configuration overrides. Changed from lino.parse() to lino.parseStringValues() to ensure only string values are returned, making .trim() safe to call. The feature was already fully implemented but crashed when used via TELEGRAM_HIVE_OVERRIDES or TELEGRAM_SOLVE_OVERRIDES.
|
|
33
|
+
|
|
3
34
|
## 0.54.3
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/package.json
CHANGED
package/src/telegram-bot.mjs
CHANGED
|
@@ -47,7 +47,7 @@ const { validateModelName } = await import('./model-validation.lib.mjs');
|
|
|
47
47
|
// Import libraries for /limits, /version, and markdown escaping
|
|
48
48
|
const { formatUsageMessage, getAllCachedLimits } = await import('./limits.lib.mjs');
|
|
49
49
|
const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
|
|
50
|
-
const { escapeMarkdown, escapeMarkdownV2 } = await import('./telegram-markdown.lib.mjs');
|
|
50
|
+
const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
|
|
51
51
|
const { getSolveQueue, getRunningClaudeProcesses, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
|
|
52
52
|
|
|
53
53
|
const config = yargs(hideBin(process.argv))
|
|
@@ -150,7 +150,7 @@ const allowedChats = resolvedAllowedChats ? lino.parseNumericIds(resolvedAllowed
|
|
|
150
150
|
const resolvedSolveOverrides = config.solveOverrides || getenv('TELEGRAM_SOLVE_OVERRIDES', '');
|
|
151
151
|
const solveOverrides = resolvedSolveOverrides
|
|
152
152
|
? lino
|
|
153
|
-
.
|
|
153
|
+
.parseStringValues(resolvedSolveOverrides)
|
|
154
154
|
.map(line => line.trim())
|
|
155
155
|
.filter(line => line)
|
|
156
156
|
: [];
|
|
@@ -158,7 +158,7 @@ const solveOverrides = resolvedSolveOverrides
|
|
|
158
158
|
const resolvedHiveOverrides = config.hiveOverrides || getenv('TELEGRAM_HIVE_OVERRIDES', '');
|
|
159
159
|
const hiveOverrides = resolvedHiveOverrides
|
|
160
160
|
? lino
|
|
161
|
-
.
|
|
161
|
+
.parseStringValues(resolvedHiveOverrides)
|
|
162
162
|
.map(line => line.trim())
|
|
163
163
|
.filter(line => line)
|
|
164
164
|
: [];
|
|
@@ -643,10 +643,21 @@ function validateGitHubUrl(args, options = {}) {
|
|
|
643
643
|
// Check if the URL type is allowed for this command
|
|
644
644
|
if (!allowedTypes.includes(parsed.type)) {
|
|
645
645
|
const allowedTypesStr = allowedTypes.map(t => (t === 'pull' ? 'pull request' : t)).join(', ');
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
646
|
+
const baseUrl = `https://github.com/${parsed.owner}/${parsed.repo}`;
|
|
647
|
+
|
|
648
|
+
// Provide specific, helpful error messages based on the URL type
|
|
649
|
+
let error;
|
|
650
|
+
if (parsed.type === 'issues_list') {
|
|
651
|
+
error = `URL points to the issues list page, but you need a specific issue\n\nš” How to fix:\n1. Open the repository: ${url}\n2. Click on a specific issue\n3. Copy the URL (it should end with /issues/NUMBER)\n\nExample: \`${baseUrl}/issues/1\``;
|
|
652
|
+
} else if (parsed.type === 'pulls_list') {
|
|
653
|
+
error = `URL points to the pull requests list page, but you need a specific pull request\n\nš” How to fix:\n1. Open the repository: ${url}\n2. Click on a specific pull request\n3. Copy the URL (it should end with /pull/NUMBER)\n\nExample: \`${baseUrl}/pull/1\``;
|
|
654
|
+
} else if (parsed.type === 'repo') {
|
|
655
|
+
error = `URL points to a repository, but you need a specific ${allowedTypesStr}\n\nš” How to fix:\n1. Go to: ${url}/issues\n2. Click on an issue to solve\n3. Use the full URL with the issue number\n\nExample: \`${baseUrl}/issues/1\``;
|
|
656
|
+
} else {
|
|
657
|
+
error = `URL must be a GitHub ${allowedTypesStr} (not ${parsed.type.replace('_', ' ')})`;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return { valid: false, error };
|
|
650
661
|
}
|
|
651
662
|
|
|
652
663
|
return { valid: true };
|
|
@@ -707,7 +718,7 @@ function extractGitHubUrl(text) {
|
|
|
707
718
|
return { url: null, error: null, linkCount: 0 };
|
|
708
719
|
}
|
|
709
720
|
|
|
710
|
-
|
|
721
|
+
text = cleanNonPrintableChars(text); // Clean non-printable chars before processing
|
|
711
722
|
const words = text.split(/\s+/);
|
|
712
723
|
const foundUrls = [];
|
|
713
724
|
|
|
@@ -1313,38 +1324,46 @@ bot.catch((error, ctx) => {
|
|
|
1313
1324
|
|
|
1314
1325
|
// Try to notify the user about the error with more details
|
|
1315
1326
|
if (ctx?.reply) {
|
|
1316
|
-
//
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1327
|
+
// Detect if this is a Telegram API parsing error
|
|
1328
|
+
const isTelegramParsingError = error.message && (error.message.includes("can't parse entities") || error.message.includes("Can't parse entities") || error.message.includes("can't find end of") || (error.message.includes('Bad Request') && error.message.includes('400')));
|
|
1329
|
+
|
|
1330
|
+
let errorMessage;
|
|
1331
|
+
|
|
1332
|
+
if (isTelegramParsingError) {
|
|
1333
|
+
// Special handling for Telegram API parsing errors caused by unescaped special characters
|
|
1334
|
+
errorMessage = `ā A message formatting error occurred.\n\nš” This usually means there was a problem with special characters in the response.\nPlease try your command again with a different URL or contact support.`;
|
|
1335
|
+
// Show the user's input with special characters visible (if available)
|
|
1336
|
+
if (ctx.message?.text) {
|
|
1337
|
+
const cleanedInput = cleanNonPrintableChars(ctx.message.text);
|
|
1338
|
+
const visibleInput = makeSpecialCharsVisible(cleanedInput, { maxLength: 150 });
|
|
1339
|
+
if (visibleInput !== cleanedInput) errorMessage += `\n\nš Your input (with special chars visible):\n\`${escapeMarkdown(visibleInput)}\``;
|
|
1340
|
+
}
|
|
1341
|
+
if (VERBOSE) {
|
|
1342
|
+
const escapedError = escapeMarkdown(error.message || 'Unknown error');
|
|
1343
|
+
errorMessage += `\n\nš Debug info: ${escapedError}\nUpdate ID: ${ctx.update.update_id}`;
|
|
1344
|
+
}
|
|
1345
|
+
} else {
|
|
1346
|
+
// Build informative error message for other errors
|
|
1347
|
+
errorMessage = 'ā An error occurred while processing your request.\n\n';
|
|
1348
|
+
if (error.message) {
|
|
1349
|
+
// Filter out sensitive info and escape markdown
|
|
1350
|
+
const sanitizedMessage = escapeMarkdown(
|
|
1351
|
+
error.message
|
|
1352
|
+
.replace(/token[s]?\s*[:=]\s*[\w-]+/gi, 'token: [REDACTED]')
|
|
1353
|
+
.replace(/password[s]?\s*[:=]\s*[\w-]+/gi, 'password: [REDACTED]')
|
|
1354
|
+
.replace(/api[_-]?key[s]?\s*[:=]\s*[\w-]+/gi, 'api_key: [REDACTED]')
|
|
1355
|
+
);
|
|
1356
|
+
errorMessage += `Details: ${sanitizedMessage}\n`;
|
|
1357
|
+
}
|
|
1358
|
+
errorMessage += '\nš” Troubleshooting:\n⢠Try running the command again\n⢠Check if all required parameters are correct\n⢠Use /help to see command examples\n⢠If the issue persists, contact support with the error details above';
|
|
1359
|
+
if (VERBOSE) errorMessage += `\n\nš Debug info: Update ID: ${ctx.update.update_id}`;
|
|
1342
1360
|
}
|
|
1343
1361
|
|
|
1344
1362
|
ctx.reply(errorMessage, { parse_mode: 'Markdown' }).catch(replyError => {
|
|
1345
1363
|
console.error('Failed to send error message to user:', replyError);
|
|
1346
1364
|
// Try sending a simple text message without Markdown if Markdown parsing failed
|
|
1347
|
-
|
|
1365
|
+
const plainMessage = `An error occurred while processing your request. Please try again or contact support.\n\nError: ${error.message || 'Unknown error'}`;
|
|
1366
|
+
ctx.reply(plainMessage).catch(fallbackError => {
|
|
1348
1367
|
console.error('Failed to send fallback error message:', fallbackError);
|
|
1349
1368
|
});
|
|
1350
1369
|
});
|
|
@@ -1361,29 +1380,17 @@ if (allowedChats && allowedChats.length > 0) {
|
|
|
1361
1380
|
} else {
|
|
1362
1381
|
console.log('Allowed chats: All (no restrictions)');
|
|
1363
1382
|
}
|
|
1364
|
-
console.log('Commands enabled:', {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
});
|
|
1368
|
-
if (solveOverrides.length > 0) {
|
|
1369
|
-
console.log('Solve overrides (lino):', lino.format(solveOverrides));
|
|
1370
|
-
}
|
|
1371
|
-
if (hiveOverrides.length > 0) {
|
|
1372
|
-
console.log('Hive overrides (lino):', lino.format(hiveOverrides));
|
|
1373
|
-
}
|
|
1383
|
+
console.log('Commands enabled:', { solve: solveEnabled, hive: hiveEnabled });
|
|
1384
|
+
if (solveOverrides.length > 0) console.log('Solve overrides (lino):', lino.format(solveOverrides));
|
|
1385
|
+
if (hiveOverrides.length > 0) console.log('Hive overrides (lino):', lino.format(hiveOverrides));
|
|
1374
1386
|
if (VERBOSE) {
|
|
1375
1387
|
console.log('[VERBOSE] Verbose logging enabled');
|
|
1376
1388
|
console.log('[VERBOSE] Bot start time (Unix):', BOT_START_TIME);
|
|
1377
1389
|
console.log('[VERBOSE] Bot start time (ISO):', new Date(BOT_START_TIME * 1000).toISOString());
|
|
1378
1390
|
}
|
|
1379
1391
|
|
|
1380
|
-
// Delete
|
|
1381
|
-
|
|
1382
|
-
// If the bot was previously configured with a webhook (or if one exists),
|
|
1383
|
-
// we must delete it to allow polling mode to receive messages
|
|
1384
|
-
if (VERBOSE) {
|
|
1385
|
-
console.log('[VERBOSE] Deleting webhook...');
|
|
1386
|
-
}
|
|
1392
|
+
// Delete existing webhook (critical: webhooks prevent polling from working)
|
|
1393
|
+
if (VERBOSE) console.log('[VERBOSE] Deleting webhook...');
|
|
1387
1394
|
bot.telegram
|
|
1388
1395
|
.deleteWebhook({ drop_pending_updates: true })
|
|
1389
1396
|
.then(result => {
|
|
@@ -1398,19 +1405,12 @@ bot.telegram
|
|
|
1398
1405
|
});
|
|
1399
1406
|
}
|
|
1400
1407
|
return bot.launch({
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
allowedUpdates: ['message', 'callback_query'],
|
|
1404
|
-
// Drop any pending updates that were sent before the bot started
|
|
1405
|
-
// This ensures we only process new messages sent after this bot instance started
|
|
1406
|
-
dropPendingUpdates: true,
|
|
1408
|
+
allowedUpdates: ['message', 'callback_query'], // Receive messages and callback queries
|
|
1409
|
+
dropPendingUpdates: true, // Drop pending updates sent before bot started
|
|
1407
1410
|
});
|
|
1408
1411
|
})
|
|
1409
1412
|
.then(async () => {
|
|
1410
|
-
|
|
1411
|
-
if (isShuttingDown) {
|
|
1412
|
-
return; // Skip success messages if shutting down
|
|
1413
|
-
}
|
|
1413
|
+
if (isShuttingDown) return; // Skip success messages if shutting down
|
|
1414
1414
|
|
|
1415
1415
|
console.log('ā
SwarmMindBot is now running!');
|
|
1416
1416
|
console.log('Press Ctrl+C to stop');
|
|
@@ -62,3 +62,79 @@ export function escapeMarkdownV2(text, options = {}) {
|
|
|
62
62
|
|
|
63
63
|
return parts.join('');
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clean non-printable and problematic Unicode characters from text.
|
|
68
|
+
* Removes zero-width characters, control characters, and other invisible/problematic sequences.
|
|
69
|
+
* @param {string} text - Text to clean
|
|
70
|
+
* @returns {string} Cleaned text
|
|
71
|
+
*/
|
|
72
|
+
export function cleanNonPrintableChars(text) {
|
|
73
|
+
if (!text || typeof text !== 'string') return text;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
text
|
|
77
|
+
// Remove zero-width characters
|
|
78
|
+
.replace(/[\u200B-\u200D\uFEFF]/g, '')
|
|
79
|
+
// Remove other non-printable control characters (except newline, tab, carriage return)
|
|
80
|
+
// eslint-disable-next-line no-control-regex
|
|
81
|
+
.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F]/g, '')
|
|
82
|
+
// Remove soft hyphens
|
|
83
|
+
.replace(/\u00AD/g, '')
|
|
84
|
+
// Normalize whitespace (replace multiple spaces with single space)
|
|
85
|
+
.replace(/[ \t]+/g, ' ')
|
|
86
|
+
// Trim leading/trailing whitespace from each line
|
|
87
|
+
.split('\n')
|
|
88
|
+
.map(line => line.trim())
|
|
89
|
+
.join('\n')
|
|
90
|
+
.trim()
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Make special characters visible for debugging purposes.
|
|
96
|
+
* Replaces special characters with their Unicode escape sequences or names.
|
|
97
|
+
* Useful for showing users where problematic characters are in their input.
|
|
98
|
+
* @param {string} text - Text to make visible
|
|
99
|
+
* @param {Object} options - Configuration options
|
|
100
|
+
* @param {number} options.maxLength - Maximum length of output (default: 200)
|
|
101
|
+
* @returns {string} Text with special characters made visible
|
|
102
|
+
*/
|
|
103
|
+
export function makeSpecialCharsVisible(text, options = {}) {
|
|
104
|
+
if (!text || typeof text !== 'string') return text;
|
|
105
|
+
|
|
106
|
+
const { maxLength = 200 } = options;
|
|
107
|
+
|
|
108
|
+
// Map of special characters to their visible representations
|
|
109
|
+
const specialChars = {
|
|
110
|
+
'\u200B': '[ZWSP]', // Zero-width space
|
|
111
|
+
'\u200C': '[ZWNJ]', // Zero-width non-joiner
|
|
112
|
+
'\u200D': '[ZWJ]', // Zero-width joiner
|
|
113
|
+
'\uFEFF': '[BOM]', // Byte order mark / zero-width no-break space
|
|
114
|
+
'\u00AD': '[SHY]', // Soft hyphen
|
|
115
|
+
'\t': '[TAB]',
|
|
116
|
+
'\r': '[CR]',
|
|
117
|
+
'\n': '[LF]',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
let result = '';
|
|
121
|
+
for (let i = 0; i < text.length && result.length < maxLength; i++) {
|
|
122
|
+
const char = text[i];
|
|
123
|
+
const code = char.charCodeAt(0);
|
|
124
|
+
|
|
125
|
+
if (specialChars[char]) {
|
|
126
|
+
result += specialChars[char];
|
|
127
|
+
} else if (code < 32 || (code >= 127 && code < 160)) {
|
|
128
|
+
// Control characters
|
|
129
|
+
result += `[U+${code.toString(16).toUpperCase().padStart(4, '0')}]`;
|
|
130
|
+
} else {
|
|
131
|
+
result += char;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (text.length > maxLength) {
|
|
136
|
+
result += '... (truncated)';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|