@link-assistant/hive-mind 1.23.1 → 1.23.3

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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.23.3
4
+
5
+ ### Patch Changes
6
+
7
+ - a797e56: fix: escape owner/repo names for Telegram MarkdownV2 in /merge command
8
+
9
+ Fixed the `/merge` command silently failing when updating Telegram messages for repositories with hyphens in their names (e.g., `link-assistant/hive-mind`). The issue was caused by unescaped special characters in MarkdownV2 format.
10
+
11
+ ## 1.23.2
12
+
13
+ ### Patch Changes
14
+
15
+ - 241ce36: Fix false error categorization and missing log upload for `--tool agent` auto-restart
16
+ - Fix `isUsageLimitError()` "resets" pattern causing false positives when scanning code output
17
+ - Changed from substring match to regex that requires time-like content after "resets"
18
+ - Prevents ordinary English words like "loads a shell and resets" from triggering usage limit detection
19
+ - Fix agent fallback pattern matching running after agent successfully recovered from errors
20
+ - Skip fallback when exitCode=0 and agentCompletedSuccessfully to prevent false error detection
21
+ - Upload failure logs when auto-restart iteration fails for `--tool agent` with `--attach-logs`
22
+ - Add comprehensive tests for false positive scenarios (Issue #1290)
23
+
3
24
  ## 1.23.1
4
25
 
5
26
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.23.1",
3
+ "version": "1.23.3",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
package/src/agent.lib.mjs CHANGED
@@ -784,7 +784,10 @@ export const executeAgentCommand = async params => {
784
784
  // Issue #1258: Fallback pattern match for error detection
785
785
  // When JSON parsing fails (e.g., multi-line pretty-printed JSON in logs),
786
786
  // we need to detect error patterns in the raw output string
787
- if (!outputError.detected && !streamingErrorDetected) {
787
+ // Issue #1290: Skip fallback when agent completed successfully with exit code 0
788
+ // The fallback can cause false positives when error events (like AI_JSONParseError)
789
+ // appear in the output but the agent recovered and completed successfully
790
+ if (!outputError.detected && !streamingErrorDetected && !(exitCode === 0 && agentCompletedSuccessfully)) {
788
791
  // Check for error type patterns in raw output (handles pretty-printed JSON)
789
792
  const errorTypePatterns = [
790
793
  { pattern: '"type": "error"', type: 'AgentError' },
package/src/solve.mjs CHANGED
@@ -1367,7 +1367,10 @@ try {
1367
1367
 
1368
1368
  // Attach updated logs to PR after auto-restart completes
1369
1369
  // Issue #1154: Skip if logs were already uploaded by verifyResults() to prevent duplicates
1370
- if (shouldAttachLogs && prNumber && !logsAlreadyUploaded) {
1370
+ // Issue #1290: Always upload if auto-restart ran but last iteration's logs weren't uploaded
1371
+ // This ensures final logs are uploaded even when the last iteration failed
1372
+ const autoRestartRanButNotUploaded = watchResult?.autoRestartIterationsRan && !watchResult?.lastIterationLogUploaded;
1373
+ if (shouldAttachLogs && prNumber && (!logsAlreadyUploaded || autoRestartRanButNotUploaded)) {
1371
1374
  await log('📎 Uploading working session logs to Pull Request...');
1372
1375
  try {
1373
1376
  const logUploadSuccess = await attachLogToGitHub({
@@ -1394,8 +1397,8 @@ try {
1394
1397
  } catch (uploadError) {
1395
1398
  await log(`âš ī¸ Error uploading logs: ${uploadError.message}`, { level: 'warning' });
1396
1399
  }
1397
- } else if (logsAlreadyUploaded) {
1398
- await log('â„šī¸ Logs already uploaded by verifyResults, skipping duplicate upload', { verbose: true });
1400
+ } else if (logsAlreadyUploaded && !autoRestartRanButNotUploaded) {
1401
+ await log('â„šī¸ Logs already uploaded by verifyResults, skipping duplicate upload');
1399
1402
  logsAttached = true;
1400
1403
  }
1401
1404
  }
@@ -51,6 +51,11 @@ export const watchForFeedback = async params => {
51
51
  let latestSessionId = null;
52
52
  let latestAnthropicCost = null;
53
53
 
54
+ // Issue #1290: Track whether auto-restart iterations actually ran and whether logs were uploaded
55
+ // This helps solve.mjs decide whether to upload final logs
56
+ let autoRestartIterationsRan = false;
57
+ let lastIterationLogUploaded = false;
58
+
54
59
  // Track consecutive API errors for retry limit
55
60
  const MAX_API_ERROR_RETRIES = 3;
56
61
  let consecutiveApiErrors = 0;
@@ -168,6 +173,8 @@ export const watchForFeedback = async params => {
168
173
 
169
174
  // Increment auto-restart counter and log restart number
170
175
  autoRestartCount++;
176
+ autoRestartIterationsRan = true; // Issue #1290: Mark that auto-restart iterations ran
177
+ lastIterationLogUploaded = false; // Reset log upload tracking for new iteration
171
178
  const restartLabel = firstIterationInTemporaryMode ? 'Initial restart' : `Restart ${autoRestartCount}/${maxAutoRestartIterations}`;
172
179
  await log(formatAligned('🔄', `${restartLabel}:`, `Running ${argv.tool.toUpperCase()} to handle uncommitted changes...`));
173
180
 
@@ -257,6 +264,60 @@ export const watchForFeedback = async params => {
257
264
  currentBackoffSeconds = watchInterval;
258
265
  await log(formatAligned('âš ī¸', `${argv.tool.toUpperCase()} execution failed`, 'Will retry in next check', 2));
259
266
  }
267
+
268
+ // Issue #1290: Upload failure logs for auto-restart iterations when --attach-logs is enabled
269
+ // This ensures that failed auto-restart sessions still report their logs
270
+ const shouldAttachLogs = argv.attachLogs || argv['attach-logs'];
271
+ if (isTemporaryWatch && prNumber && shouldAttachLogs) {
272
+ await log('');
273
+ await log(formatAligned('📎', 'Uploading auto-restart failure log...', ''));
274
+ try {
275
+ const logFile = getLogFile();
276
+ if (logFile) {
277
+ // Use "Auto-restart X/Y Failure Log" format to distinguish from success logs
278
+ const customTitle = `âš ī¸ Auto-restart ${autoRestartCount}/${maxAutoRestartIterations} Failure Log`;
279
+ const logUploadSuccess = await attachLogToGitHub({
280
+ logFile,
281
+ targetType: 'pr',
282
+ targetNumber: prNumber,
283
+ owner,
284
+ repo,
285
+ $,
286
+ log,
287
+ sanitizeLogContent,
288
+ verbose: argv.verbose,
289
+ customTitle,
290
+ sessionId: toolResult.sessionId || latestSessionId,
291
+ tempDir,
292
+ // Include error information in the log upload
293
+ errorMessage: toolResult.errorInfo?.message || toolResult.result || `${argv.tool.toUpperCase()} execution failed`,
294
+ // Include pricing data if available from failed attempt
295
+ publicPricingEstimate: toolResult.publicPricingEstimate,
296
+ pricingInfo: toolResult.pricingInfo,
297
+ // Mark if this was a usage limit failure
298
+ isUsageLimit: toolResult.limitReached,
299
+ limitResetTime: toolResult.limitResetTime,
300
+ });
301
+
302
+ if (logUploadSuccess) {
303
+ await log(formatAligned('', '✅ Auto-restart failure log uploaded to PR', '', 2));
304
+ lastIterationLogUploaded = true; // Issue #1290: Mark that logs were uploaded
305
+ } else {
306
+ await log(formatAligned('', 'âš ī¸ Could not upload auto-restart failure log', '', 2));
307
+ }
308
+ }
309
+ } catch (logUploadError) {
310
+ reportError(logUploadError, {
311
+ context: 'attach_auto_restart_failure_log',
312
+ prNumber,
313
+ owner,
314
+ repo,
315
+ autoRestartCount,
316
+ operation: 'upload_failure_log',
317
+ });
318
+ await log(formatAligned('', `âš ī¸ Log upload error: ${cleanErrorMessage(logUploadError)}`, '', 2));
319
+ }
320
+ }
260
321
  } else {
261
322
  // Success - reset error counters
262
323
  consecutiveApiErrors = 0;
@@ -306,6 +367,7 @@ export const watchForFeedback = async params => {
306
367
 
307
368
  if (logUploadSuccess) {
308
369
  await log(formatAligned('', '✅ Auto-restart session log uploaded to PR', '', 2));
370
+ lastIterationLogUploaded = true; // Issue #1290: Mark that logs were uploaded
309
371
  } else {
310
372
  await log(formatAligned('', 'âš ī¸ Could not upload auto-restart session log', '', 2));
311
373
  }
@@ -368,9 +430,12 @@ export const watchForFeedback = async params => {
368
430
  }
369
431
 
370
432
  // Return latest session data for accurate pricing in log uploads
433
+ // Issue #1290: Include flags to help solve.mjs decide whether to upload final logs
371
434
  return {
372
435
  latestSessionId,
373
436
  latestAnthropicCost,
437
+ autoRestartIterationsRan, // True if any auto-restart iterations actually ran
438
+ lastIterationLogUploaded, // True if the last iteration's logs were uploaded
374
439
  };
375
440
  };
376
441
 
@@ -440,7 +440,8 @@ export class MergeQueueProcessor {
440
440
  const update = this.getProgressUpdate();
441
441
 
442
442
  let message = `*Merge Queue*\n`;
443
- message += `${this.owner}/${this.repo}\n\n`;
443
+ // Issue #1292: Escape owner/repo for MarkdownV2 (may contain hyphens, underscores, etc.)
444
+ message += `${this.escapeMarkdown(this.owner)}/${this.escapeMarkdown(this.repo)}\n\n`;
444
445
 
445
446
  // Progress bar in code block for better style
446
447
  const progressBar = getProgressBar(update.progress.percentage);
@@ -515,7 +516,8 @@ export class MergeQueueProcessor {
515
516
  }
516
517
 
517
518
  let message = `${statusEmoji} *Merge Queue ${statusText}*\n`;
518
- message += `${this.owner}/${this.repo}\n\n`;
519
+ // Issue #1292: Escape owner/repo for MarkdownV2 (may contain hyphens, underscores, etc.)
520
+ message += `${this.escapeMarkdown(this.owner)}/${this.escapeMarkdown(this.repo)}\n\n`;
519
521
 
520
522
  // Final progress bar in code block
521
523
  const percentage = report.stats.total > 0 ? Math.round((report.stats.merged / report.stats.total) * 100) : 0;
@@ -33,6 +33,7 @@ export function isUsageLimitError(message) {
33
33
  const lowerMessage = message.toLowerCase();
34
34
 
35
35
  // Check for specific usage limit patterns
36
+ // Supports both plain string patterns (checked via .includes()) and RegExp patterns
36
37
  const patterns = [
37
38
  // Generic
38
39
  "you've hit your usage limit",
@@ -52,12 +53,21 @@ export function isUsageLimitError(message) {
52
53
  'billing hard limit',
53
54
  'please try again at', // Codex/OpenCode style
54
55
  'available again at',
55
- 'resets', // Claude shows: "∙ resets 5am"
56
+ // Claude shows: "∙ resets 5am" or "resets Jan 15, 8am"
57
+ // Issue #1290: Use regex to avoid false positives when "resets" appears in code output
58
+ // (e.g., "loads a shell and resets", "Also resets drag start")
59
+ // Only match "resets" when followed by a time-like pattern (digit or month name)
60
+ /resets\s+(?:(?:at\s+)?[0-9]|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))/i,
56
61
  // Agent/OpenCode Zen specific - issue #1287
57
62
  'freeusagelimiterror', // Agent JSON error type
58
63
  ];
59
64
 
60
- return patterns.some(pattern => lowerMessage.includes(pattern));
65
+ return patterns.some(pattern => {
66
+ if (pattern instanceof RegExp) {
67
+ return pattern.test(lowerMessage);
68
+ }
69
+ return lowerMessage.includes(pattern);
70
+ });
61
71
  }
62
72
 
63
73
  /**