@link-assistant/hive-mind 0.41.3 ā 0.41.7
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 +23 -0
- package/package.json +1 -1
- package/src/github.lib.mjs +16 -0
- package/src/hive.mjs +3 -4
- package/src/solve.mjs +98 -27
- package/src/solve.validation.lib.mjs +3 -4
- package/src/telegram-bot.mjs +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 0.41.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 781a8e4: Fix: Upload logs when usage limit is reached
|
|
8
|
+
|
|
9
|
+
## 0.41.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 27bbc44: Add backslash detection and validation in GitHub URLs
|
|
14
|
+
|
|
15
|
+
When users provide URLs with backslashes (e.g., `https://github.com/owner/repo/issues/123\`), the system now properly validates them and provides helpful error messages with auto-corrected URL suggestions. According to RFC 3986, backslash is not a valid character in URL paths.
|
|
16
|
+
|
|
17
|
+
**Changes:**
|
|
18
|
+
|
|
19
|
+
- Enhanced `parseGitHubUrl()` function to detect backslashes in URL paths
|
|
20
|
+
- Updated all validation points (Telegram bot `/solve` and `/hive` commands, CLI `hive` and `solve` commands)
|
|
21
|
+
- Provides user-friendly error messages with corrected URL suggestions
|
|
22
|
+
- Comprehensive test suite for backslash validation scenarios
|
|
23
|
+
|
|
24
|
+
Fixes #923
|
|
25
|
+
|
|
3
26
|
## 0.41.3
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/package.json
CHANGED
package/src/github.lib.mjs
CHANGED
|
@@ -1107,6 +1107,22 @@ export function parseGitHubUrl(url) {
|
|
|
1107
1107
|
if (normalizedUrl.startsWith('http://')) {
|
|
1108
1108
|
normalizedUrl = normalizedUrl.replace(/^http:\/\//, 'https://');
|
|
1109
1109
|
}
|
|
1110
|
+
|
|
1111
|
+
// Check for backslashes in the URL path (excluding query params and hash)
|
|
1112
|
+
// According to RFC 3986, backslash is not a valid character in URL paths
|
|
1113
|
+
const urlBeforeQueryAndHash = normalizedUrl.split('?')[0].split('#')[0];
|
|
1114
|
+
if (urlBeforeQueryAndHash.includes('\\')) {
|
|
1115
|
+
// Generate suggested URL by replacing backslashes with forward slashes
|
|
1116
|
+
const suggestedUrl = urlBeforeQueryAndHash.replace(/\\/g, '/');
|
|
1117
|
+
const urlAfterPath = normalizedUrl.substring(urlBeforeQueryAndHash.length);
|
|
1118
|
+
|
|
1119
|
+
return {
|
|
1120
|
+
valid: false,
|
|
1121
|
+
error: 'Invalid character in URL: backslash (\\) is not allowed in URL paths',
|
|
1122
|
+
suggestion: suggestedUrl + urlAfterPath
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1110
1126
|
// Parse the URL
|
|
1111
1127
|
let urlObj;
|
|
1112
1128
|
try {
|
package/src/hive.mjs
CHANGED
|
@@ -337,10 +337,9 @@ if (githubUrl) {
|
|
|
337
337
|
|
|
338
338
|
if (!parsedUrl.valid) {
|
|
339
339
|
console.error('Error: Invalid GitHub URL format');
|
|
340
|
-
if (parsedUrl.error) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
console.error('Expected: https://github.com/owner or https://github.com/owner/repo');
|
|
340
|
+
if (parsedUrl.error) console.error(` ${parsedUrl.error}`);
|
|
341
|
+
if (parsedUrl.suggestion) console.error(`\nš” Did you mean: ${parsedUrl.suggestion}`);
|
|
342
|
+
console.error('\nExpected: https://github.com/owner or https://github.com/owner/repo');
|
|
344
343
|
console.error('You can use any of these formats:');
|
|
345
344
|
console.error(' - https://github.com/owner');
|
|
346
345
|
console.error(' - https://github.com/owner/repo');
|
package/src/solve.mjs
CHANGED
|
@@ -875,8 +875,42 @@ try {
|
|
|
875
875
|
await log('\nā USAGE LIMIT REACHED!');
|
|
876
876
|
await log(' The AI tool has reached its usage limit.');
|
|
877
877
|
|
|
878
|
-
//
|
|
879
|
-
if (prNumber) {
|
|
878
|
+
// If --attach-logs is enabled and we have a PR, attach logs with usage limit details
|
|
879
|
+
if (shouldAttachLogs && sessionId && prNumber) {
|
|
880
|
+
await log('\nš Attaching logs to Pull Request...');
|
|
881
|
+
try {
|
|
882
|
+
// Build resume command
|
|
883
|
+
const resumeCommand = `${process.argv[0]} ${process.argv[1]} ${issueUrl} --resume ${sessionId}`;
|
|
884
|
+
const logUploadSuccess = await attachLogToGitHub({
|
|
885
|
+
logFile: getLogFile(),
|
|
886
|
+
targetType: 'pr',
|
|
887
|
+
targetNumber: prNumber,
|
|
888
|
+
owner,
|
|
889
|
+
repo,
|
|
890
|
+
$,
|
|
891
|
+
log,
|
|
892
|
+
sanitizeLogContent,
|
|
893
|
+
// Mark this as a usage limit case for proper formatting
|
|
894
|
+
isUsageLimit: true,
|
|
895
|
+
limitResetTime: global.limitResetTime,
|
|
896
|
+
toolName: (argv.tool || 'AI tool').toString().toLowerCase() === 'claude' ? 'Claude' :
|
|
897
|
+
(argv.tool || 'AI tool').toString().toLowerCase() === 'codex' ? 'Codex' :
|
|
898
|
+
(argv.tool || 'AI tool').toString().toLowerCase() === 'opencode' ? 'OpenCode' :
|
|
899
|
+
(argv.tool || 'AI tool').toString().toLowerCase() === 'agent' ? 'Agent' : 'AI tool',
|
|
900
|
+
resumeCommand,
|
|
901
|
+
sessionId
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
if (logUploadSuccess) {
|
|
905
|
+
await log(' ā
Logs uploaded successfully');
|
|
906
|
+
} else {
|
|
907
|
+
await log(' ā ļø Failed to upload logs', { verbose: true });
|
|
908
|
+
}
|
|
909
|
+
} catch (uploadError) {
|
|
910
|
+
await log(` ā ļø Error uploading logs: ${uploadError.message}`, { verbose: true });
|
|
911
|
+
}
|
|
912
|
+
} else if (prNumber) {
|
|
913
|
+
// Fallback: Post simple failure comment if logs are not attached
|
|
880
914
|
try {
|
|
881
915
|
const resetTime = global.limitResetTime;
|
|
882
916
|
const failureComment = resetTime
|
|
@@ -894,33 +928,70 @@ try {
|
|
|
894
928
|
|
|
895
929
|
await safeExit(1, 'Usage limit reached - use --auto-continue-on-limit-reset to wait for reset');
|
|
896
930
|
} else {
|
|
897
|
-
// auto-continue-on-limit-reset is enabled - post waiting comment
|
|
931
|
+
// auto-continue-on-limit-reset is enabled - attach logs and/or post waiting comment
|
|
898
932
|
if (prNumber && global.limitResetTime) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
933
|
+
// If --attach-logs is enabled, upload logs with usage limit details
|
|
934
|
+
if (shouldAttachLogs && sessionId) {
|
|
935
|
+
await log('\nš Attaching logs to Pull Request (auto-continue mode)...');
|
|
936
|
+
try {
|
|
937
|
+
// Build resume command
|
|
938
|
+
const resumeCommand = `${process.argv[0]} ${process.argv[1]} ${issueUrl} --resume ${sessionId}`;
|
|
939
|
+
const logUploadSuccess = await attachLogToGitHub({
|
|
940
|
+
logFile: getLogFile(),
|
|
941
|
+
targetType: 'pr',
|
|
942
|
+
targetNumber: prNumber,
|
|
943
|
+
owner,
|
|
944
|
+
repo,
|
|
945
|
+
$,
|
|
946
|
+
log,
|
|
947
|
+
sanitizeLogContent,
|
|
948
|
+
// Mark this as a usage limit case for proper formatting
|
|
949
|
+
isUsageLimit: true,
|
|
950
|
+
limitResetTime: global.limitResetTime,
|
|
951
|
+
toolName: (argv.tool || 'AI tool').toString().toLowerCase() === 'claude' ? 'Claude' :
|
|
952
|
+
(argv.tool || 'AI tool').toString().toLowerCase() === 'codex' ? 'Codex' :
|
|
953
|
+
(argv.tool || 'AI tool').toString().toLowerCase() === 'opencode' ? 'OpenCode' :
|
|
954
|
+
(argv.tool || 'AI tool').toString().toLowerCase() === 'agent' ? 'Agent' : 'AI tool',
|
|
955
|
+
resumeCommand,
|
|
956
|
+
sessionId
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
if (logUploadSuccess) {
|
|
960
|
+
await log(' ā
Logs uploaded successfully');
|
|
961
|
+
} else {
|
|
962
|
+
await log(' ā ļø Failed to upload logs', { verbose: true });
|
|
963
|
+
}
|
|
964
|
+
} catch (uploadError) {
|
|
965
|
+
await log(` ā ļø Error uploading logs: ${uploadError.message}`, { verbose: true });
|
|
966
|
+
}
|
|
967
|
+
} else {
|
|
968
|
+
// Fallback: Post simple waiting comment if logs are not attached
|
|
969
|
+
try {
|
|
970
|
+
// Calculate wait time in d:h:m:s format
|
|
971
|
+
const validation = await import('./solve.validation.lib.mjs');
|
|
972
|
+
const { calculateWaitTime } = validation;
|
|
973
|
+
const waitMs = calculateWaitTime(global.limitResetTime);
|
|
974
|
+
|
|
975
|
+
const formatWaitTime = (ms) => {
|
|
976
|
+
const seconds = Math.floor(ms / 1000);
|
|
977
|
+
const minutes = Math.floor(seconds / 60);
|
|
978
|
+
const hours = Math.floor(minutes / 60);
|
|
979
|
+
const days = Math.floor(hours / 24);
|
|
980
|
+
const s = seconds % 60;
|
|
981
|
+
const m = minutes % 60;
|
|
982
|
+
const h = hours % 24;
|
|
983
|
+
return `${days}:${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
const waitingComment = `ā³ **Usage Limit Reached - Waiting to Continue**\n\nThe AI tool has reached its usage limit. Auto-continue is enabled with \`--auto-continue-on-limit-reset\`.\n\n**Reset time:** ${global.limitResetTime}\n**Wait time:** ${formatWaitTime(waitMs)} (days:hours:minutes:seconds)\n\nThe session will automatically resume when the limit resets.\n\nSession ID: \`${sessionId}\``;
|
|
987
|
+
|
|
988
|
+
const commentResult = await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${waitingComment}`;
|
|
989
|
+
if (commentResult.code === 0) {
|
|
990
|
+
await log(' Posted waiting comment to PR');
|
|
991
|
+
}
|
|
992
|
+
} catch (error) {
|
|
993
|
+
await log(` Warning: Could not post waiting comment: ${cleanErrorMessage(error)}`, { verbose: true });
|
|
921
994
|
}
|
|
922
|
-
} catch (error) {
|
|
923
|
-
await log(` Warning: Could not post waiting comment: ${cleanErrorMessage(error)}`, { verbose: true });
|
|
924
995
|
}
|
|
925
996
|
}
|
|
926
997
|
}
|
|
@@ -66,10 +66,9 @@ export const validateGitHubUrl = (issueUrl) => {
|
|
|
66
66
|
|
|
67
67
|
if (!parsedUrl.valid) {
|
|
68
68
|
console.error('Error: Invalid GitHub URL format');
|
|
69
|
-
if (parsedUrl.error) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
console.error(' Please provide a valid GitHub issue or pull request URL');
|
|
69
|
+
if (parsedUrl.error) console.error(` ${parsedUrl.error}`);
|
|
70
|
+
if (parsedUrl.suggestion) console.error(`\nš” Did you mean: ${parsedUrl.suggestion}`);
|
|
71
|
+
console.error('\n Please provide a valid GitHub issue or pull request URL');
|
|
73
72
|
console.error(' Examples:');
|
|
74
73
|
console.error(' https://github.com/owner/repo/issues/123 (issue)');
|
|
75
74
|
console.error(' https://github.com/owner/repo/pull/456 (pull request)');
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -638,7 +638,8 @@ function validateGitHubUrl(args, options = {}) {
|
|
|
638
638
|
if (!parsed.valid) {
|
|
639
639
|
return {
|
|
640
640
|
valid: false,
|
|
641
|
-
error: parsed.error || 'Invalid GitHub URL'
|
|
641
|
+
error: parsed.error || 'Invalid GitHub URL',
|
|
642
|
+
suggestion: parsed.suggestion
|
|
642
643
|
};
|
|
643
644
|
}
|
|
644
645
|
|
|
@@ -987,7 +988,12 @@ bot.command(/^solve$/i, async (ctx) => {
|
|
|
987
988
|
|
|
988
989
|
const validation = validateGitHubUrl(userArgs);
|
|
989
990
|
if (!validation.valid) {
|
|
990
|
-
|
|
991
|
+
let errorMsg = `ā ${validation.error}`;
|
|
992
|
+
if (validation.suggestion) {
|
|
993
|
+
errorMsg += `\n\nš” Did you mean: \`${validation.suggestion}\``;
|
|
994
|
+
}
|
|
995
|
+
errorMsg += '\n\nExample: `/solve https://github.com/owner/repo/issues/123`\n\nOr reply to a message containing a GitHub link with `/solve`';
|
|
996
|
+
await ctx.reply(errorMsg, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
991
997
|
return;
|
|
992
998
|
}
|
|
993
999
|
|
|
@@ -1136,7 +1142,12 @@ bot.command(/^hive$/i, async (ctx) => {
|
|
|
1136
1142
|
exampleUrl: 'https://github.com/owner/repo'
|
|
1137
1143
|
});
|
|
1138
1144
|
if (!validation.valid) {
|
|
1139
|
-
|
|
1145
|
+
let errorMsg = `ā ${validation.error}`;
|
|
1146
|
+
if (validation.suggestion) {
|
|
1147
|
+
errorMsg += `\n\nš” Did you mean: \`${validation.suggestion}\``;
|
|
1148
|
+
}
|
|
1149
|
+
errorMsg += '\n\nExample: `/hive https://github.com/owner/repo`';
|
|
1150
|
+
await ctx.reply(errorMsg, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
1140
1151
|
return;
|
|
1141
1152
|
}
|
|
1142
1153
|
|