@link-assistant/hive-mind 1.69.10 → 1.69.12
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 +12 -0
- package/package.json +1 -1
- package/src/github.lib.mjs +9 -2
- package/src/limits-i18n.lib.mjs +145 -0
- package/src/limits.lib.mjs +46 -74
- package/src/locales/en.lino +52 -0
- package/src/locales/hi.lino +52 -0
- package/src/locales/ru.lino +53 -1
- package/src/locales/zh.lino +52 -0
- package/src/solve.mjs +11 -0
- package/src/solve.pre-pr-failure-notifier.lib.mjs +153 -37
- package/src/telegram-bot.mjs +9 -8
- package/src/telegram-safe-reply.lib.mjs +117 -14
- package/src/telegram-solve-queue-command.lib.mjs +18 -6
- package/src/telegram-solve-queue.helpers.lib.mjs +58 -6
- package/src/telegram-solve-queue.lib.mjs +65 -41
- package/src/version-info.lib.mjs +52 -15
package/src/locales/ru.lino
CHANGED
|
@@ -80,6 +80,10 @@ ru
|
|
|
80
80
|
limits.current_week_sonnet_only "Текущая неделя (только Sonnet)"
|
|
81
81
|
limits.disabled_by_admin "`--show-limits` отключён администратором бота."
|
|
82
82
|
limits.disk_space "Дисковое пространство"
|
|
83
|
+
limits.duration_day_short "д"
|
|
84
|
+
limits.duration_hour_short "ч"
|
|
85
|
+
limits.duration_minute_short "мин"
|
|
86
|
+
limits.duration_second_short "с"
|
|
83
87
|
limits.end "Конец"
|
|
84
88
|
limits.five_hour_session "5-часовой сеанс"
|
|
85
89
|
limits.five_min_load_avg "средняя нагрузка за 5 мин"
|
|
@@ -92,9 +96,41 @@ ru
|
|
|
92
96
|
limits.passed "прошло"
|
|
93
97
|
limits.plan "План"
|
|
94
98
|
limits.ram "RAM"
|
|
99
|
+
limits.reason_claude_5_hour_session "Лимит 5-часового сеанса Claude {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
100
|
+
limits.reason_claude_running "Процесс Claude уже выполняется"
|
|
101
|
+
limits.reason_claude_weekly "Недельный лимит Claude {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
102
|
+
limits.reason_codex_5_hour_session "Лимит 5-часового сеанса Codex {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
103
|
+
limits.reason_codex_running "Процесс Codex уже выполняется"
|
|
104
|
+
limits.reason_codex_weekly "Недельный лимит Codex {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
105
|
+
limits.reason_cpu_usage "Использование CPU {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
106
|
+
limits.reason_disk_usage "Использование диска {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
107
|
+
limits.reason_gemini_running "Процесс Gemini CLI уже выполняется"
|
|
108
|
+
limits.reason_github_api "Использование GitHub API {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
109
|
+
limits.reason_min_interval "Минимальный интервал между командами ещё не прошёл"
|
|
110
|
+
limits.reason_qwen_running "Процесс Qwen Code уже выполняется"
|
|
111
|
+
limits.reason_ram_usage "Использование RAM {{currentPercent}}% (порог: {{thresholdPercent}})"
|
|
112
|
+
limits.reason_threshold_exceeded "Превышен порог {{metric}}"
|
|
113
|
+
limits.remaining "осталось {{duration}}"
|
|
95
114
|
limits.requests "запросов"
|
|
96
115
|
limits.resets_at "Сброс {{time}}"
|
|
97
116
|
limits.resets_in "Сброс через {{duration}}"
|
|
117
|
+
limits.resource_limit_exceeded "Превышен лимит ресурсов"
|
|
118
|
+
limits.solve_queue_status "Статус очереди solve"
|
|
119
|
+
limits.queue_completed "Завершено"
|
|
120
|
+
limits.queue_failed "С ошибкой"
|
|
121
|
+
limits.queue_and_more "и ещё {{count}}"
|
|
122
|
+
limits.queue_pending "ожидает"
|
|
123
|
+
limits.queue_processing "выполняется"
|
|
124
|
+
limits.queue_processes "процессов: {{count}}"
|
|
125
|
+
limits.queue_status_cancelled "отменено"
|
|
126
|
+
limits.queue_status_failed "ошибка"
|
|
127
|
+
limits.queue_status_queued "в очереди"
|
|
128
|
+
limits.queue_status_started "запущено"
|
|
129
|
+
limits.queue_status_starting "запускается"
|
|
130
|
+
limits.queue_status_waiting "ожидание"
|
|
131
|
+
limits.queue_waiting_current_command "ожидание текущей команды"
|
|
132
|
+
limits.queue_waiting_in_queue "Ожидание в очереди"
|
|
133
|
+
limits.queues "Очереди"
|
|
98
134
|
limits.seven_day_all_models "7 дней, все модели"
|
|
99
135
|
limits.seven_day_sonnet_only "7 дней, только Sonnet"
|
|
100
136
|
limits.session "сеанс"
|
|
@@ -107,10 +143,12 @@ ru
|
|
|
107
143
|
telegram.fetching_limits "🔄 Получение лимитов использования..."
|
|
108
144
|
telegram.gathering_version "🔄 Сбор информации о версии..."
|
|
109
145
|
telegram.usage_limits_title "📊 *Лимиты использования*"
|
|
110
|
-
telegram.version_information_title "🤖 *Информация о
|
|
146
|
+
telegram.version_information_title "🤖 *Информация о версиях*"
|
|
147
|
+
telegram.formatting_fallback_warning "⚠️ Обнаружена ошибка форматирования. Показываю обычный текст."
|
|
111
148
|
telegram.limits_only_in_groups "❌ Команда /limits работает только в групповых чатах. Добавьте бота в группу и сделайте его администратором."
|
|
112
149
|
telegram.version_only_in_groups "❌ Команда /version работает только в групповых чатах. Добавьте бота в группу и сделайте его администратором."
|
|
113
150
|
telegram.solve_only_in_groups "❌ Команда {{commandDisplay}} работает только в групповых чатах. Добавьте бота в группу и сделайте его администратором."
|
|
151
|
+
telegram.solve_queue_only_in_groups "❌ Команда /solve_queue работает только в групповых чатах. Добавьте бота в группу и сделайте его администратором."
|
|
114
152
|
telegram.hive_only_in_groups "❌ Команда /hive работает только в групповых чатах. Добавьте бота в группу и сделайте его администратором."
|
|
115
153
|
telegram.solve_disabled "❌ Команда solve отключена в этом экземпляре бота."
|
|
116
154
|
telegram.hive_disabled "❌ Команда /hive отключена в этом экземпляре бота."
|
|
@@ -215,6 +253,20 @@ ru
|
|
|
215
253
|
language.ru "Русский"
|
|
216
254
|
language.zh "Китайский"
|
|
217
255
|
language.hi "Хинди"
|
|
256
|
+
version.ai_agents "AI-агенты"
|
|
257
|
+
version.browsers "Браузеры"
|
|
258
|
+
version.browser_automation "Автоматизация браузера"
|
|
259
|
+
version.connected "подключено"
|
|
260
|
+
version.development_tools "Инструменты разработки"
|
|
261
|
+
version.environment "Среда"
|
|
262
|
+
version.architecture "Архитектура"
|
|
263
|
+
version.kernel "Ядро"
|
|
264
|
+
version.not_connected "не подключено"
|
|
265
|
+
version.os "ОС"
|
|
266
|
+
version.platform "Платформа"
|
|
267
|
+
version.process_running_restart_needed "Запущенный процесс: `{{processVersion}}` (требуется перезапуск)"
|
|
268
|
+
version.system "Система"
|
|
269
|
+
version.version "Версия"
|
|
218
270
|
prompt.user.issue_to_solve "Задача для решения: {{issueUrl}}"
|
|
219
271
|
prompt.user.issue_linked_to_pr "Задача для решения: задача, связанная с PR #{{prNumber}}"
|
|
220
272
|
prompt.user.prepared_branch "Подготовленная ветка: {{branchName}}"
|
package/src/locales/zh.lino
CHANGED
|
@@ -80,6 +80,10 @@ zh
|
|
|
80
80
|
limits.current_week_sonnet_only "本周(仅 Sonnet)"
|
|
81
81
|
limits.disabled_by_admin "`--show-limits` 已被机器人管理员禁用。"
|
|
82
82
|
limits.disk_space "磁盘空间"
|
|
83
|
+
limits.duration_day_short "天"
|
|
84
|
+
limits.duration_hour_short "小时"
|
|
85
|
+
limits.duration_minute_short "分钟"
|
|
86
|
+
limits.duration_second_short "秒"
|
|
83
87
|
limits.end "结束"
|
|
84
88
|
limits.five_hour_session "5 小时会话"
|
|
85
89
|
limits.five_min_load_avg "5 分钟平均负载"
|
|
@@ -92,9 +96,41 @@ zh
|
|
|
92
96
|
limits.passed "已过"
|
|
93
97
|
limits.plan "方案"
|
|
94
98
|
limits.ram "内存"
|
|
99
|
+
limits.reason_claude_5_hour_session "Claude 5 小时会话限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
100
|
+
limits.reason_claude_running "Claude 进程已在运行"
|
|
101
|
+
limits.reason_claude_weekly "Claude 每周限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
102
|
+
limits.reason_codex_5_hour_session "Codex 5 小时会话限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
103
|
+
limits.reason_codex_running "Codex 进程已在运行"
|
|
104
|
+
limits.reason_codex_weekly "Codex 每周限额为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
105
|
+
limits.reason_cpu_usage "CPU 使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
106
|
+
limits.reason_disk_usage "磁盘使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
107
|
+
limits.reason_gemini_running "Gemini CLI 进程已在运行"
|
|
108
|
+
limits.reason_github_api "GitHub API 使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
109
|
+
limits.reason_min_interval "尚未达到命令之间的最小间隔"
|
|
110
|
+
limits.reason_qwen_running "Qwen Code 进程已在运行"
|
|
111
|
+
limits.reason_ram_usage "内存使用率为 {{currentPercent}}%(阈值:{{thresholdPercent}})"
|
|
112
|
+
limits.reason_threshold_exceeded "{{metric}} 超过阈值"
|
|
113
|
+
limits.remaining "剩余 {{duration}}"
|
|
95
114
|
limits.requests "请求"
|
|
96
115
|
limits.resets_at "{{time}} 重置"
|
|
97
116
|
limits.resets_in "{{duration}} 后重置"
|
|
117
|
+
limits.resource_limit_exceeded "资源限额已超出"
|
|
118
|
+
limits.solve_queue_status "Solve 队列状态"
|
|
119
|
+
limits.queue_completed "已完成"
|
|
120
|
+
limits.queue_failed "失败"
|
|
121
|
+
limits.queue_and_more "还有 {{count}} 个"
|
|
122
|
+
limits.queue_pending "等待"
|
|
123
|
+
limits.queue_processing "处理中"
|
|
124
|
+
limits.queue_processes "{{count}} 个进程"
|
|
125
|
+
limits.queue_status_cancelled "已取消"
|
|
126
|
+
limits.queue_status_failed "失败"
|
|
127
|
+
limits.queue_status_queued "已排队"
|
|
128
|
+
limits.queue_status_started "已启动"
|
|
129
|
+
limits.queue_status_starting "正在启动"
|
|
130
|
+
limits.queue_status_waiting "等待中"
|
|
131
|
+
limits.queue_waiting_current_command "等待当前命令"
|
|
132
|
+
limits.queue_waiting_in_queue "在队列中等待"
|
|
133
|
+
limits.queues "队列"
|
|
98
134
|
limits.seven_day_all_models "7 天所有模型"
|
|
99
135
|
limits.seven_day_sonnet_only "7 天仅 Sonnet"
|
|
100
136
|
limits.session "会话"
|
|
@@ -108,9 +144,11 @@ zh
|
|
|
108
144
|
telegram.gathering_version "🔄 正在收集版本信息……"
|
|
109
145
|
telegram.usage_limits_title "📊 *使用限额*"
|
|
110
146
|
telegram.version_information_title "🤖 *版本信息*"
|
|
147
|
+
telegram.formatting_fallback_warning "⚠️ 检测到格式错误。正在显示纯文本备用内容。"
|
|
111
148
|
telegram.limits_only_in_groups "❌ /limits 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
112
149
|
telegram.version_only_in_groups "❌ /version 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
113
150
|
telegram.solve_only_in_groups "❌ {{commandDisplay}} 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
151
|
+
telegram.solve_queue_only_in_groups "❌ /solve_queue 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
114
152
|
telegram.hive_only_in_groups "❌ /hive 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
|
|
115
153
|
telegram.solve_disabled "❌ 此机器人实例已禁用 solve 命令。"
|
|
116
154
|
telegram.hive_disabled "❌ 此机器人实例已禁用 /hive 命令。"
|
|
@@ -215,6 +253,20 @@ zh
|
|
|
215
253
|
language.ru "俄语"
|
|
216
254
|
language.zh "中文"
|
|
217
255
|
language.hi "印地语"
|
|
256
|
+
version.ai_agents "AI 代理"
|
|
257
|
+
version.browsers "浏览器"
|
|
258
|
+
version.browser_automation "浏览器自动化"
|
|
259
|
+
version.connected "已连接"
|
|
260
|
+
version.development_tools "开发工具"
|
|
261
|
+
version.environment "环境"
|
|
262
|
+
version.architecture "架构"
|
|
263
|
+
version.kernel "内核"
|
|
264
|
+
version.not_connected "未连接"
|
|
265
|
+
version.os "操作系统"
|
|
266
|
+
version.platform "平台"
|
|
267
|
+
version.process_running_restart_needed "正在运行的进程:`{{processVersion}}`(需要重启)"
|
|
268
|
+
version.system "系统"
|
|
269
|
+
version.version "版本"
|
|
218
270
|
prompt.user.issue_to_solve "要解决的问题:{{issueUrl}}"
|
|
219
271
|
prompt.user.issue_linked_to_pr "要解决的问题:与 PR #{{prNumber}} 关联的问题"
|
|
220
272
|
prompt.user.prepared_branch "为你准备的分支:{{branchName}}"
|
package/src/solve.mjs
CHANGED
|
@@ -148,6 +148,14 @@ const cleanupWrapper = async () => {
|
|
|
148
148
|
const interruptWrapper = createInterruptWrapper({ cleanupContext, checkForUncommittedChanges, shouldAttachLogs, attachLogToGitHub, getLogFile, sanitizeLogContent, $, log });
|
|
149
149
|
initializeExitHandler(getAbsoluteLogPath, log, cleanupWrapper, interruptWrapper, ({ code, reason }) => notifyIssueAboutPrePullRequestFailure({ code, reason, argv, globalState: global, $, log, getLogFile, shouldAttachLogs, attachLogToGitHub, sanitizeLogContent, rawCommand }));
|
|
150
150
|
installGlobalExitHandlers();
|
|
151
|
+
const markFailureNotificationPosted = targetType => {
|
|
152
|
+
global.preExitFailureNotificationPosted = true;
|
|
153
|
+
if (targetType === 'pr') {
|
|
154
|
+
global.pullRequestFailureNotificationPosted = true;
|
|
155
|
+
} else if (targetType === 'issue') {
|
|
156
|
+
global.prePullRequestFailureNotificationPosted = true;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
151
159
|
|
|
152
160
|
// Now handle argument validation that was moved from early checks
|
|
153
161
|
let issueUrl = argv['issue-url'] || argv._[0];
|
|
@@ -900,6 +908,7 @@ try {
|
|
|
900
908
|
});
|
|
901
909
|
|
|
902
910
|
if (logUploadSuccess) {
|
|
911
|
+
markFailureNotificationPosted('pr');
|
|
903
912
|
await log(' ✅ Logs uploaded successfully');
|
|
904
913
|
} else {
|
|
905
914
|
// Issue #1212: Always show log upload failures (not just verbose)
|
|
@@ -924,6 +933,7 @@ try {
|
|
|
924
933
|
|
|
925
934
|
const posted = await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: failureComment });
|
|
926
935
|
if (posted.ok) {
|
|
936
|
+
markFailureNotificationPosted('pr');
|
|
927
937
|
await log(` Posted failure comment to PR${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
|
|
928
938
|
}
|
|
929
939
|
} catch (error) {
|
|
@@ -1078,6 +1088,7 @@ try {
|
|
|
1078
1088
|
});
|
|
1079
1089
|
|
|
1080
1090
|
if (logUploadSuccess) {
|
|
1091
|
+
markFailureNotificationPosted(logTargetType);
|
|
1081
1092
|
await log(` 📎 Failure logs posted to ${logTargetLabel}`);
|
|
1082
1093
|
} else {
|
|
1083
1094
|
// Issue #1212: Always show log upload failures (not just verbose)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getTrackedToolCommentIds, postTrackedComment, SOLUTION_DRAFT_FAILED_MARKER } from './tool-comments.lib.mjs';
|
|
2
2
|
|
|
3
|
+
export const FORK_DIVERGENCE_RESOLUTION_OPTION = '--allow-fork-divergence-resolution-using-force-push-with-lease';
|
|
4
|
+
|
|
3
5
|
const truncate = (value, maxLength = 2000) => {
|
|
4
6
|
const text = value === null || value === undefined ? '' : String(value);
|
|
5
7
|
if (text.length <= maxLength) return text;
|
|
@@ -8,10 +10,24 @@ const truncate = (value, maxLength = 2000) => {
|
|
|
8
10
|
|
|
9
11
|
const fence = value => truncate(value || 'Unknown error').replaceAll('```', '` ` `');
|
|
10
12
|
|
|
13
|
+
const isForkDivergenceFailure = reason => {
|
|
14
|
+
const normalizedReason = String(reason || '').toLowerCase();
|
|
15
|
+
return normalizedReason.includes('fork divergence') || (normalizedReason.includes('fork') && normalizedReason.includes('non-fast-forward')) || normalizedReason.includes('force-with-lease');
|
|
16
|
+
};
|
|
17
|
+
|
|
11
18
|
export function buildPrePullRequestFailureActionSection(reason = '') {
|
|
12
19
|
const normalizedReason = String(reason || '').toLowerCase();
|
|
13
20
|
const isForkOrRecoveryFailure = normalizedReason.includes('fork') || normalizedReason.includes('auto-recovery') || normalizedReason.includes('repository setup');
|
|
14
21
|
|
|
22
|
+
if (isForkDivergenceFailure(reason)) {
|
|
23
|
+
return `### What you can do
|
|
24
|
+
- If the fork's default branch can be overwritten safely, rerun with \`${FORK_DIVERGENCE_RESOLUTION_OPTION}\` to allow a guarded force-with-lease sync.
|
|
25
|
+
- If the fork has commits you need to preserve, resolve the divergence manually, then rerun the solver.
|
|
26
|
+
- If this requires elevated Hive Mind access, ask a Hive Mind administrator to handle the affected fork or repository.
|
|
27
|
+
|
|
28
|
+
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this GitHub comment.`;
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
if (isForkOrRecoveryFailure) {
|
|
16
32
|
return `### What you can do
|
|
17
33
|
- If the affected fork or repository belongs to you, remove, rename, archive, initialize, or otherwise repair it in GitHub, then rerun the solver.
|
|
@@ -36,6 +52,42 @@ export function shouldNotifyIssueAboutPrePullRequestFailure({ code, globalState
|
|
|
36
52
|
return getTrackedToolCommentIds().size === 0;
|
|
37
53
|
}
|
|
38
54
|
|
|
55
|
+
export function resolvePreExitFailureNotificationTarget({ code, globalState }) {
|
|
56
|
+
if (code === 0) return null;
|
|
57
|
+
if (!globalState?.owner || !globalState?.repo) return null;
|
|
58
|
+
if (globalState.preExitFailureNotificationPosted || globalState.preExitFailureNotificationInProgress) return null;
|
|
59
|
+
|
|
60
|
+
const owner = globalState.owner;
|
|
61
|
+
const repo = globalState.repo;
|
|
62
|
+
const issueNumber = globalState.issueNumber || null;
|
|
63
|
+
const prNumber = globalState.createdPR?.number || globalState.prNumber || null;
|
|
64
|
+
|
|
65
|
+
if (prNumber) {
|
|
66
|
+
if (globalState.pullRequestFailureNotificationPosted || globalState.pullRequestFailureNotificationInProgress) return null;
|
|
67
|
+
return {
|
|
68
|
+
targetType: 'pr',
|
|
69
|
+
targetNumber: prNumber,
|
|
70
|
+
owner,
|
|
71
|
+
repo,
|
|
72
|
+
issueNumber,
|
|
73
|
+
prNumber,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!issueNumber) return null;
|
|
78
|
+
if (globalState.prePullRequestFailureNotificationPosted || globalState.prePullRequestFailureNotificationInProgress) return null;
|
|
79
|
+
if (getTrackedToolCommentIds().size !== 0) return null;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
targetType: 'issue',
|
|
83
|
+
targetNumber: issueNumber,
|
|
84
|
+
owner,
|
|
85
|
+
repo,
|
|
86
|
+
issueNumber,
|
|
87
|
+
prNumber: null,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
39
91
|
export function buildPrePullRequestFailureComment({ reason, owner, repo, issueNumber, argv = {}, logAttachmentAttempted = false }) {
|
|
40
92
|
const tool = argv.tool || 'claude';
|
|
41
93
|
const modelLine = argv.model ? `\n- **Requested model**: \`${argv.model}\`` : '';
|
|
@@ -62,60 +114,124 @@ ${logLine}
|
|
|
62
114
|
`;
|
|
63
115
|
}
|
|
64
116
|
|
|
117
|
+
export function buildExistingPullRequestFailureComment({ reason, owner, repo, prNumber, issueNumber = null, argv = {}, logAttachmentAttempted = false }) {
|
|
118
|
+
const tool = argv.tool || 'claude';
|
|
119
|
+
const modelLine = argv.model ? `\n- **Requested model**: \`${argv.model}\`` : '';
|
|
120
|
+
const issueLine = issueNumber ? `\n- **Linked issue**: #${issueNumber}` : '';
|
|
121
|
+
const logLine = logAttachmentAttempted ? 'Log attachment was attempted but failed. Check the solver terminal log for the complete failure output.' : 'Logs were not attached because `--attach-logs` was not enabled.';
|
|
122
|
+
const actionSection = buildPrePullRequestFailureActionSection(reason);
|
|
123
|
+
|
|
124
|
+
return `## 🚨 ${SOLUTION_DRAFT_FAILED_MARKER}
|
|
125
|
+
|
|
126
|
+
The automated solver stopped while continuing this existing pull request, so the failure details are posted here for review.
|
|
127
|
+
|
|
128
|
+
### Failure
|
|
129
|
+
- **Repository**: \`${owner}/${repo}\`
|
|
130
|
+
- **Pull request**: #${prNumber}${issueLine}
|
|
131
|
+
- **Tool**: \`${tool}\`${modelLine}
|
|
132
|
+
|
|
133
|
+
**Reason**
|
|
134
|
+
\`\`\`text
|
|
135
|
+
${fence(reason)}
|
|
136
|
+
\`\`\`
|
|
137
|
+
|
|
138
|
+
${actionSection}
|
|
139
|
+
|
|
140
|
+
${logLine}
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const markNotificationPosted = ({ globalState, targetType }) => {
|
|
145
|
+
globalState.preExitFailureNotificationPosted = true;
|
|
146
|
+
if (targetType === 'pr') {
|
|
147
|
+
globalState.pullRequestFailureNotificationPosted = true;
|
|
148
|
+
} else {
|
|
149
|
+
globalState.prePullRequestFailureNotificationPosted = true;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
65
153
|
export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
66
154
|
const { code, reason, argv = {}, globalState = globalThis, $, log = async () => {}, getLogFile, shouldAttachLogs = false, attachLogToGitHub, sanitizeLogContent, rawCommand = null, postComment = postTrackedComment } = options;
|
|
67
155
|
|
|
68
|
-
|
|
156
|
+
const target = resolvePreExitFailureNotificationTarget({ code, globalState });
|
|
157
|
+
if (!target) {
|
|
69
158
|
return { notified: false, skipped: true };
|
|
70
159
|
}
|
|
71
160
|
|
|
72
|
-
const owner =
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
161
|
+
const { owner, repo, issueNumber, prNumber, targetType, targetNumber } = target;
|
|
162
|
+
const targetLabel = targetType === 'pr' ? `pull request #${targetNumber}` : `issue #${targetNumber}`;
|
|
163
|
+
globalState.preExitFailureNotificationInProgress = true;
|
|
164
|
+
if (targetType === 'pr') {
|
|
165
|
+
globalState.pullRequestFailureNotificationInProgress = true;
|
|
166
|
+
} else {
|
|
167
|
+
globalState.prePullRequestFailureNotificationInProgress = true;
|
|
168
|
+
}
|
|
76
169
|
|
|
77
170
|
try {
|
|
78
171
|
if (shouldAttachLogs && getLogFile && attachLogToGitHub && sanitizeLogContent) {
|
|
79
|
-
await log(`\n📄 Notifying
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
172
|
+
await log(`\n📄 Notifying ${targetLabel} about solver failure with logs...`);
|
|
173
|
+
const errorPrefix = targetType === 'pr' ? `The solver stopped while continuing pull request #${targetNumber}.` : 'The solver stopped before creating a pull request.';
|
|
174
|
+
try {
|
|
175
|
+
const uploaded = await attachLogToGitHub({
|
|
176
|
+
logFile: getLogFile(),
|
|
177
|
+
targetType,
|
|
178
|
+
targetNumber,
|
|
179
|
+
owner,
|
|
180
|
+
repo,
|
|
181
|
+
$,
|
|
182
|
+
log,
|
|
183
|
+
sanitizeLogContent,
|
|
184
|
+
verbose: argv.verbose,
|
|
185
|
+
errorMessage: `${errorPrefix}\n\nReason: ${reason || 'Unknown error'}`,
|
|
186
|
+
failureActionSection: buildPrePullRequestFailureActionSection(reason),
|
|
187
|
+
requestedModel: argv.originalModel || argv.model,
|
|
188
|
+
tool: argv.tool || 'claude',
|
|
189
|
+
});
|
|
190
|
+
if (uploaded) {
|
|
191
|
+
markNotificationPosted({ globalState, targetType });
|
|
192
|
+
return { notified: true, method: 'log-upload' };
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const message = error && error.message ? error.message : String(error);
|
|
196
|
+
await log(` ⚠️ Could not upload solver failure log: ${message}`, { level: 'warning' });
|
|
97
197
|
}
|
|
98
198
|
}
|
|
99
199
|
|
|
100
|
-
await log(`\n💬 Notifying
|
|
101
|
-
const body =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
200
|
+
await log(`\n💬 Notifying ${targetLabel} about solver failure...`);
|
|
201
|
+
const body =
|
|
202
|
+
targetType === 'pr'
|
|
203
|
+
? buildExistingPullRequestFailureComment({
|
|
204
|
+
reason,
|
|
205
|
+
owner,
|
|
206
|
+
repo,
|
|
207
|
+
prNumber,
|
|
208
|
+
issueNumber,
|
|
209
|
+
argv,
|
|
210
|
+
rawCommand,
|
|
211
|
+
logAttachmentAttempted: shouldAttachLogs,
|
|
212
|
+
})
|
|
213
|
+
: buildPrePullRequestFailureComment({
|
|
214
|
+
reason,
|
|
215
|
+
owner,
|
|
216
|
+
repo,
|
|
217
|
+
issueNumber,
|
|
218
|
+
argv,
|
|
219
|
+
rawCommand,
|
|
220
|
+
logAttachmentAttempted: shouldAttachLogs,
|
|
221
|
+
});
|
|
222
|
+
const posted = await postComment({ $, owner, repo, targetNumber, body });
|
|
111
223
|
if (posted.ok) {
|
|
112
|
-
globalState
|
|
113
|
-
await log(` ✅
|
|
224
|
+
markNotificationPosted({ globalState, targetType });
|
|
225
|
+
await log(` ✅ Solver failure comment posted to ${targetLabel}${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
|
|
114
226
|
return { notified: true, method: 'comment', commentId: posted.commentId || null };
|
|
115
227
|
}
|
|
116
|
-
await log(` ⚠️ Could not post
|
|
228
|
+
await log(` ⚠️ Could not post solver failure comment: ${posted.stderr || 'unknown error'}`, { level: 'warning' });
|
|
117
229
|
return { notified: false, error: posted.stderr || 'unknown error' };
|
|
118
230
|
} finally {
|
|
231
|
+
globalState.preExitFailureNotificationInProgress = false;
|
|
232
|
+
if (targetType === 'pr') {
|
|
233
|
+
globalState.pullRequestFailureNotificationInProgress = false;
|
|
234
|
+
}
|
|
119
235
|
globalState.prePullRequestFailureNotificationInProgress = false;
|
|
120
236
|
}
|
|
121
237
|
}
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -40,7 +40,7 @@ const { applySolveToolAlias, getFirstParsedPositionalArg, getSolveCommandNameFro
|
|
|
40
40
|
const { executeStartScreen: executeStartScreenCommand, buildExecuteAndUpdateMessage } = await import('./telegram-command-execution.lib.mjs');
|
|
41
41
|
const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STOP_REASON } = await import('./telegram-start-stop-command.lib.mjs');
|
|
42
42
|
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
|
|
43
|
-
const { safeReply } = await import('./telegram-safe-reply.lib.mjs');
|
|
43
|
+
const { installTelegramFormattingFallback, safeEditMessageText, safeReply } = await import('./telegram-safe-reply.lib.mjs');
|
|
44
44
|
const { registerTerminalWatchCommand, startAutoTerminalWatchForSession } = await import('./telegram-terminal-watch-command.lib.mjs');
|
|
45
45
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
46
46
|
const { trackSession, startSessionMonitoring, hasActiveSessionForUrlAsync } = await import('./session-monitor.lib.mjs');
|
|
@@ -315,6 +315,7 @@ const { Telegraf } = telegrafModule;
|
|
|
315
315
|
const bot = new Telegraf(BOT_TOKEN, {
|
|
316
316
|
handlerTimeout: Infinity, // Remove default 90s timeout; command handlers like /solve spawn long-running processes
|
|
317
317
|
});
|
|
318
|
+
installTelegramFormattingFallback(bot.telegram, { verbose: VERBOSE });
|
|
318
319
|
|
|
319
320
|
// Track bot startup time (Unix seconds to match Telegram's message.date format)
|
|
320
321
|
const BOT_START_TIME = Math.floor(Date.now() / 1000);
|
|
@@ -514,7 +515,7 @@ bot.command('help', async ctx => {
|
|
|
514
515
|
allowTopicHint: topicId ? `TELEGRAM_ALLOWED_TOPICS="(${chatId} ${topicId})"` : '',
|
|
515
516
|
});
|
|
516
517
|
|
|
517
|
-
await ctx
|
|
518
|
+
await safeReply(ctx, message, { fallbackLocale: helpLocale });
|
|
518
519
|
});
|
|
519
520
|
|
|
520
521
|
bot.command('limits', async ctx => {
|
|
@@ -564,10 +565,10 @@ bot.command('limits', async ctx => {
|
|
|
564
565
|
const claudeError = limits.claude.success ? null : limits.claude.error;
|
|
565
566
|
const codexError = limits.codex.success ? null : limits.codex.error;
|
|
566
567
|
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
567
|
-
const queueStatus = await solveQueue.formatStatus();
|
|
568
|
+
const queueStatus = await solveQueue.formatStatus({ locale: userLocale });
|
|
568
569
|
const codexSection = formatCodexLimitsSection(limits.codex.success ? limits.codex : null, codexError, { locale: userLocale });
|
|
569
570
|
const message = t('telegram.usage_limits_title', {}, { locale: userLocale }) + '\n\n' + formatUsageMessage(limits.claude.success ? limits.claude.usage : null, limits.disk.success ? limits.disk.diskSpace : null, limits.github.success ? limits.github.githubRateLimit : null, limits.cpu.success ? limits.cpu.cpuLoad : null, limits.memory.success ? limits.memory.memory : null, claudeError, [codexSection, queueStatus], { locale: userLocale });
|
|
570
|
-
await ctx.telegram
|
|
571
|
+
await safeEditMessageText(ctx.telegram, fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown', fallbackLocale: userLocale, verbose: VERBOSE });
|
|
571
572
|
});
|
|
572
573
|
bot.command('version', async ctx => {
|
|
573
574
|
VERBOSE && console.log('[VERBOSE] /version command received');
|
|
@@ -585,8 +586,8 @@ bot.command('version', async ctx => {
|
|
|
585
586
|
reply_to_message_id: ctx.message.message_id,
|
|
586
587
|
});
|
|
587
588
|
const result = await getVersionInfo(VERBOSE);
|
|
588
|
-
if (!result.success) return await ctx.telegram
|
|
589
|
-
await ctx.telegram
|
|
589
|
+
if (!result.success) return await safeEditMessageText(ctx.telegram, fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `❌ ${escapeMarkdownV2(result.error, { preserveCodeBlocks: true })}`, { parse_mode: 'MarkdownV2', fallbackLocale: versionLocale, verbose: VERBOSE });
|
|
590
|
+
await safeEditMessageText(ctx.telegram, fetchingMessage.chat.id, fetchingMessage.message_id, undefined, t('telegram.version_information_title', {}, { locale: versionLocale }) + '\n\n' + formatVersionMessage(result.versions, { locale: versionLocale }), { parse_mode: 'Markdown', fallbackLocale: versionLocale, verbose: VERBOSE });
|
|
590
591
|
});
|
|
591
592
|
|
|
592
593
|
const { registerLanguageCommand } = await import('./telegram-language-command.lib.mjs');
|
|
@@ -598,7 +599,7 @@ registerAcceptInvitesCommand(bot, sharedCommandOpts);
|
|
|
598
599
|
const { registerMergeCommand } = await import('./telegram-merge-command.lib.mjs');
|
|
599
600
|
registerMergeCommand(bot, sharedCommandOpts);
|
|
600
601
|
const { registerSolveQueueCommand } = await import('./telegram-solve-queue-command.lib.mjs');
|
|
601
|
-
const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, { ...sharedCommandOpts, getSolveQueue });
|
|
602
|
+
const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, { ...sharedCommandOpts, getSolveQueue, safeReply, resolveLocale: resolveLocaleFromTelegramCtx });
|
|
602
603
|
const { registerSubscribeCommands } = await import('./telegram-subscribers.lib.mjs'); // #1688
|
|
603
604
|
registerSubscribeCommands(bot, sharedCommandOpts);
|
|
604
605
|
const { registerTaskCommands } = await import('./telegram-task-command.lib.mjs');
|
|
@@ -848,7 +849,7 @@ async function handleSolveCommand(ctx) {
|
|
|
848
849
|
await safeReply(ctx, t('telegram.url_session_running', { url: escapeMarkdown(normalizedUrl), session: activeSession.sessionName }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
|
|
849
850
|
return;
|
|
850
851
|
}
|
|
851
|
-
const check = await solveQueue.canStartCommand({ tool: solveTool }); // Skip Claude limits for agent (#1159)
|
|
852
|
+
const check = await solveQueue.canStartCommand({ tool: solveTool, locale: solveLocale }); // Skip Claude limits for agent (#1159)
|
|
852
853
|
const queueStats = solveQueue.getStats();
|
|
853
854
|
// Handle rejection: threshold strategy is 'reject' — fail immediately (issue #1267)
|
|
854
855
|
if (check.rejected) {
|