@link-assistant/hive-mind 1.74.4 → 1.74.6
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/README.hi.md +19 -6
- package/README.md +19 -6
- package/README.ru.md +32 -19
- package/README.zh.md +19 -6
- package/package.json +1 -1
- package/src/external-review-limit.lib.mjs +66 -0
- package/src/github-merge.lib.mjs +6 -2
- package/src/solve.auto-merge-helpers.lib.mjs +19 -5
- package/src/solve.auto-merge.lib.mjs +41 -1
- package/src/telegram-bot.mjs +18 -20
- package/src/telegram-safe-reply.lib.mjs +274 -38
- package/src/tool-comments.lib.mjs +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.74.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e07b243: Split oversized Telegram text messages in the safe reply/edit helper so localized `/help` output cannot exceed Telegram's 4096-character limit.
|
|
8
|
+
|
|
9
|
+
## 1.74.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c20c2ec: Stop auto-restart-until-mergeable from restarting on CodeRabbit review quota/credit failures, and report them as Ready for review with skipped checks instead.
|
|
14
|
+
|
|
3
15
|
## 1.74.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.hi.md
CHANGED
|
@@ -29,14 +29,19 @@ Hive Mind एक **सामान्यवादी AI** (मिनी-AGI) ह
|
|
|
29
29
|
| **पूर्व-स्थापित टूलचेन** | 25GB+ तैयार: 10 भाषा रनटाइम, 2 थ्योरम प्रूवर, बिल्ड टूल्स। और इंस्टॉल कर सकते हैं। |
|
|
30
30
|
| **टोकन दक्षता** | नियमित कार्य कोड में स्वचालित, ताकि AI टोकन रचनात्मक समस्या-समाधान पर केंद्रित रहें। |
|
|
31
31
|
| **समय की स्वतंत्रता** | जो काम मनुष्यों को 2-8 घंटे लगता है, AI प्रत्येक कार्य सत्र में 10-25 मिनट में पूरा करता है। रिपॉजिटरी में कार्यों का बड़े पैमाने पर निष्पादन संभव है। "सोते समय कोड लिखा जाता है।" |
|
|
32
|
-
| **ऑर्केस्ट्रेशन के साथ स्केल** | समानांतर वर्कर डेवलपर्स की एक टीम की तरह महसूस होते
|
|
32
|
+
| **ऑर्केस्ट्रेशन के साथ स्केल** | समानांतर वर्कर डेवलपर्स की एक टीम की तरह महसूस होते हैं। Claude MAX और ChatGPT Pro (प्रत्येक $200) को जोड़कर दो स्वतंत्र लगभग असीमित बजट मिलते हैं। |
|
|
33
33
|
| **मानव नियंत्रण** | AI ड्राफ्ट PR बनाता है - आप तय करते हैं क्या मर्ज होगा। जहाँ मायने रखता है वहाँ गुणवत्ता द्वार। |
|
|
34
34
|
| **किसी भी डिवाइस से प्रोग्रामिंग** | Telegram बॉट के माध्यम से `/solve` और `/hive` से किसी भी डिवाइस से AI प्रबंधित करें। कोई PC, IDE या लैपटॉप की आवश्यकता नहीं। |
|
|
35
35
|
| **100% ओपन सोर्स** | Unlicense (पब्लिक डोमेन)। पूर्ण पारदर्शिता, कोई वेंडर लॉक-इन नहीं। |
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
**लागत**: Hive Mind दो $200/माह सदस्यताओं को पूर्ण-फीचर वाले लगभग "unlimited" विकल्पों के रूप में समर्थन देता है:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
| सदस्यता | `--tool` के साथ | डिफ़ॉल्ट मॉडल | किसके लिए बेहतर है |
|
|
40
|
+
| ---------------------------------------------------------------- | ------------------- | ------------- | ------------------------------------------------- |
|
|
41
|
+
| **Anthropic Claude MAX** (~$200/माह, अक्सर 50% छूट = $400 मूल्य) | `claude` (डिफ़ॉल्ट) | Sonnet/Haiku | उच्चतम रचनात्मकता, मजबूत सामान्य कोड रीजनिंग |
|
|
42
|
+
| **OpenAI ChatGPT Pro** ($200/माह, Codex शामिल) | `codex` | `gpt-5.5` | भरोसेमंद deterministic refactors और तेज iteration |
|
|
43
|
+
|
|
44
|
+
दोनों टूल एक ही hive में साथ उपयोग किए जा सकते हैं। Worker अलग-अलग टूल समानांतर चला सकते हैं, और `/codex` या `/solve --tool codex` कार्यों को ChatGPT Pro पर भेजता है जबकि डिफ़ॉल्ट Claude MAX पर जाता है। किसी एक को चुनना आवश्यक नहीं है: किसी भी एक सदस्यता से संचालन संभव है, और दोनों का उपयोग per-tool/model concurrency mode (#1474) खोलता है।
|
|
40
45
|
|
|
41
46
|
Hive Mind में औसत प्रोग्रामर से अलग न पहचानी जा सकने वाली उच्च रचनात्मकता है। यदि आवश्यकताएँ अस्पष्ट हों तो यह प्रश्न पूछता है, और आप PR टिप्पणियों के माध्यम से चलते-चलते स्पष्ट कर सकते हैं।
|
|
42
47
|
|
|
@@ -48,7 +53,7 @@ Hive Mind में औसत प्रोग्रामर से अलग
|
|
|
48
53
|
|
|
49
54
|
इंस्टॉलेशन के लिए Docker का उपयोग करना अनुशंसित है (स्थानीय और सर्वर दोनों पर)। नीचे [Docker इंस्टॉलेशन](#using-docker) अनुभाग देखें।
|
|
50
55
|
|
|
51
|
-
यह सॉफ़्टवेयर Claude Code
|
|
56
|
+
यह सॉफ़्टवेयर Claude Code और Codex जैसे समर्थित AI टूल को पूर्ण स्वायत्त मोड में चलाता है, जिसका अर्थ है कि वे जो भी उचित समझें वे कमांड निष्पादित करने के लिए स्वतंत्र हैं।
|
|
52
57
|
|
|
53
58
|
इसका मतलब है कि इससे अप्रत्याशित दुष्प्रभाव हो सकते हैं।
|
|
54
59
|
|
|
@@ -60,7 +65,7 @@ Hive Mind में औसत प्रोग्रामर से अलग
|
|
|
60
65
|
|
|
61
66
|
इंटरनेट से जुड़ी वर्चुअल मशीन से टोकन निकालने के असंख्य तरीके हैं। इसमें शामिल हैं लेकिन इन तक सीमित नहीं:
|
|
62
67
|
|
|
63
|
-
- **Claude MAX टोकन** - AI संचालन के लिए
|
|
68
|
+
- **Claude MAX टोकन** और/या **ChatGPT Pro (Codex) टोकन** - AI संचालन के लिए आवश्यक; आप इनमें से किसी एक या दोनों के साथ चला सकते हैं
|
|
64
69
|
- **GitHub टोकन** - रिपॉजिटरी एक्सेस के लिए आवश्यक
|
|
65
70
|
- **API keys और क्रेडेंशियल** - सिस्टम पर कोई भी संवेदनशील डेटा
|
|
66
71
|
|
|
@@ -161,7 +166,7 @@ docker exec -it hive-mind /bin/bash
|
|
|
161
166
|
# Inside the container, authenticate with GitHub
|
|
162
167
|
gh-setup-git-identity
|
|
163
168
|
|
|
164
|
-
# Authenticate with Claude
|
|
169
|
+
# Authenticate with Claude (if you have Claude MAX)
|
|
165
170
|
claude
|
|
166
171
|
|
|
167
172
|
# Optionally set configuration like this:
|
|
@@ -174,6 +179,14 @@ claude
|
|
|
174
179
|
# Optionally test Claude connection
|
|
175
180
|
claude -p hi --model haiku
|
|
176
181
|
|
|
182
|
+
# Authenticate with Codex (if you have ChatGPT Pro)
|
|
183
|
+
codex login --device-auth
|
|
184
|
+
|
|
185
|
+
# Optionally test Codex connection. codex exec refuses to run unless
|
|
186
|
+
# either cwd is a git repo it trusts or --skip-git-repo-check is passed.
|
|
187
|
+
# It prints the refusal to STDOUT but still exits 0, so do not skip the flag.
|
|
188
|
+
codex exec --skip-git-repo-check --model gpt-5.4-mini "reply with only OK"
|
|
189
|
+
|
|
177
190
|
# You might need to update hive-mind and agent to latest versions:
|
|
178
191
|
bun install -g @link-assistant/hive-mind
|
|
179
192
|
bun install -g @link-assistant/agent
|
package/README.md
CHANGED
|
@@ -29,14 +29,19 @@ Hive Mind is a **generalist AI** (mini-AGI) capable of working on a wide range o
|
|
|
29
29
|
| **Pre-installed Toolchain** | 25GB+ ready: 10 language runtimes, 2 theorem provers, build tools. Can install more. |
|
|
30
30
|
| **Token Efficiency** | Routine tasks automated in code, so AI tokens focus on creative problem-solving. |
|
|
31
31
|
| **Time Freedom** | What takes humans 2-8 hours, AI completes each working session in 10-25 minutes. Mass execution of tasks in repository is possible. "The code is written while you sleep." |
|
|
32
|
-
| **Scale with Orchestration** | Parallel workers feel like a team of developers
|
|
32
|
+
| **Scale with Orchestration** | Parallel workers feel like a team of developers. Pair Claude MAX and ChatGPT Pro ($200 each) for two independent unlimited budgets. |
|
|
33
33
|
| **Human Control** | AI creates draft PRs - you decide what merges. Quality gates where they matter. |
|
|
34
34
|
| **Any Device Programming** | Manage AI from any device with `/solve` and `/hive` via Telegram bot. No PC, IDE, or laptop required. |
|
|
35
35
|
| **100% Open Source** | Unlicense (public domain). Full transparency, no vendor lock-in. |
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
**Cost**: Hive Mind supports two $200/month subscriptions as full-featured "unlimited" options:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
| Subscription | Pairs with `--tool` | Default model | Best for |
|
|
40
|
+
| ------------------------------------------------------------------ | ------------------- | ------------- | ------------------------------------------------------- |
|
|
41
|
+
| **Anthropic Claude MAX** (~$200/month, often 50% off = $400 value) | `claude` (default) | Sonnet/Haiku | Highest creativity, strongest general code reasoning |
|
|
42
|
+
| **OpenAI ChatGPT Pro** ($200/month, includes Codex) | `codex` | `gpt-5.5` | Strong deterministic refactors and fast iteration loops |
|
|
43
|
+
|
|
44
|
+
Both tools can be combined in the same hive. Workers can run different tools in parallel, and `/codex` or `/solve --tool codex` routes tasks to ChatGPT Pro while the default routes to Claude MAX. There is no requirement to pick one: either single subscription is enough to operate, and using both unlocks per-tool/model concurrency mode (#1474).
|
|
40
45
|
|
|
41
46
|
Hive Mind has high creativity indistinguishable from average programmers. It asks questions if requirements are unclear, and you can clarify on the go via PR comments.
|
|
42
47
|
|
|
@@ -48,7 +53,7 @@ It is UNSAFE to run this software on your developer machine.
|
|
|
48
53
|
|
|
49
54
|
It is recommended to use Docker for installation (both locally and on servers). See the [Docker installation](#using-docker) section below.
|
|
50
55
|
|
|
51
|
-
This software
|
|
56
|
+
This software runs supported AI tools such as Claude Code and Codex in full autonomous mode, which means they are free to execute any commands they see fit.
|
|
52
57
|
|
|
53
58
|
That means it can lead to unexpected side effects.
|
|
54
59
|
|
|
@@ -60,7 +65,7 @@ There is also a known issue of space leakage. So you need to make sure you are a
|
|
|
60
65
|
|
|
61
66
|
There are infinite ways to extract tokens from a virtual machine connected to the internet. This includes but is not limited to:
|
|
62
67
|
|
|
63
|
-
- **Claude MAX tokens** - Required for AI operations
|
|
68
|
+
- **Claude MAX tokens** and/or **ChatGPT Pro (Codex) tokens** - Required for AI operations; you can run with either or both
|
|
64
69
|
- **GitHub tokens** - Required for repository access
|
|
65
70
|
- **API keys and credentials** - Any sensitive data on the system
|
|
66
71
|
|
|
@@ -161,7 +166,7 @@ docker exec -it hive-mind /bin/bash
|
|
|
161
166
|
# Inside the container, authenticate with GitHub
|
|
162
167
|
gh-setup-git-identity
|
|
163
168
|
|
|
164
|
-
# Authenticate with Claude
|
|
169
|
+
# Authenticate with Claude (if you have Claude MAX)
|
|
165
170
|
claude
|
|
166
171
|
|
|
167
172
|
# Optionally set configuration like this:
|
|
@@ -174,6 +179,14 @@ claude
|
|
|
174
179
|
# Optionally test Claude connection
|
|
175
180
|
claude -p hi --model haiku
|
|
176
181
|
|
|
182
|
+
# Authenticate with Codex (if you have ChatGPT Pro)
|
|
183
|
+
codex login --device-auth
|
|
184
|
+
|
|
185
|
+
# Optionally test Codex connection. codex exec refuses to run unless
|
|
186
|
+
# either cwd is a git repo it trusts or --skip-git-repo-check is passed.
|
|
187
|
+
# It prints the refusal to STDOUT but still exits 0, so do not skip the flag.
|
|
188
|
+
codex exec --skip-git-repo-check --model gpt-5.4-mini "reply with only OK"
|
|
189
|
+
|
|
177
190
|
# Verify Playwright MCP is registered for both CLIs in this container image
|
|
178
191
|
claude mcp list | grep playwright
|
|
179
192
|
codex mcp list | grep playwright
|
package/README.ru.md
CHANGED
|
@@ -21,22 +21,27 @@
|
|
|
21
21
|
|
|
22
22
|
Hive Mind — это **универсальный ИИ** (мини-AGI), способный работать над широким спектром задач, а не только над программированием. Практически всё, что можно сделать с файлами в репозитории, поддаётся автоматизации.
|
|
23
23
|
|
|
24
|
-
| Возможность | Что это означает для вас
|
|
25
|
-
| ---------------------------------------- |
|
|
26
|
-
| **Без постоянного контроля** | Полный автономный режим с правами sudo. ИИ обладает творческой свободой, как настоящий программист.
|
|
27
|
-
| **Изоляция в облаке** | Работает на выделенных виртуальных машинах или в Docker. Легко восстановить в случае поломки.
|
|
28
|
-
| **Полный доступ к интернету + Sudo** | ИИ может устанавливать пакеты, получать документацию и настраивать систему по мере необходимости.
|
|
29
|
-
| **Предустановленный инструментарий** | Более 25 ГБ готово к работе: 10 языковых сред выполнения, 2 средства доказательства теорем, инструменты сборки. Можно установить дополнительные.
|
|
30
|
-
| **Эффективность токенов** | Рутинные задачи автоматизированы в коде, поэтому токены ИИ сосредоточены на творческом решении проблем.
|
|
31
|
-
| **Свобода времени** | То, что занимает у людей 2–8 часов, ИИ выполняет за 10–25 минут за рабочую сессию. Возможно массовое выполнение задач в репозитории. «Код пишется, пока вы спите».
|
|
32
|
-
| **Масштабирование с оркестрацией** | Параллельные воркеры ощущаются как команда
|
|
33
|
-
| **Контроль со стороны человека** | ИИ создаёт черновые PR — вы решаете, что вливать. Контроль качества там, где это важно.
|
|
34
|
-
| **Программирование с любого устройства** | Управляйте ИИ с любого устройства через `/solve` и `/hive` посредством Telegram-бота. ПК, IDE или ноутбук не нужны.
|
|
35
|
-
| **100% открытый исходный код** | Лицензия Unlicense (общественное достояние). Полная прозрачность, без привязки к поставщику.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
| Возможность | Что это означает для вас |
|
|
25
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| **Без постоянного контроля** | Полный автономный режим с правами sudo. ИИ обладает творческой свободой, как настоящий программист. |
|
|
27
|
+
| **Изоляция в облаке** | Работает на выделенных виртуальных машинах или в Docker. Легко восстановить в случае поломки. |
|
|
28
|
+
| **Полный доступ к интернету + Sudo** | ИИ может устанавливать пакеты, получать документацию и настраивать систему по мере необходимости. |
|
|
29
|
+
| **Предустановленный инструментарий** | Более 25 ГБ готово к работе: 10 языковых сред выполнения, 2 средства доказательства теорем, инструменты сборки. Можно установить дополнительные. |
|
|
30
|
+
| **Эффективность токенов** | Рутинные задачи автоматизированы в коде, поэтому токены ИИ сосредоточены на творческом решении проблем. |
|
|
31
|
+
| **Свобода времени** | То, что занимает у людей 2–8 часов, ИИ выполняет за 10–25 минут за рабочую сессию. Возможно массовое выполнение задач в репозитории. «Код пишется, пока вы спите». |
|
|
32
|
+
| **Масштабирование с оркестрацией** | Параллельные воркеры ощущаются как команда разработчиков. Свяжите Claude MAX и ChatGPT Pro (по $200 каждый), чтобы получить два независимых почти безлимитных бюджета. |
|
|
33
|
+
| **Контроль со стороны человека** | ИИ создаёт черновые PR — вы решаете, что вливать. Контроль качества там, где это важно. |
|
|
34
|
+
| **Программирование с любого устройства** | Управляйте ИИ с любого устройства через `/solve` и `/hive` посредством Telegram-бота. ПК, IDE или ноутбук не нужны. |
|
|
35
|
+
| **100% открытый исходный код** | Лицензия Unlicense (общественное достояние). Полная прозрачность, без привязки к поставщику. |
|
|
36
|
+
|
|
37
|
+
**Стоимость**: Hive Mind поддерживает две подписки по $200 в месяц как полнофункциональные почти «безлимитные» варианты:
|
|
38
|
+
|
|
39
|
+
| Подписка | Используется с `--tool` | Модель по умолчанию | Лучше всего подходит для |
|
|
40
|
+
| ----------------------------------------------------------------- | ----------------------- | ------------------- | ---------------------------------------------------------- |
|
|
41
|
+
| **Anthropic Claude MAX** (~$200 в месяц, часто скидка 50% = $400) | `claude` (по умолчанию) | Sonnet/Haiku | Максимальная креативность и сильное общее кодовое мышление |
|
|
42
|
+
| **OpenAI ChatGPT Pro** ($200 в месяц, включает Codex) | `codex` | `gpt-5.5` | Надёжные детерминированные рефакторинги и быстрые итерации |
|
|
43
|
+
|
|
44
|
+
Оба инструмента можно сочетать в одном hive. Воркеры могут параллельно запускать разные инструменты, а `/codex` или `/solve --tool codex` направляет задачи в ChatGPT Pro, тогда как маршрут по умолчанию идёт в Claude MAX. Выбирать только один вариант не требуется: любой одной подписки достаточно для работы, а использование обеих открывает режим параллелизма по инструментам/моделям (#1474).
|
|
40
45
|
|
|
41
46
|
Hive Mind обладает высоким уровнем творчества, неотличимым от среднего программиста. Он задаёт вопросы, если требования неясны, и вы можете уточнять их на ходу через комментарии к PR.
|
|
42
47
|
|
|
@@ -48,7 +53,7 @@ Hive Mind обладает высоким уровнем творчества,
|
|
|
48
53
|
|
|
49
54
|
Рекомендуется использовать Docker для установки (как локально, так и на серверах). Смотрите раздел [Установка через Docker](#использование-docker) ниже.
|
|
50
55
|
|
|
51
|
-
Это программное обеспечение
|
|
56
|
+
Это программное обеспечение запускает поддерживаемые ИИ-инструменты, такие как Claude Code и Codex, в полностью автономном режиме, а значит, они могут выполнять любые команды по своему усмотрению.
|
|
52
57
|
|
|
53
58
|
Это может привести к непредвиденным побочным эффектам.
|
|
54
59
|
|
|
@@ -60,7 +65,7 @@ Hive Mind обладает высоким уровнем творчества,
|
|
|
60
65
|
|
|
61
66
|
Существует бесконечное множество способов извлечь токены с виртуальной машины, подключённой к интернету. Это включает, но не ограничивается:
|
|
62
67
|
|
|
63
|
-
- **Токены Claude MAX** — необходимы для работы
|
|
68
|
+
- **Токены Claude MAX** и/или **токены ChatGPT Pro (Codex)** — необходимы для работы ИИ; можно запускать с любым из них или с обоими
|
|
64
69
|
- **Токены GitHub** — необходимы для доступа к репозиториям
|
|
65
70
|
- **API-ключи и учётные данные** — любые конфиденциальные данные в системе
|
|
66
71
|
|
|
@@ -161,7 +166,7 @@ docker exec -it hive-mind /bin/bash
|
|
|
161
166
|
# Inside the container, authenticate with GitHub
|
|
162
167
|
gh-setup-git-identity
|
|
163
168
|
|
|
164
|
-
# Authenticate with Claude
|
|
169
|
+
# Authenticate with Claude (if you have Claude MAX)
|
|
165
170
|
claude
|
|
166
171
|
|
|
167
172
|
# Optionally set configuration like this:
|
|
@@ -174,6 +179,14 @@ claude
|
|
|
174
179
|
# Optionally test Claude connection
|
|
175
180
|
claude -p hi --model haiku
|
|
176
181
|
|
|
182
|
+
# Authenticate with Codex (if you have ChatGPT Pro)
|
|
183
|
+
codex login --device-auth
|
|
184
|
+
|
|
185
|
+
# Optionally test Codex connection. codex exec refuses to run unless
|
|
186
|
+
# either cwd is a git repo it trusts or --skip-git-repo-check is passed.
|
|
187
|
+
# It prints the refusal to STDOUT but still exits 0, so do not skip the flag.
|
|
188
|
+
codex exec --skip-git-repo-check --model gpt-5.4-mini "reply with only OK"
|
|
189
|
+
|
|
177
190
|
# You might need to update hive-mind and agent to latest versions:
|
|
178
191
|
bun install -g @link-assistant/hive-mind
|
|
179
192
|
bun install -g @link-assistant/agent
|
package/README.zh.md
CHANGED
|
@@ -29,14 +29,19 @@ Hive Mind 是一款**通用 AI**(迷你 AGI),能够处理广泛的任务
|
|
|
29
29
|
| **预装工具链** | 25GB+ 开箱即用:10 种语言运行时、2 个定理证明器、构建工具,还可继续安装更多。 |
|
|
30
30
|
| **高效利用 Token** | 常规任务通过代码自动化完成,让 AI Token 专注于创造性问题解决。 |
|
|
31
31
|
| **节省时间** | 人类需要 2~8 小时的工作,AI 每个工作会话仅需 10~25 分钟完成。可批量执行仓库中的任务。"代码在你睡觉时已写好。" |
|
|
32
|
-
| **编排式扩展** |
|
|
32
|
+
| **编排式扩展** | 并行工作进程犹如一支开发团队。可将 Claude MAX 和 ChatGPT Pro(各 $200)配对,获得两份独立的近乎无限预算。 |
|
|
33
33
|
| **人工控制** | AI 创建草稿 PR——由您决定是否合并。在关键节点设置质量把关。 |
|
|
34
34
|
| **任意设备编程** | 通过 Telegram 机器人使用 `/solve` 和 `/hive` 命令,在任意设备上管理 AI,无需 PC、IDE 或笔记本电脑。 |
|
|
35
35
|
| **100% 开源** | Unlicense(公共领域)。完全透明,无供应商锁定。 |
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
**费用**:Hive Mind 支持两种 $200/月订阅作为功能完整、近乎“无限”的选项:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
| 订阅 | 搭配 `--tool` | 默认模型 | 最适合场景 |
|
|
40
|
+
| ---------------------------------------------------------------- | ---------------- | ------------ | ---------------------------- |
|
|
41
|
+
| **Anthropic Claude MAX**(约 $200/月,常有五折优惠 = 价值 $400) | `claude`(默认) | Sonnet/Haiku | 最高创造力、最强通用代码推理 |
|
|
42
|
+
| **OpenAI ChatGPT Pro**($200/月,包含 Codex) | `codex` | `gpt-5.5` | 稳定确定性的重构和快速迭代 |
|
|
43
|
+
|
|
44
|
+
两个工具可以在同一个 hive 中组合使用。Worker 可以并行运行不同工具,`/codex` 或 `/solve --tool codex` 会将任务路由到 ChatGPT Pro,而默认路由到 Claude MAX。无需二选一:任意单一订阅都足以运行,同时使用两者可解锁按工具/模型划分的并发模式(#1474)。
|
|
40
45
|
|
|
41
46
|
Hive Mind 具备与普通程序员无异的高度创造力。当需求不明确时,它会主动提问,您也可以随时通过 PR 评论进行说明补充。
|
|
42
47
|
|
|
@@ -48,7 +53,7 @@ Hive Mind 具备与普通程序员无异的高度创造力。当需求不明确
|
|
|
48
53
|
|
|
49
54
|
建议使用 Docker 进行安装(本地和服务器均适用)。请参阅下方的 [Docker 安装](#使用-docker) 部分。
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
本软件会以完全自主模式运行 Claude Code、Codex 等受支持的 AI 工具,这意味着它们可以自由执行任何它们认为合适的命令。
|
|
52
57
|
|
|
53
58
|
这可能导致意想不到的副作用。
|
|
54
59
|
|
|
@@ -60,7 +65,7 @@ Hive Mind 具备与普通程序员无异的高度创造力。当需求不明确
|
|
|
60
65
|
|
|
61
66
|
从连接互联网的虚拟机中提取 Token 的方式多种多样,包括但不限于:
|
|
62
67
|
|
|
63
|
-
- **Claude MAX Token** - AI
|
|
68
|
+
- **Claude MAX Token** 和/或 **ChatGPT Pro (Codex) Token** - AI 操作所必需;您可以使用其中任意一种或同时使用两者
|
|
64
69
|
- **GitHub Token** - 仓库访问所必需
|
|
65
70
|
- **API 密钥和凭证** - 系统上的任何敏感数据
|
|
66
71
|
|
|
@@ -161,7 +166,7 @@ docker exec -it hive-mind /bin/bash
|
|
|
161
166
|
# Inside the container, authenticate with GitHub
|
|
162
167
|
gh-setup-git-identity
|
|
163
168
|
|
|
164
|
-
# Authenticate with Claude
|
|
169
|
+
# Authenticate with Claude (if you have Claude MAX)
|
|
165
170
|
claude
|
|
166
171
|
|
|
167
172
|
# Optionally set configuration like this:
|
|
@@ -174,6 +179,14 @@ claude
|
|
|
174
179
|
# Optionally test Claude connection
|
|
175
180
|
claude -p hi --model haiku
|
|
176
181
|
|
|
182
|
+
# Authenticate with Codex (if you have ChatGPT Pro)
|
|
183
|
+
codex login --device-auth
|
|
184
|
+
|
|
185
|
+
# Optionally test Codex connection. codex exec refuses to run unless
|
|
186
|
+
# either cwd is a git repo it trusts or --skip-git-repo-check is passed.
|
|
187
|
+
# It prints the refusal to STDOUT but still exits 0, so do not skip the flag.
|
|
188
|
+
codex exec --skip-git-repo-check --model gpt-5.4-mini "reply with only OK"
|
|
189
|
+
|
|
177
190
|
# You might need to update hive-mind and agent to latest versions:
|
|
178
191
|
bun install -g @link-assistant/hive-mind
|
|
179
192
|
bun install -g @link-assistant/agent
|
package/package.json
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helpers for external review services whose checks fail because the service
|
|
5
|
+
* could not run, not because repository code failed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EXTERNAL_REVIEW_SERVICE_PATTERNS = [/coderabbit/i, /code\s*rabbit/i];
|
|
9
|
+
|
|
10
|
+
const EXTERNAL_REVIEW_LIMIT_PATTERNS = [/insufficient\s+(?:review\s+)?credits?/i, /review\s+limit\s+reached/i, /rate\s+limit(?:ed|s)?/i, /run\s+out\s+of\s+usage\s+credits?/i, /out\s+of\s+usage\s+credits?/i, /usage\s+credits?\s+(?:exhausted|reached|insufficient)/i, /insufficient\s+(?:balance|limits?)/i, /no\s+(?:credits?|balance)\s+(?:left|remaining)/i];
|
|
11
|
+
|
|
12
|
+
const checkText = check => {
|
|
13
|
+
if (!check || typeof check !== 'object') return '';
|
|
14
|
+
return [check.name, check.context, check.description, check.summary, check.text, check.html_url, check.details_url].filter(Boolean).join('\n');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const isExternalReviewLimitCheck = check => {
|
|
18
|
+
const text = checkText(check);
|
|
19
|
+
if (!text) return false;
|
|
20
|
+
return EXTERNAL_REVIEW_SERVICE_PATTERNS.some(pattern => pattern.test(text)) && EXTERNAL_REVIEW_LIMIT_PATTERNS.some(pattern => pattern.test(text));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const splitExternalReviewLimitChecks = checks => {
|
|
24
|
+
const limitedChecks = [];
|
|
25
|
+
const actionableFailedChecks = [];
|
|
26
|
+
|
|
27
|
+
for (const check of checks || []) {
|
|
28
|
+
if (isExternalReviewLimitCheck(check)) {
|
|
29
|
+
limitedChecks.push(check);
|
|
30
|
+
} else {
|
|
31
|
+
actionableFailedChecks.push(check);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { limitedChecks, actionableFailedChecks };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const formatExternalReviewLimitCheck = check => {
|
|
39
|
+
const name = check?.name || check?.context || 'External review';
|
|
40
|
+
const description = check?.description && check.description !== name ? ` — ${check.description}` : '';
|
|
41
|
+
const url = check?.html_url || check?.details_url;
|
|
42
|
+
return `${name}${description}${url ? ` — ${url}` : ''}`;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const formatList = items => items.map(item => `- ${item}`).join('\n');
|
|
46
|
+
|
|
47
|
+
export const buildReadyForReviewComment = ({ blocker, ciStatus } = {}) => {
|
|
48
|
+
const skippedChecks = blocker?.details?.length ? blocker.details : (blocker?.checks || []).map(formatExternalReviewLimitCheck);
|
|
49
|
+
const skippedList = skippedChecks.length > 0 ? skippedChecks : ['External review check — blocked by service credits/rate limits'];
|
|
50
|
+
const passedChecks = (ciStatus?.passedChecks || []).map(formatExternalReviewLimitCheck);
|
|
51
|
+
const passedSection = passedChecks.length > 0 ? `\n\n**Checks completed successfully:**\n${formatList(passedChecks)}` : '';
|
|
52
|
+
|
|
53
|
+
return `## 🟡 Ready for review
|
|
54
|
+
|
|
55
|
+
Hive Mind stopped automatic restart because the remaining failed check is an external review quota/credit limit, not a code failure it can fix.
|
|
56
|
+
|
|
57
|
+
**Checks not executed:**
|
|
58
|
+
${formatList(skippedList)}${passedSection}
|
|
59
|
+
|
|
60
|
+
**Action required:**
|
|
61
|
+
- Restore the external review credits/rate limit and rerun the review, or decide manually whether this PR can proceed.
|
|
62
|
+
- No new AI session was started for this blocker.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
*Monitored by hive-mind with --auto-restart-until-mergeable flag.*`;
|
|
66
|
+
};
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -346,12 +346,14 @@ export async function checkPRCIStatus(owner, repo, prNumber, verbose = false) {
|
|
|
346
346
|
status: check.status,
|
|
347
347
|
conclusion: check.conclusion,
|
|
348
348
|
type: 'check_run',
|
|
349
|
+
description: check.output?.title || check.output?.summary || null,
|
|
349
350
|
})),
|
|
350
351
|
...statuses.map(status => ({
|
|
351
352
|
name: status.context,
|
|
352
353
|
status: status.state === 'pending' ? 'in_progress' : 'completed',
|
|
353
354
|
conclusion: status.state === 'pending' ? null : status.state === 'success' ? 'success' : status.state === 'failure' ? 'failure' : status.state,
|
|
354
355
|
type: 'status',
|
|
356
|
+
description: status.description || null,
|
|
355
357
|
})),
|
|
356
358
|
];
|
|
357
359
|
|
|
@@ -375,7 +377,7 @@ export async function checkPRCIStatus(owner, repo, prNumber, verbose = false) {
|
|
|
375
377
|
|
|
376
378
|
const hasPending = allChecks.some(c => c.status !== 'completed' || c.conclusion === null);
|
|
377
379
|
const allPassed = !hasPending && allChecks.every(c => c.conclusion === 'success' || c.conclusion === 'skipped' || c.conclusion === 'neutral');
|
|
378
|
-
const hasFailed = allChecks.some(c => c.conclusion === 'failure' || c.conclusion === 'cancelled' || c.conclusion === 'timed_out');
|
|
380
|
+
const hasFailed = allChecks.some(c => c.conclusion === 'failure' || c.conclusion === 'error' || c.conclusion === 'cancelled' || c.conclusion === 'timed_out');
|
|
379
381
|
|
|
380
382
|
let status;
|
|
381
383
|
if (hasPending) {
|
|
@@ -1126,6 +1128,7 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1126
1128
|
conclusion: check.conclusion, // success, failure, cancelled, timed_out, skipped, neutral, action_required, stale, null
|
|
1127
1129
|
type: 'check_run',
|
|
1128
1130
|
id: check.id,
|
|
1131
|
+
description: check.output?.title || check.output?.summary || null,
|
|
1129
1132
|
html_url: check.html_url || check.details_url || null,
|
|
1130
1133
|
})),
|
|
1131
1134
|
...statuses.map(status => ({
|
|
@@ -1134,6 +1137,7 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1134
1137
|
conclusion: status.state === 'pending' ? null : status.state === 'success' ? 'success' : status.state === 'failure' ? 'failure' : status.state,
|
|
1135
1138
|
type: 'status',
|
|
1136
1139
|
id: null,
|
|
1140
|
+
description: status.description || null,
|
|
1137
1141
|
html_url: status.target_url || null,
|
|
1138
1142
|
})),
|
|
1139
1143
|
];
|
|
@@ -1173,7 +1177,7 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1173
1177
|
// neutral, action_required, stale, null (not yet completed)
|
|
1174
1178
|
// GitHub check run statuses include: queued, in_progress, completed, waiting, requested, pending
|
|
1175
1179
|
const passedChecks = allChecks.filter(c => c.conclusion === 'success' || c.conclusion === 'skipped' || c.conclusion === 'neutral');
|
|
1176
|
-
const failedChecks = allChecks.filter(c => c.conclusion === 'failure' || c.conclusion === 'timed_out' || c.conclusion === 'action_required');
|
|
1180
|
+
const failedChecks = allChecks.filter(c => c.conclusion === 'failure' || c.conclusion === 'error' || c.conclusion === 'timed_out' || c.conclusion === 'action_required');
|
|
1177
1181
|
const cancelledChecks = allChecks.filter(c => c.conclusion === 'cancelled');
|
|
1178
1182
|
const staleChecks = allChecks.filter(c => c.conclusion === 'stale');
|
|
1179
1183
|
const pendingChecks = allChecks.filter(c => (c.status === 'in_progress' || c.status === 'waiting' || c.status === 'requested' || c.status === 'pending') && c.conclusion === null);
|
|
@@ -78,6 +78,9 @@ const formatRunLine = run => {
|
|
|
78
78
|
const toolComments = await import('./tool-comments.lib.mjs');
|
|
79
79
|
const { SESSION_ENDING_MARKERS, isToolGeneratedComment, isToolTrackedCommentId, trackToolCommentId } = toolComments;
|
|
80
80
|
|
|
81
|
+
const externalReviewLimitLib = await import('./external-review-limit.lib.mjs');
|
|
82
|
+
const { formatExternalReviewLimitCheck, splitExternalReviewLimitChecks } = externalReviewLimitLib;
|
|
83
|
+
|
|
81
84
|
/**
|
|
82
85
|
* Issue #1323: Check if a comment with specific content already exists on the PR
|
|
83
86
|
* This prevents duplicate status comments when multiple processes or restarts occur
|
|
@@ -798,11 +801,22 @@ export const getMergeBlockers = async (owner, repo, prNumber, verbose = false, c
|
|
|
798
801
|
sha: ciStatus.sha,
|
|
799
802
|
});
|
|
800
803
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
804
|
+
const { limitedChecks, actionableFailedChecks } = splitExternalReviewLimitChecks(ciStatus.failedChecks);
|
|
805
|
+
if (limitedChecks.length > 0) {
|
|
806
|
+
blockers.push({
|
|
807
|
+
type: 'external_review_limit',
|
|
808
|
+
message: 'External review check was not executed because credits/rate limits are exhausted',
|
|
809
|
+
details: limitedChecks.map(formatExternalReviewLimitCheck),
|
|
810
|
+
checks: limitedChecks,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
if (actionableFailedChecks.length > 0) {
|
|
814
|
+
blockers.push({
|
|
815
|
+
type: 'ci_failure',
|
|
816
|
+
message: 'CI/CD checks are failing',
|
|
817
|
+
details: actionableFailedChecks.map(c => c.name),
|
|
818
|
+
});
|
|
819
|
+
}
|
|
806
820
|
}
|
|
807
821
|
} else if (ciStatus.status === 'unknown') {
|
|
808
822
|
// Unable to determine CI status - treat as pending to be safe
|
|
@@ -61,7 +61,10 @@ const { buildCancelledCIReviewComment, getRetriggerableWorkflowRuns, shouldStopF
|
|
|
61
61
|
|
|
62
62
|
// Issue #1625: Shared marker constants + posting/tracking helpers
|
|
63
63
|
const toolComments = await import('./tool-comments.lib.mjs');
|
|
64
|
-
const { READY_TO_MERGE_MARKER, AUTO_RESTART_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
|
|
64
|
+
const { READY_TO_MERGE_MARKER, READY_FOR_REVIEW_MARKER, AUTO_RESTART_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
|
|
65
|
+
|
|
66
|
+
const externalReviewLimitLib = await import('./external-review-limit.lib.mjs');
|
|
67
|
+
const { buildReadyForReviewComment } = externalReviewLimitLib;
|
|
65
68
|
|
|
66
69
|
// Issue #1728: Per-iteration working session summary attachment helper
|
|
67
70
|
// Issue #1763: Per-iteration PR ↔ issue link verification (so a clobbered
|
|
@@ -517,7 +520,44 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
517
520
|
|
|
518
521
|
// Reason 2: CI failures (only if NOT a billing limit issue and NOT just cancelled)
|
|
519
522
|
// Only restart AI when we have genuine code failures (real feedback to act on)
|
|
523
|
+
const externalReviewLimitBlocker = blockers.find(b => b.type === 'external_review_limit');
|
|
520
524
|
const ciBlocker = blockers.find(b => b.type === 'ci_failure');
|
|
525
|
+
const hasMergeConflictBlocker = blockers.some(b => b.type === 'not_mergeable' && b.message?.includes('conflicts'));
|
|
526
|
+
if (externalReviewLimitBlocker && !ciBlocker && !billingBlocker && !cancelledBlocker && !hasNewComments && !hasUncommittedChanges && !hasMergeConflictBlocker) {
|
|
527
|
+
await log('');
|
|
528
|
+
await log(formatAligned('🟡', 'READY FOR REVIEW', 'External review quota/credit limit requires human decision'));
|
|
529
|
+
for (const detail of externalReviewLimitBlocker.details || []) {
|
|
530
|
+
await log(formatAligned('', 'Check not executed:', detail, 2));
|
|
531
|
+
}
|
|
532
|
+
await log(formatAligned('', 'Action:', 'Stopping auto-restart without starting another AI session', 2));
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
const commentSignature = `## 🟡 ${READY_FOR_REVIEW_MARKER}`;
|
|
536
|
+
const hasExistingReadyForReviewComment = await checkForExistingComment(owner, repo, prNumber, commentSignature, argv.verbose);
|
|
537
|
+
if (hasExistingReadyForReviewComment) {
|
|
538
|
+
await log(formatAligned('', `Skipping duplicate "${READY_FOR_REVIEW_MARKER}" comment (already posted by another process)`, '', 2));
|
|
539
|
+
} else {
|
|
540
|
+
const commentBody = buildReadyForReviewComment({
|
|
541
|
+
blocker: externalReviewLimitBlocker,
|
|
542
|
+
ciStatus,
|
|
543
|
+
});
|
|
544
|
+
await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: commentBody });
|
|
545
|
+
await log(formatAligned('', '💬 Posted ready-for-review notification to PR', '', 2));
|
|
546
|
+
}
|
|
547
|
+
} catch (commentError) {
|
|
548
|
+
reportError(commentError, {
|
|
549
|
+
context: 'post_external_review_limit_comment',
|
|
550
|
+
owner,
|
|
551
|
+
repo,
|
|
552
|
+
prNumber,
|
|
553
|
+
operation: 'comment_on_pr',
|
|
554
|
+
});
|
|
555
|
+
await log(formatAligned('', '⚠️ Could not post ready-for-review comment to PR', '', 2));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return { success: false, reason: 'external_review_limit', latestSessionId, latestAnthropicCost };
|
|
559
|
+
}
|
|
560
|
+
|
|
521
561
|
if (ciBlocker && !billingBlocker) {
|
|
522
562
|
shouldRestart = true;
|
|
523
563
|
restartReason = restartReason ? `${restartReason}; CI failures` : 'CI failures detected';
|
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 { installTelegramFormattingFallback, safeEditMessageText, safeReply } = await import('./telegram-safe-reply.lib.mjs');
|
|
43
|
+
const { installTelegramFormattingFallback, isTelegramFormattingError, isTelegramMessageTooLongError, safeEditMessageText, safeReply, TELEGRAM_TEXT_LIMIT } = 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');
|
|
@@ -1214,15 +1214,17 @@ bot.catch((error, ctx) => {
|
|
|
1214
1214
|
|
|
1215
1215
|
// Try to notify the user about the error with more details
|
|
1216
1216
|
if (ctx?.reply) {
|
|
1217
|
-
const isTelegramParsingError =
|
|
1217
|
+
const isTelegramParsingError = isTelegramFormattingError(error);
|
|
1218
|
+
const isTelegramTextLimitError = isTelegramMessageTooLongError(error);
|
|
1218
1219
|
|
|
1219
1220
|
let errorMessage;
|
|
1220
1221
|
|
|
1221
|
-
if (isTelegramParsingError) {
|
|
1222
|
+
if (isTelegramParsingError || isTelegramTextLimitError) {
|
|
1222
1223
|
// Issue #1460: Log detailed context for root cause analysis (always logged, not just in verbose mode)
|
|
1223
1224
|
const userInfo = ctx.from ? { id: ctx.from.id, username: ctx.from.username, first_name: ctx.from.first_name, last_name: ctx.from.last_name } : 'unknown';
|
|
1224
|
-
|
|
1225
|
-
console.error(`[telegram-bot]
|
|
1225
|
+
const errorKind = isTelegramTextLimitError ? 'Message length error' : 'Parsing error';
|
|
1226
|
+
console.error(`[telegram-bot] ${errorKind}: ${error.message}`);
|
|
1227
|
+
console.error(`[telegram-bot] ${errorKind} context - user: ${JSON.stringify(userInfo)}, command: ${ctx.message?.text?.split(' ')[0] || 'unknown'}`);
|
|
1226
1228
|
console.error(`[telegram-bot] User input text: ${ctx.message?.text || 'none'}`);
|
|
1227
1229
|
if (ctx.message?.text) {
|
|
1228
1230
|
const visibleInput = makeSpecialCharsVisible(ctx.message.text, { maxLength: 500 });
|
|
@@ -1233,8 +1235,11 @@ bot.catch((error, ctx) => {
|
|
|
1233
1235
|
}
|
|
1234
1236
|
}
|
|
1235
1237
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
+
if (isTelegramTextLimitError) {
|
|
1239
|
+
errorMessage = `❌ Telegram rejected an oversized bot message.\n\nThe bot splits messages above ${TELEGRAM_TEXT_LIMIT} characters automatically. Please try your command again.\n\nIf the issue persists, contact support with Update ID: ${ctx.update.update_id}`;
|
|
1240
|
+
} else {
|
|
1241
|
+
errorMessage = `❌ Telegram rejected a formatted bot message, and the fallback handler could not recover automatically.\n\nPlease try your command again.\n\nIf the issue persists, contact support with Update ID: ${ctx.update.update_id}`;
|
|
1242
|
+
}
|
|
1238
1243
|
} else {
|
|
1239
1244
|
errorMessage = '❌ An error occurred while processing your request.\n\n';
|
|
1240
1245
|
if (error.message) {
|
|
@@ -1251,20 +1256,13 @@ bot.catch((error, ctx) => {
|
|
|
1251
1256
|
if (VERBOSE) errorMessage += `\n\n🔍 Debug info: Update ID: ${ctx.update.update_id}`;
|
|
1252
1257
|
}
|
|
1253
1258
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1259
|
+
safeReply(ctx, errorMessage, { fallbackLocale: resolveLocaleFromTelegramCtx(ctx), verbose: VERBOSE }).catch(replyError => {
|
|
1260
|
+
console.error('Failed to send error message to user:', replyError);
|
|
1261
|
+
const plainMessage = `An error occurred while processing your request. Please try again or contact support.\n\nError: ${error.message || 'Unknown error'}`;
|
|
1262
|
+
ctx.reply(plainMessage).catch(fallbackError => {
|
|
1263
|
+
console.error('Failed to send fallback error message:', fallbackError);
|
|
1258
1264
|
});
|
|
1259
|
-
}
|
|
1260
|
-
ctx.reply(errorMessage, { parse_mode: 'Markdown' }).catch(replyError => {
|
|
1261
|
-
console.error('Failed to send error message to user:', replyError);
|
|
1262
|
-
const plainMessage = `An error occurred while processing your request. Please try again or contact support.\n\nError: ${error.message || 'Unknown error'}`;
|
|
1263
|
-
ctx.reply(plainMessage).catch(fallbackError => {
|
|
1264
|
-
console.error('Failed to send fallback error message:', fallbackError);
|
|
1265
|
-
});
|
|
1266
|
-
});
|
|
1267
|
-
}
|
|
1265
|
+
});
|
|
1268
1266
|
}
|
|
1269
1267
|
});
|
|
1270
1268
|
|
|
@@ -2,6 +2,7 @@ import { normalizeLocale, t } from './i18n.lib.mjs';
|
|
|
2
2
|
|
|
3
3
|
const FORMATTING_FALLBACK_INSTALLED = Symbol.for('hiveMind.telegramFormattingFallbackInstalled');
|
|
4
4
|
const DEFAULT_FORMATTING_FALLBACK_WARNING = '⚠️ Formatting error detected. Showing plain text fallback.';
|
|
5
|
+
export const TELEGRAM_TEXT_LIMIT = 4096;
|
|
5
6
|
const FORMATTING_FALLBACK_WARNINGS = {
|
|
6
7
|
en: DEFAULT_FORMATTING_FALLBACK_WARNING,
|
|
7
8
|
ru: '⚠️ Обнаружена ошибка форматирования. Показываю обычный текст.',
|
|
@@ -18,9 +19,18 @@ function splitOptions(options = {}) {
|
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
function getTelegramErrorMessage(error) {
|
|
23
|
+
return error?.description || error?.message || String(error || '');
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
export function isTelegramFormattingError(error) {
|
|
22
|
-
const message =
|
|
23
|
-
return /can't parse entities/i.test(message) || /can't find end of/i.test(message) || /entity.*parse/i.test(message) || (/
|
|
27
|
+
const message = getTelegramErrorMessage(error);
|
|
28
|
+
return /can't parse entities/i.test(message) || /can't find end of/i.test(message) || /entity.*parse/i.test(message) || /parse.*entity/i.test(message) || /character .* is reserved/i.test(message) || /unsupported start tag/i.test(message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isTelegramMessageTooLongError(error) {
|
|
32
|
+
const message = getTelegramErrorMessage(error);
|
|
33
|
+
return /message is too long/i.test(message) || /message text is too long/i.test(message) || /text is too long/i.test(message) || /message_too_long/i.test(message) || (/bad request/i.test(message) && /too long/i.test(message) && /(message|text|caption)/i.test(message));
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
export function stripTelegramMarkdown(text) {
|
|
@@ -36,6 +46,45 @@ export function stripTelegramMarkdown(text) {
|
|
|
36
46
|
.replace(/`([^`]+)`/g, '$1');
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
function findTelegramSplitIndex(text, limit) {
|
|
50
|
+
const minUsefulSplit = Math.floor(limit * 0.45);
|
|
51
|
+
const separators = ['\n\n', '\n', '. ', '; ', ', ', ' '];
|
|
52
|
+
|
|
53
|
+
for (const separator of separators) {
|
|
54
|
+
const searchEnd = Math.max(0, limit - separator.length);
|
|
55
|
+
const index = text.lastIndexOf(separator, searchEnd);
|
|
56
|
+
if (index >= minUsefulSplit) {
|
|
57
|
+
return index + separator.length;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return limit;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function splitTelegramMessageText(text, limit = TELEGRAM_TEXT_LIMIT) {
|
|
65
|
+
const source = String(text ?? '');
|
|
66
|
+
if (source.length <= limit) return [source];
|
|
67
|
+
|
|
68
|
+
const chunks = [];
|
|
69
|
+
let remaining = source;
|
|
70
|
+
|
|
71
|
+
while (remaining.length > limit) {
|
|
72
|
+
let splitAt = findTelegramSplitIndex(remaining, limit);
|
|
73
|
+
let chunk = remaining.slice(0, splitAt).trimEnd();
|
|
74
|
+
|
|
75
|
+
if (!chunk) {
|
|
76
|
+
splitAt = limit;
|
|
77
|
+
chunk = remaining.slice(0, splitAt);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
chunks.push(chunk);
|
|
81
|
+
remaining = remaining.slice(splitAt).trimStart();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (remaining) chunks.push(remaining);
|
|
85
|
+
return chunks;
|
|
86
|
+
}
|
|
87
|
+
|
|
39
88
|
function getFormattingFallbackWarning(locale) {
|
|
40
89
|
const key = 'telegram.formatting_fallback_warning';
|
|
41
90
|
const normalizedLocale = normalizeLocale(locale);
|
|
@@ -50,7 +99,7 @@ export function buildTelegramFormattingFallbackText(text, options = {}) {
|
|
|
50
99
|
}
|
|
51
100
|
|
|
52
101
|
function logFormattingFailure(scope, error, text, verbose = false, fallbackText = null) {
|
|
53
|
-
const message =
|
|
102
|
+
const message = getTelegramErrorMessage(error);
|
|
54
103
|
console.error(`[telegram-bot] ${scope}: formatted Telegram message failed: ${message}`);
|
|
55
104
|
if (verbose) {
|
|
56
105
|
const originalBytes = Buffer.byteLength(String(text ?? ''), 'utf-8');
|
|
@@ -76,54 +125,241 @@ function logFormattingFailure(scope, error, text, verbose = false, fallbackText
|
|
|
76
125
|
}
|
|
77
126
|
}
|
|
78
127
|
|
|
128
|
+
function logMessageTooLongFailure(scope, error, text, verbose = false, fallbackText = null) {
|
|
129
|
+
const message = getTelegramErrorMessage(error);
|
|
130
|
+
console.error(`[telegram-bot] ${scope}: Telegram message exceeded ${TELEGRAM_TEXT_LIMIT} character limit: ${message}`);
|
|
131
|
+
if (verbose) {
|
|
132
|
+
const original = String(text ?? '');
|
|
133
|
+
console.error(`[telegram-bot] ${scope}: Oversized message (${original.length} chars, ${Buffer.byteLength(original, 'utf-8')} bytes): ${original}`);
|
|
134
|
+
if (fallbackText !== null) {
|
|
135
|
+
const fallback = String(fallbackText ?? '');
|
|
136
|
+
console.error(`[telegram-bot] ${scope}: Plain text fallback (${fallback.length} chars, ${Buffer.byteLength(fallback, 'utf-8')} bytes): ${fallback}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function logChunking(scope, text, chunks, verbose = false) {
|
|
142
|
+
if (chunks.length <= 1) return;
|
|
143
|
+
const source = String(text ?? '');
|
|
144
|
+
console.warn(`[telegram-bot] ${scope}: Telegram text is ${source.length} chars (${Buffer.byteLength(source, 'utf-8')} bytes), splitting into ${chunks.length} messages (limit ${TELEGRAM_TEXT_LIMIT}).`);
|
|
145
|
+
if (verbose) {
|
|
146
|
+
chunks.forEach((chunk, index) => {
|
|
147
|
+
console.error(`[telegram-bot] ${scope}: Chunk ${index + 1}/${chunks.length} (${chunk.length} chars, ${Buffer.byteLength(chunk, 'utf-8')} bytes): ${chunk}`);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getPlainTextOptions(telegramOptions) {
|
|
153
|
+
return { ...telegramOptions, parse_mode: undefined, entities: undefined };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function sendPlainTextChunks({ text, telegramOptions, scope, verbose, sendChunk }) {
|
|
157
|
+
const plainOptions = getPlainTextOptions(telegramOptions);
|
|
158
|
+
const chunks = splitTelegramMessageText(text);
|
|
159
|
+
logChunking(`${scope}:plainText`, text, chunks, verbose);
|
|
160
|
+
|
|
161
|
+
let firstResult;
|
|
162
|
+
for (const chunk of chunks) {
|
|
163
|
+
const result = await sendChunk(chunk, plainOptions);
|
|
164
|
+
if (firstResult === undefined) firstResult = result;
|
|
165
|
+
}
|
|
166
|
+
return firstResult;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function sendTelegramTextChunks({ text, telegramOptions, fallbackLocale, verbose, scope, sendChunk }) {
|
|
170
|
+
const chunks = splitTelegramMessageText(text);
|
|
171
|
+
logChunking(scope, text, chunks, verbose);
|
|
172
|
+
|
|
173
|
+
let firstResult;
|
|
174
|
+
for (const chunk of chunks) {
|
|
175
|
+
try {
|
|
176
|
+
const result = await sendChunk(chunk, telegramOptions);
|
|
177
|
+
if (firstResult === undefined) firstResult = result;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
let fallbackText;
|
|
180
|
+
if (isTelegramFormattingError(error)) {
|
|
181
|
+
fallbackText = buildTelegramFormattingFallbackText(chunk, { fallbackLocale });
|
|
182
|
+
logFormattingFailure(scope, error, chunk, verbose, fallbackText);
|
|
183
|
+
} else if (isTelegramMessageTooLongError(error)) {
|
|
184
|
+
fallbackText = stripTelegramMarkdown(chunk);
|
|
185
|
+
logMessageTooLongFailure(scope, error, chunk, verbose, fallbackText);
|
|
186
|
+
} else {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = await sendPlainTextChunks({
|
|
191
|
+
text: fallbackText,
|
|
192
|
+
telegramOptions,
|
|
193
|
+
scope,
|
|
194
|
+
verbose,
|
|
195
|
+
sendChunk,
|
|
196
|
+
});
|
|
197
|
+
if (firstResult === undefined) firstResult = result;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return firstResult;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function sendRemainingEditChunks({ chunks, telegramOptions, fallbackLocale, verbose, scope, sendFollowUpChunk }) {
|
|
205
|
+
if (chunks.length === 0) return undefined;
|
|
206
|
+
if (!sendFollowUpChunk) {
|
|
207
|
+
console.error(`[telegram-bot] ${scope}: cannot send ${chunks.length} remaining chunk(s) after edit because chat_id is unavailable.`);
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return await sendTelegramTextChunks({
|
|
212
|
+
text: chunks.join('\n'),
|
|
213
|
+
telegramOptions,
|
|
214
|
+
fallbackLocale,
|
|
215
|
+
verbose,
|
|
216
|
+
scope: `${scope}:followUp`,
|
|
217
|
+
sendChunk: sendFollowUpChunk,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function sendPlainRemainingEditChunks({ chunks, telegramOptions, verbose, scope, sendFollowUpChunk }) {
|
|
222
|
+
if (chunks.length === 0) return undefined;
|
|
223
|
+
if (!sendFollowUpChunk) {
|
|
224
|
+
console.error(`[telegram-bot] ${scope}: cannot send ${chunks.length} plain-text fallback chunk(s) after edit because chat_id is unavailable.`);
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return await sendPlainTextChunks({
|
|
229
|
+
text: chunks.join('\n'),
|
|
230
|
+
telegramOptions,
|
|
231
|
+
scope: `${scope}:followUp`,
|
|
232
|
+
verbose,
|
|
233
|
+
sendChunk: sendFollowUpChunk,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function editTelegramTextChunks({ text, telegramOptions, fallbackLocale, verbose, scope, editChunk, sendFollowUpChunk }) {
|
|
238
|
+
const chunks = splitTelegramMessageText(text);
|
|
239
|
+
logChunking(scope, text, chunks, verbose);
|
|
240
|
+
|
|
241
|
+
const [firstChunk, ...remainingChunks] = chunks;
|
|
242
|
+
try {
|
|
243
|
+
const result = await editChunk(firstChunk, telegramOptions);
|
|
244
|
+
await sendRemainingEditChunks({
|
|
245
|
+
chunks: remainingChunks,
|
|
246
|
+
telegramOptions,
|
|
247
|
+
fallbackLocale,
|
|
248
|
+
verbose,
|
|
249
|
+
scope,
|
|
250
|
+
sendFollowUpChunk,
|
|
251
|
+
});
|
|
252
|
+
return result;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
let fallbackText;
|
|
255
|
+
if (isTelegramFormattingError(error)) {
|
|
256
|
+
fallbackText = buildTelegramFormattingFallbackText(firstChunk, { fallbackLocale });
|
|
257
|
+
logFormattingFailure(scope, error, firstChunk, verbose, fallbackText);
|
|
258
|
+
} else if (isTelegramMessageTooLongError(error)) {
|
|
259
|
+
fallbackText = stripTelegramMarkdown(firstChunk);
|
|
260
|
+
logMessageTooLongFailure(scope, error, firstChunk, verbose, fallbackText);
|
|
261
|
+
} else {
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const plainOptions = getPlainTextOptions(telegramOptions);
|
|
266
|
+
const fallbackChunks = splitTelegramMessageText(fallbackText);
|
|
267
|
+
logChunking(`${scope}:plainText`, fallbackText, fallbackChunks, verbose);
|
|
268
|
+
const [firstFallbackChunk, ...remainingFallbackChunks] = fallbackChunks;
|
|
269
|
+
const result = await editChunk(firstFallbackChunk, plainOptions);
|
|
270
|
+
await sendPlainRemainingEditChunks({
|
|
271
|
+
chunks: [...remainingFallbackChunks, ...remainingChunks.map(stripTelegramMarkdown)],
|
|
272
|
+
telegramOptions,
|
|
273
|
+
verbose,
|
|
274
|
+
scope,
|
|
275
|
+
sendFollowUpChunk,
|
|
276
|
+
});
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
79
281
|
// Issue #1460/#1497/#1788: try Markdown first, fall back to localized plain text on parsing errors.
|
|
80
282
|
export async function safeReply(ctx, text, options = {}) {
|
|
81
283
|
const { telegramOptions, fallbackLocale, verbose } = splitOptions(options);
|
|
82
284
|
const firstOptions = { parse_mode: 'Markdown', ...telegramOptions };
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
285
|
+
return await sendTelegramTextChunks({
|
|
286
|
+
text,
|
|
287
|
+
telegramOptions: firstOptions,
|
|
288
|
+
fallbackLocale,
|
|
289
|
+
verbose,
|
|
290
|
+
scope: 'safeReply',
|
|
291
|
+
sendChunk: (chunk, chunkOptions) => ctx.reply(chunk, chunkOptions),
|
|
292
|
+
});
|
|
91
293
|
}
|
|
92
294
|
|
|
93
295
|
export async function safeEditMessageText(telegram, chatId, messageId, inlineMessageId, text, options = {}) {
|
|
94
296
|
const { telegramOptions, fallbackLocale, verbose } = splitOptions(options);
|
|
95
297
|
const firstOptions = { parse_mode: 'Markdown', ...telegramOptions };
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
298
|
+
return await editTelegramTextChunks({
|
|
299
|
+
text,
|
|
300
|
+
telegramOptions: firstOptions,
|
|
301
|
+
fallbackLocale,
|
|
302
|
+
verbose,
|
|
303
|
+
scope: 'safeEditMessageText',
|
|
304
|
+
editChunk: (chunk, chunkOptions) => telegram.editMessageText(chatId, messageId, inlineMessageId, chunk, chunkOptions),
|
|
305
|
+
sendFollowUpChunk: chatId !== undefined && chatId !== null && typeof telegram.sendMessage === 'function' ? (chunk, chunkOptions) => telegram.sendMessage(chatId, chunk, chunkOptions) : null,
|
|
306
|
+
});
|
|
104
307
|
}
|
|
105
308
|
|
|
106
|
-
function
|
|
107
|
-
const original = telegram?.
|
|
309
|
+
function wrapTelegramSendMessage(telegram, defaults = {}) {
|
|
310
|
+
const original = telegram?.sendMessage;
|
|
108
311
|
if (typeof original !== 'function') return;
|
|
109
312
|
|
|
110
|
-
telegram
|
|
111
|
-
const text = args[
|
|
112
|
-
const originalOptions = args[
|
|
313
|
+
telegram.sendMessage = async function wrappedTelegramSendMessage(...args) {
|
|
314
|
+
const text = args[1];
|
|
315
|
+
const originalOptions = args[2] || {};
|
|
113
316
|
const { telegramOptions, fallbackLocale, verbose } = splitOptions(originalOptions);
|
|
114
|
-
args[
|
|
317
|
+
args[2] = telegramOptions;
|
|
115
318
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
319
|
+
if (typeof text !== 'string') return await original.apply(this, args);
|
|
320
|
+
|
|
321
|
+
return await sendTelegramTextChunks({
|
|
322
|
+
text,
|
|
323
|
+
telegramOptions,
|
|
324
|
+
fallbackLocale: fallbackLocale || defaults.fallbackLocale,
|
|
325
|
+
verbose: verbose || defaults.verbose,
|
|
326
|
+
scope: 'sendMessage',
|
|
327
|
+
sendChunk: (chunk, chunkOptions) => {
|
|
328
|
+
const chunkArgs = [...args];
|
|
329
|
+
chunkArgs[1] = chunk;
|
|
330
|
+
chunkArgs[2] = chunkOptions;
|
|
331
|
+
return original.apply(this, chunkArgs);
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function wrapTelegramEditMessageText(telegram, defaults = {}) {
|
|
338
|
+
const original = telegram?.editMessageText;
|
|
339
|
+
if (typeof original !== 'function') return;
|
|
340
|
+
|
|
341
|
+
telegram.editMessageText = async function wrappedTelegramEditMessageText(...args) {
|
|
342
|
+
const text = args[3];
|
|
343
|
+
const originalOptions = args[4] || {};
|
|
344
|
+
const { telegramOptions, fallbackLocale, verbose } = splitOptions(originalOptions);
|
|
345
|
+
args[4] = telegramOptions;
|
|
346
|
+
|
|
347
|
+
if (typeof text !== 'string') return await original.apply(this, args);
|
|
348
|
+
|
|
349
|
+
return await editTelegramTextChunks({
|
|
350
|
+
text,
|
|
351
|
+
telegramOptions,
|
|
352
|
+
fallbackLocale: fallbackLocale || defaults.fallbackLocale,
|
|
353
|
+
verbose: verbose || defaults.verbose,
|
|
354
|
+
scope: 'editMessageText',
|
|
355
|
+
editChunk: (chunk, chunkOptions) => {
|
|
356
|
+
const chunkArgs = [...args];
|
|
357
|
+
chunkArgs[3] = chunk;
|
|
358
|
+
chunkArgs[4] = chunkOptions;
|
|
359
|
+
return original.apply(this, chunkArgs);
|
|
360
|
+
},
|
|
361
|
+
sendFollowUpChunk: args[0] !== undefined && args[0] !== null && typeof this.sendMessage === 'function' ? (chunk, chunkOptions) => this.sendMessage(args[0], chunk, chunkOptions) : null,
|
|
362
|
+
});
|
|
127
363
|
};
|
|
128
364
|
}
|
|
129
365
|
|
|
@@ -135,8 +371,8 @@ export function installTelegramFormattingFallback(telegram, options = {}) {
|
|
|
135
371
|
verbose: Boolean(options.verbose),
|
|
136
372
|
};
|
|
137
373
|
|
|
138
|
-
|
|
139
|
-
|
|
374
|
+
wrapTelegramSendMessage(telegram, defaults);
|
|
375
|
+
wrapTelegramEditMessageText(telegram, defaults);
|
|
140
376
|
telegram[FORMATTING_FALLBACK_INSTALLED] = true;
|
|
141
377
|
return telegram;
|
|
142
378
|
}
|
|
@@ -49,6 +49,9 @@ export const AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER = 'Auto-restart-until-merge
|
|
|
49
49
|
// solve.auto-merge.lib.mjs — "ready to merge" status comments
|
|
50
50
|
export const READY_TO_MERGE_MARKER = 'Ready to merge';
|
|
51
51
|
|
|
52
|
+
// solve.auto-merge.lib.mjs — external review quota/credit stop comments
|
|
53
|
+
export const READY_FOR_REVIEW_MARKER = 'Ready for review';
|
|
54
|
+
|
|
52
55
|
// solve.auto-merge.lib.mjs — "auto-merged successfully" status comments
|
|
53
56
|
export const AUTO_MERGED_MARKER = 'Auto-merged';
|
|
54
57
|
|
|
@@ -107,7 +110,7 @@ export const USAGE_LIMIT_REACHED_MARKER = 'Usage Limit Reached';
|
|
|
107
110
|
* named constants above so that adding a new marker only requires adding
|
|
108
111
|
* the constant and appending it here.
|
|
109
112
|
*/
|
|
110
|
-
export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, CANCELLED_CI_REVIEW_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, WORKING_SESSION_SUMMARY_AUTOMATION_MARKER];
|
|
113
|
+
export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, READY_FOR_REVIEW_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, CANCELLED_CI_REVIEW_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, WORKING_SESSION_SUMMARY_AUTOMATION_MARKER];
|
|
111
114
|
|
|
112
115
|
/**
|
|
113
116
|
* Markers that indicate the end of a working session. Used by
|