@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 +24 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +16 -2
- package/src/solve.mjs +9 -4
- package/src/solve.watch.lib.mjs +65 -0
- package/src/usage-limit.lib.mjs +15 -3
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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'
|
|
1400
|
+
} else if (logsAlreadyUploaded && !autoRestartRanButNotUploaded) {
|
|
1401
|
+
await log('ā¹ļø Logs already uploaded by verifyResults, skipping duplicate upload');
|
|
1397
1402
|
logsAttached = true;
|
|
1398
1403
|
}
|
|
1399
1404
|
}
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -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
|
|
package/src/usage-limit.lib.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 =>
|
|
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
|
/**
|