@link-assistant/hive-mind 1.23.0 → 1.23.2

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,29 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.23.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 241ce36: Fix false error categorization and missing log upload for `--tool agent` auto-restart
8
+ - Fix `isUsageLimitError()` "resets" pattern causing false positives when scanning code output
9
+ - Changed from substring match to regex that requires time-like content after "resets"
10
+ - Prevents ordinary English words like "loads a shell and resets" from triggering usage limit detection
11
+ - Fix agent fallback pattern matching running after agent successfully recovered from errors
12
+ - Skip fallback when exitCode=0 and agentCompletedSuccessfully to prevent false error detection
13
+ - Upload failure logs when auto-restart iteration fails for `--tool agent` with `--attach-logs`
14
+ - Add comprehensive tests for false positive scenarios (Issue #1290)
15
+
16
+ ## 1.23.1
17
+
18
+ ### Patch Changes
19
+
20
+ - 5c635fc: Fix agent tool error handling: upload failure logs to PR even when sessionId is not available
21
+ - Remove overly strict sessionId requirement for failure log upload in solve.mjs
22
+ - Add FreeUsageLimitError pattern detection for Agent/OpenCode Zen rate limits
23
+ - Improve rate limit detection by checking multiple sources (lastMessage, errorMatch, fullOutput)
24
+ - Add comprehensive case study documentation for issue #1287
25
+ - Add tests for FreeUsageLimitError detection
26
+
3
27
  ## 1.23.0
4
28
 
5
29
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.23.0",
3
+ "version": "1.23.2",
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' },
@@ -848,7 +851,18 @@ export const executeAgentCommand = async params => {
848
851
  };
849
852
 
850
853
  // Check for usage limit errors first (more specific)
851
- const limitInfo = detectUsageLimit(lastMessage);
854
+ // Issue #1287: Check multiple sources for usage limit detection:
855
+ // 1. lastMessage (the last chunk of output)
856
+ // 2. errorMatch (the extracted error message from JSON output)
857
+ // 3. fullOutput (complete output - fallback)
858
+ let limitInfo = detectUsageLimit(lastMessage);
859
+ if (!limitInfo.isUsageLimit && outputError.match) {
860
+ limitInfo = detectUsageLimit(outputError.match);
861
+ }
862
+ if (!limitInfo.isUsageLimit) {
863
+ // Fallback: scan fullOutput for usage limit patterns
864
+ limitInfo = detectUsageLimit(fullOutput);
865
+ }
852
866
  if (limitInfo.isUsageLimit) {
853
867
  limitReached = true;
854
868
  limitResetTime = limitInfo.resetTime;
package/src/solve.mjs CHANGED
@@ -1112,7 +1112,9 @@ try {
1112
1112
  }
1113
1113
 
1114
1114
  // If --attach-logs is enabled and we have a PR, attach failure logs before exiting
1115
- if (shouldAttachLogs && sessionId && global.createdPR && global.createdPR.number) {
1115
+ // Note: sessionId is not required - logs should be uploaded even if agent failed before establishing a session
1116
+ // This aligns with the pattern in handleFailure() in solve.error-handlers.lib.mjs
1117
+ if (shouldAttachLogs && global.createdPR && global.createdPR.number) {
1116
1118
  await log('\nšŸ“„ Attaching failure logs to Pull Request...');
1117
1119
  try {
1118
1120
  // Build Claude CLI resume command
@@ -1365,7 +1367,10 @@ try {
1365
1367
 
1366
1368
  // Attach updated logs to PR after auto-restart completes
1367
1369
  // Issue #1154: Skip if logs were already uploaded by verifyResults() to prevent duplicates
1368
- 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)) {
1369
1374
  await log('šŸ“Ž Uploading working session logs to Pull Request...');
1370
1375
  try {
1371
1376
  const logUploadSuccess = await attachLogToGitHub({
@@ -1392,8 +1397,8 @@ try {
1392
1397
  } catch (uploadError) {
1393
1398
  await log(`āš ļø Error uploading logs: ${uploadError.message}`, { level: 'warning' });
1394
1399
  }
1395
- } else if (logsAlreadyUploaded) {
1396
- 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');
1397
1402
  logsAttached = true;
1398
1403
  }
1399
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
 
@@ -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",
@@ -44,7 +45,7 @@ export function isUsageLimitError(message) {
44
45
  'rate limit exceeded',
45
46
  'limit reached',
46
47
  'limit has been reached',
47
- // Provider-specific phrasings we’ve seen in the wild
48
+ // Provider-specific phrasings we've seen in the wild
48
49
  'session limit reached', // Claude
49
50
  'weekly limit reached', // Claude
50
51
  'daily limit reached',
@@ -52,10 +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,
61
+ // Agent/OpenCode Zen specific - issue #1287
62
+ 'freeusagelimiterror', // Agent JSON error type
56
63
  ];
57
64
 
58
- 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
+ });
59
71
  }
60
72
 
61
73
  /**