@link-assistant/hive-mind 1.56.6 → 1.56.8
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 +14 -0
- package/package.json +2 -2
- package/src/agent.lib.mjs +31 -4
- package/src/auto-iteration-limits.lib.mjs +33 -0
- package/src/claude.lib.mjs +9 -4
- package/src/codex.lib.mjs +47 -5
- package/src/hive.config.lib.mjs +1 -1
- package/src/hive.mjs +3 -0
- package/src/isolation-runner.lib.mjs +86 -27
- package/src/models/index.mjs +17 -0
- package/src/opencode.lib.mjs +28 -6
- package/src/option-suggestions.lib.mjs +1 -0
- package/src/session-monitor.lib.mjs +161 -77
- package/src/solve.auto-continue.lib.mjs +14 -0
- package/src/solve.auto-merge.lib.mjs +91 -24
- package/src/solve.config.lib.mjs +25 -3
- package/src/solve.error-handlers.lib.mjs +1 -1
- package/src/solve.execution.lib.mjs +1 -1
- package/src/solve.mjs +12 -15
- package/src/solve.pre-pr-failure-notifier.lib.mjs +1 -1
- package/src/solve.results.lib.mjs +14 -8
- package/src/solve.watch.lib.mjs +14 -9
- package/src/telegram-bot.mjs +9 -9
- package/src/telegram-isolation.lib.mjs +2 -2
- package/src/telegram-solve-queue.lib.mjs +80 -34
- package/src/tool-retry.lib.mjs +118 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { retryLimits } from './config.lib.mjs';
|
|
4
|
+
import { resolveDefaultFallbackModel, resolveModelId } from './models/index.mjs';
|
|
5
|
+
|
|
6
|
+
const normalizeMessage = value => {
|
|
7
|
+
if (value === null || value === undefined) return '';
|
|
8
|
+
if (typeof value === 'string') return value;
|
|
9
|
+
if (typeof value?.error?.message === 'string') return value.error.message;
|
|
10
|
+
if (typeof value?.message === 'string') return value.message;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
} catch {
|
|
14
|
+
return String(value);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const normalizeModelKey = value => {
|
|
19
|
+
if (!value) return '';
|
|
20
|
+
return String(value)
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/\[1m\]$/i, '')
|
|
23
|
+
.trim();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const classifyRetryableError = value => {
|
|
27
|
+
const message = normalizeMessage(value);
|
|
28
|
+
const lower = message.toLowerCase();
|
|
29
|
+
|
|
30
|
+
if (lower.includes('selected model is at capacity') || (lower.includes('at capacity') && lower.includes('try a different model'))) {
|
|
31
|
+
return { message, isRetryable: true, isCapacity: true, label: 'Model capacity error' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (lower.includes('overloaded') || lower.includes('overloaded_error')) {
|
|
35
|
+
return { message, isRetryable: true, isCapacity: true, label: 'API overload' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (lower.includes('request timed out')) {
|
|
39
|
+
return { message, isRetryable: true, isCapacity: false, label: 'Request timeout' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (lower.includes('api error: 503') || (lower.includes('503') && (lower.includes('upstream connect error') || lower.includes('remote connection failure')))) {
|
|
43
|
+
return { message, isRetryable: true, isCapacity: false, label: '503 network error' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (lower.includes('internal server error') || lower.includes('api error: 500')) {
|
|
47
|
+
return { message, isRetryable: true, isCapacity: false, label: 'Internal server error (500)' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { message, isRetryable: false, isCapacity: false, label: null };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const getRetryDelayMs = ({ retryCount, initialDelayMs = retryLimits.initialTransientErrorDelayMs, maxDelayMs = retryLimits.maxTransientErrorDelayMs } = {}) => {
|
|
54
|
+
return Math.min(initialDelayMs * Math.pow(retryLimits.retryBackoffMultiplier, retryCount), maxDelayMs);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const waitWithCountdown = async (delayMs, log) => {
|
|
58
|
+
if (delayMs <= 60000) {
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let remaining = delayMs;
|
|
64
|
+
const timer = setInterval(async () => {
|
|
65
|
+
remaining -= 60000;
|
|
66
|
+
if (remaining > 0) await log(`⏳ ${Math.round(remaining / 60000)} min remaining...`);
|
|
67
|
+
}, 60000);
|
|
68
|
+
|
|
69
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
70
|
+
clearInterval(timer);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const resolveConfiguredFallbackModel = ({ tool, currentModel, configuredFallbackModel = undefined } = {}) => {
|
|
74
|
+
if (configuredFallbackModel) return configuredFallbackModel;
|
|
75
|
+
return resolveDefaultFallbackModel(tool, currentModel);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const maybeSwitchToFallbackModel = async ({ tool, argv, log, errorMessage } = {}) => {
|
|
79
|
+
const fallbackModel = resolveConfiguredFallbackModel({
|
|
80
|
+
tool,
|
|
81
|
+
currentModel: argv?.model,
|
|
82
|
+
configuredFallbackModel: argv?.fallbackModel,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const classification = classifyRetryableError(errorMessage);
|
|
86
|
+
if (!fallbackModel || !classification.isCapacity || !argv?.model) {
|
|
87
|
+
return { switched: false, fallbackModel, reason: classification.label };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const currentResolvedModel = normalizeModelKey(resolveModelId(argv.model, tool));
|
|
91
|
+
const fallbackResolvedModel = normalizeModelKey(resolveModelId(fallbackModel, tool));
|
|
92
|
+
if (!fallbackResolvedModel || currentResolvedModel === fallbackResolvedModel) {
|
|
93
|
+
return { switched: false, fallbackModel, reason: classification.label };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const previousModel = argv.model;
|
|
97
|
+
argv.model = fallbackModel;
|
|
98
|
+
if (!argv.fallbackModel) argv.fallbackModel = fallbackModel;
|
|
99
|
+
|
|
100
|
+
if (typeof log === 'function') {
|
|
101
|
+
await log(`🔀 Switching to fallback model: ${previousModel} -> ${fallbackModel}`, { level: 'warning' });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
switched: true,
|
|
106
|
+
fallbackModel,
|
|
107
|
+
previousModel,
|
|
108
|
+
reason: classification.label,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default {
|
|
113
|
+
classifyRetryableError,
|
|
114
|
+
getRetryDelayMs,
|
|
115
|
+
waitWithCountdown,
|
|
116
|
+
resolveConfiguredFallbackModel,
|
|
117
|
+
maybeSwitchToFallbackModel,
|
|
118
|
+
};
|