@link-assistant/hive-mind 1.17.0 ā 1.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +2 -2
- package/src/claude.lib.mjs +7 -10
- package/src/config.lib.mjs +85 -11
- package/src/model-mapping.lib.mjs +2 -2
- package/src/model-validation.lib.mjs +6 -4
- package/src/solve.config.lib.mjs +1 -1
- package/src/telegram-accept-invitations.lib.mjs +4 -4
- package/src/telegram-bot.mjs +19 -3
- package/src/telegram-solve-queue-command.lib.mjs +96 -0
- package/src/telegram-solve-queue.lib.mjs +12 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.17.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ae013b3: Default thinking budget to zero (thinking disabled by default), align Opus 4.6 max thinking budget with standard models (31999), change `opus` alias to map to Opus 4.5 by default (supports both `opus-4-5` and `opus-4-6` aliases)
|
|
8
|
+
|
|
9
|
+
## 1.17.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0e59647: Fix /solve-queue command: register /solve_queue handler, fix hint text to use underscore instead of hyphen (Telegram Bot API only supports underscores in command names)
|
|
14
|
+
|
|
3
15
|
## 1.17.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.2",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"hive-telegram-bot": "./src/telegram-bot.mjs"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-telegram-message-filters.mjs",
|
|
16
|
+
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-solve-queue-command.mjs",
|
|
17
17
|
"test:queue": "node tests/solve-queue.test.mjs",
|
|
18
18
|
"test:limits-display": "node tests/limits-display.test.mjs",
|
|
19
19
|
"test:usage-limit": "node tests/test-usage-limit.mjs",
|
package/src/claude.lib.mjs
CHANGED
|
@@ -911,23 +911,20 @@ export const executeClaudeCommand = async params => {
|
|
|
911
911
|
try {
|
|
912
912
|
// Resolve thinking settings (handles translation between --think and --thinking-budget based on Claude version)
|
|
913
913
|
// See issue #1146 for details on thinking budget translation
|
|
914
|
-
const { thinkingBudget: resolvedThinkingBudget, thinkLevel, isNewVersion } = await resolveThinkingSettings(argv, log);
|
|
914
|
+
const { thinkingBudget: resolvedThinkingBudget, thinkLevel, isNewVersion, maxBudget } = await resolveThinkingSettings(argv, log);
|
|
915
915
|
|
|
916
916
|
// Set CLAUDE_CODE_MAX_OUTPUT_TOKENS (see issue #1076), MAX_THINKING_TOKENS (see issue #1146),
|
|
917
|
-
//
|
|
917
|
+
// MCP timeout configurations (see issue #1066), and CLAUDE_CODE_EFFORT_LEVEL for Opus 4.6 (Issue #1238)
|
|
918
918
|
// Pass model for model-specific max output tokens (Issue #1221)
|
|
919
|
-
|
|
919
|
+
// Pass thinkLevel and maxBudget for Opus 4.6 effort level conversion (Issue #1238)
|
|
920
|
+
const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model: mappedModel, thinkLevel, maxBudget });
|
|
920
921
|
const modelMaxOutputTokens = getMaxOutputTokensForModel(mappedModel);
|
|
921
922
|
if (argv.verbose) await log(`š CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${modelMaxOutputTokens}`, { verbose: true });
|
|
922
923
|
if (argv.verbose) await log(`š MCP_TIMEOUT: ${claudeCode.mcpTimeout}ms (server startup)`, { verbose: true });
|
|
923
924
|
if (argv.verbose) await log(`š MCP_TOOL_TIMEOUT: ${claudeCode.mcpToolTimeout}ms (tool execution)`, { verbose: true });
|
|
924
|
-
if (resolvedThinkingBudget !== undefined) {
|
|
925
|
-
|
|
926
|
-
}
|
|
927
|
-
// Log thinking level for older Claude Code versions that use thinking keywords
|
|
928
|
-
if (!isNewVersion && thinkLevel) {
|
|
929
|
-
await log(`š Thinking level (via keywords): ${thinkLevel}`, { verbose: true });
|
|
930
|
-
}
|
|
925
|
+
if (resolvedThinkingBudget !== undefined) await log(`š MAX_THINKING_TOKENS: ${resolvedThinkingBudget}`, { verbose: true });
|
|
926
|
+
if (claudeEnv.CLAUDE_CODE_EFFORT_LEVEL) await log(`š CLAUDE_CODE_EFFORT_LEVEL: ${claudeEnv.CLAUDE_CODE_EFFORT_LEVEL}`, { verbose: true });
|
|
927
|
+
if (!isNewVersion && thinkLevel) await log(`š Thinking level (via keywords): ${thinkLevel}`, { verbose: true });
|
|
931
928
|
if (argv.resume) {
|
|
932
929
|
// When resuming, pass prompt directly with -p flag. Escape double quotes for shell.
|
|
933
930
|
const simpleEscapedPrompt = prompt.replace(/"/g, '\\"');
|
package/src/config.lib.mjs
CHANGED
|
@@ -119,21 +119,24 @@ export const claudeCode = {
|
|
|
119
119
|
// Can be overridden via --max-thinking-budget option
|
|
120
120
|
export const DEFAULT_MAX_THINKING_BUDGET = 31999;
|
|
121
121
|
|
|
122
|
-
// Default max thinking budget for Opus 4.6 (Issue #1221)
|
|
123
|
-
//
|
|
122
|
+
// Default max thinking budget for Opus 4.6 (Issue #1221, updated in Issue #1238)
|
|
123
|
+
// Aligned with standard models (31999) for consistency.
|
|
124
|
+
// Opus 4.6 uses CLAUDE_CODE_EFFORT_LEVEL for thinking depth instead of MAX_THINKING_TOKENS
|
|
125
|
+
// (MAX_THINKING_TOKENS is ignored for Opus 4.6 unless set to 0 to disable thinking).
|
|
124
126
|
// Can be overridden via --max-thinking-budget option or HIVE_MIND_MAX_THINKING_BUDGET_OPUS_46
|
|
125
|
-
export const DEFAULT_MAX_THINKING_BUDGET_OPUS_46 = parseIntWithDefault('HIVE_MIND_MAX_THINKING_BUDGET_OPUS_46',
|
|
127
|
+
export const DEFAULT_MAX_THINKING_BUDGET_OPUS_46 = parseIntWithDefault('HIVE_MIND_MAX_THINKING_BUDGET_OPUS_46', 31999);
|
|
126
128
|
|
|
127
129
|
/**
|
|
128
|
-
* Check if a model is Opus 4.6 or later (Issue #1221)
|
|
130
|
+
* Check if a model is Opus 4.6 or later (Issue #1221, updated in Issue #1238)
|
|
129
131
|
* @param {string} model - The model name or ID
|
|
130
132
|
* @returns {boolean} True if the model is Opus 4.6 or later
|
|
131
133
|
*/
|
|
132
134
|
export const isOpus46OrLater = model => {
|
|
133
135
|
if (!model) return false;
|
|
134
136
|
const normalizedModel = model.toLowerCase();
|
|
135
|
-
// Check for opus
|
|
136
|
-
|
|
137
|
+
// Check for explicit opus-4-6 or later versions
|
|
138
|
+
// Note: The 'opus' alias now maps to Opus 4.5 (Issue #1238), so we only check explicit version identifiers
|
|
139
|
+
return normalizedModel.includes('opus-4-6') || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-5');
|
|
137
140
|
};
|
|
138
141
|
|
|
139
142
|
/**
|
|
@@ -202,6 +205,59 @@ export const getTokensToThinkingLevel = (maxBudget = DEFAULT_MAX_THINKING_BUDGET
|
|
|
202
205
|
// Default tokens to thinking level function (using default max budget)
|
|
203
206
|
export const tokensToThinkingLevel = getTokensToThinkingLevel(DEFAULT_MAX_THINKING_BUDGET);
|
|
204
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Valid effort levels for Opus 4.6 (Issue #1238)
|
|
210
|
+
* Opus 4.6 uses CLAUDE_CODE_EFFORT_LEVEL for thinking depth control
|
|
211
|
+
* @type {string[]}
|
|
212
|
+
*/
|
|
213
|
+
export const OPUS_46_EFFORT_LEVELS = ['low', 'medium', 'high'];
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Convert thinking level to Opus 4.6 effort level (Issue #1238)
|
|
217
|
+
* Opus 4.6 uses CLAUDE_CODE_EFFORT_LEVEL (low/medium/high) instead of MAX_THINKING_TOKENS
|
|
218
|
+
* @param {string|undefined} thinkLevel - The thinking level (off/low/medium/high/max)
|
|
219
|
+
* @returns {string|undefined} The effort level (low/medium/high) or undefined if thinking is off
|
|
220
|
+
*/
|
|
221
|
+
export const thinkLevelToEffortLevel = thinkLevel => {
|
|
222
|
+
if (!thinkLevel || thinkLevel === 'off') {
|
|
223
|
+
// No effort level when thinking is disabled
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Map hive-mind thinking levels to Opus 4.6 effort levels
|
|
228
|
+
// Note: Opus 4.6 only supports low/medium/high, not 'max'
|
|
229
|
+
// We map 'max' to 'high' as it's the highest available level
|
|
230
|
+
switch (thinkLevel) {
|
|
231
|
+
case 'low':
|
|
232
|
+
return 'low';
|
|
233
|
+
case 'medium':
|
|
234
|
+
return 'medium';
|
|
235
|
+
case 'high':
|
|
236
|
+
case 'max':
|
|
237
|
+
return 'high';
|
|
238
|
+
default:
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Convert thinking budget (tokens) to Opus 4.6 effort level (Issue #1238)
|
|
245
|
+
* Uses token thresholds to determine the appropriate effort level
|
|
246
|
+
* @param {number|undefined} thinkingBudget - The thinking budget in tokens
|
|
247
|
+
* @param {number} maxBudget - Maximum thinking budget (default: 31999)
|
|
248
|
+
* @returns {string|undefined} The effort level (low/medium/high) or undefined if thinking is off
|
|
249
|
+
*/
|
|
250
|
+
export const thinkingBudgetToEffortLevel = (thinkingBudget, maxBudget = DEFAULT_MAX_THINKING_BUDGET) => {
|
|
251
|
+
if (thinkingBudget === undefined || thinkingBudget === 0) {
|
|
252
|
+
// No effort level when thinking is disabled
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Convert tokens to thinking level, then to effort level
|
|
257
|
+
const thinkLevel = getTokensToThinkingLevel(maxBudget)(thinkingBudget);
|
|
258
|
+
return thinkLevelToEffortLevel(thinkLevel);
|
|
259
|
+
};
|
|
260
|
+
|
|
205
261
|
// Check if a version supports thinking budget (>= minimum version)
|
|
206
262
|
// Uses semver npm package for reliable version comparison (see issue #1146)
|
|
207
263
|
export const supportsThinkingBudget = (version, minVersion = '2.1.12') => {
|
|
@@ -221,6 +277,7 @@ export const supportsThinkingBudget = (version, minVersion = '2.1.12') => {
|
|
|
221
277
|
// Optionally sets MAX_THINKING_TOKENS when thinkingBudget is provided (see issue #1146)
|
|
222
278
|
// Also sets MCP_TIMEOUT and MCP_TOOL_TIMEOUT for MCP tool execution (see issue #1066)
|
|
223
279
|
// Supports model-specific max output tokens for Opus 4.6 (Issue #1221)
|
|
280
|
+
// Sets CLAUDE_CODE_EFFORT_LEVEL for Opus 4.6 models (Issue #1238)
|
|
224
281
|
export const getClaudeEnv = (options = {}) => {
|
|
225
282
|
// Get max output tokens based on model (Issue #1221)
|
|
226
283
|
const maxOutputTokens = options.model ? getMaxOutputTokensForModel(options.model) : claudeCode.maxOutputTokens;
|
|
@@ -235,12 +292,29 @@ export const getClaudeEnv = (options = {}) => {
|
|
|
235
292
|
MCP_TIMEOUT: String(claudeCode.mcpTimeout),
|
|
236
293
|
MCP_TOOL_TIMEOUT: String(claudeCode.mcpToolTimeout),
|
|
237
294
|
};
|
|
238
|
-
|
|
239
|
-
//
|
|
240
|
-
// Default is
|
|
241
|
-
|
|
242
|
-
|
|
295
|
+
|
|
296
|
+
// Set MAX_THINKING_TOKENS to control Claude Code's extended thinking feature (Claude Code >= 2.1.12)
|
|
297
|
+
// Default is 0 (thinking disabled) per Issue #1238. Set to 0 to disable thinking.
|
|
298
|
+
// Users can explicitly enable thinking via --think or --thinking-budget options.
|
|
299
|
+
env.MAX_THINKING_TOKENS = String(options.thinkingBudget ?? 0);
|
|
300
|
+
|
|
301
|
+
// For Opus 4.6+, also set CLAUDE_CODE_EFFORT_LEVEL to control thinking depth (Issue #1238)
|
|
302
|
+
// Opus 4.6 uses effort level (low/medium/high) instead of MAX_THINKING_TOKENS for thinking depth.
|
|
303
|
+
// MAX_THINKING_TOKENS is only used to disable thinking (when set to 0).
|
|
304
|
+
if (options.model && isOpus46OrLater(options.model)) {
|
|
305
|
+
// Convert thinkLevel or thinkingBudget to effort level
|
|
306
|
+
let effortLevel;
|
|
307
|
+
if (options.thinkLevel) {
|
|
308
|
+
effortLevel = thinkLevelToEffortLevel(options.thinkLevel);
|
|
309
|
+
} else if (options.thinkingBudget !== undefined && options.thinkingBudget > 0) {
|
|
310
|
+
effortLevel = thinkingBudgetToEffortLevel(options.thinkingBudget, options.maxBudget);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (effortLevel) {
|
|
314
|
+
env.CLAUDE_CODE_EFFORT_LEVEL = effortLevel;
|
|
315
|
+
}
|
|
243
316
|
}
|
|
317
|
+
|
|
244
318
|
return env;
|
|
245
319
|
};
|
|
246
320
|
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Claude models (Anthropic API)
|
|
9
|
-
// Updated for Opus 4.6 support (Issue #1221)
|
|
9
|
+
// Updated for Opus 4.5/4.6 support (Issue #1221, Issue #1238)
|
|
10
10
|
export const claudeModels = {
|
|
11
11
|
sonnet: 'claude-sonnet-4-5-20250929', // Sonnet 4.5
|
|
12
|
-
opus: 'claude-opus-4-
|
|
12
|
+
opus: 'claude-opus-4-5-20251101', // Opus 4.5 (default, Issue #1238)
|
|
13
13
|
haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
14
14
|
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
15
15
|
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
@@ -15,7 +15,7 @@ import { log } from './lib.mjs';
|
|
|
15
15
|
export const CLAUDE_MODELS = {
|
|
16
16
|
// Short aliases (single word)
|
|
17
17
|
sonnet: 'claude-sonnet-4-5-20250929',
|
|
18
|
-
opus: 'claude-opus-4-
|
|
18
|
+
opus: 'claude-opus-4-5-20251101', // Changed to Opus 4.5 (Issue #1238)
|
|
19
19
|
haiku: 'claude-haiku-4-5-20251001',
|
|
20
20
|
'haiku-3-5': 'claude-3-5-haiku-20241022',
|
|
21
21
|
'haiku-3': 'claude-3-haiku-20240307',
|
|
@@ -25,8 +25,8 @@ export const CLAUDE_MODELS = {
|
|
|
25
25
|
'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias
|
|
26
26
|
'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
|
|
27
27
|
// Opus version aliases (Issue #1221)
|
|
28
|
-
'claude-opus-4-6': 'claude-opus-4-6', //
|
|
29
|
-
'claude-opus-4-5': 'claude-opus-4-5-20251101', //
|
|
28
|
+
'claude-opus-4-6': 'claude-opus-4-6', // Opus 4.6
|
|
29
|
+
'claude-opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5
|
|
30
30
|
// Sonnet version aliases
|
|
31
31
|
'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929',
|
|
32
32
|
// Haiku version aliases
|
|
@@ -39,15 +39,17 @@ export const CLAUDE_MODELS = {
|
|
|
39
39
|
'claude-3-haiku-20240307': 'claude-3-haiku-20240307',
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
// Models that support 1M token context window via [1m] suffix (Issue #1221)
|
|
42
|
+
// Models that support 1M token context window via [1m] suffix (Issue #1221, Issue #1238)
|
|
43
43
|
// See: https://code.claude.com/docs/en/model-config
|
|
44
44
|
export const MODELS_SUPPORTING_1M_CONTEXT = [
|
|
45
45
|
'claude-opus-4-6',
|
|
46
|
+
'claude-opus-4-5-20251101',
|
|
46
47
|
'claude-sonnet-4-5-20250929',
|
|
47
48
|
'claude-sonnet-4-5',
|
|
48
49
|
'sonnet',
|
|
49
50
|
'opus',
|
|
50
51
|
'opus-4-6', // Short alias (Issue #1221 - PR comment feedback)
|
|
52
|
+
'opus-4-5', // Short alias (Issue #1238)
|
|
51
53
|
'sonnet-4-5', // Short alias (Issue #1221 - PR comment feedback)
|
|
52
54
|
];
|
|
53
55
|
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -220,7 +220,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
220
220
|
},
|
|
221
221
|
'thinking-budget': {
|
|
222
222
|
type: 'number',
|
|
223
|
-
description: 'Thinking token budget for Claude Code (0-
|
|
223
|
+
description: 'Thinking token budget for Claude Code (0-31999). Controls MAX_THINKING_TOKENS. Default: 0 (thinking disabled). For older Claude Code versions, translated back to --think level.',
|
|
224
224
|
default: undefined,
|
|
225
225
|
},
|
|
226
226
|
'thinking-budget-claude-minimum-version': {
|
|
@@ -122,10 +122,10 @@ export function registerAcceptInvitesCommand(bot, options) {
|
|
|
122
122
|
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, addBreadcrumb } = options;
|
|
123
123
|
|
|
124
124
|
bot.command(/^accept[_-]?invites$/i, async ctx => {
|
|
125
|
-
VERBOSE && console.log('[VERBOSE] /
|
|
125
|
+
VERBOSE && console.log('[VERBOSE] /accept_invites command received');
|
|
126
126
|
await addBreadcrumb({
|
|
127
127
|
category: 'telegram.command',
|
|
128
|
-
message: '/
|
|
128
|
+
message: '/accept_invites command received',
|
|
129
129
|
level: 'info',
|
|
130
130
|
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
131
131
|
});
|
|
@@ -165,7 +165,7 @@ export function registerAcceptInvitesCommand(bot, options) {
|
|
|
165
165
|
} catch (err) {
|
|
166
166
|
// Ignore "message not modified" errors
|
|
167
167
|
if (!err.message?.includes('message is not modified')) {
|
|
168
|
-
VERBOSE && console.log(`[VERBOSE] /
|
|
168
|
+
VERBOSE && console.log(`[VERBOSE] /accept_invites: Error updating message: ${err.message}`);
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
};
|
|
@@ -228,7 +228,7 @@ export function registerAcceptInvitesCommand(bot, options) {
|
|
|
228
228
|
state.isComplete = true;
|
|
229
229
|
await updateMessage();
|
|
230
230
|
} catch (error) {
|
|
231
|
-
console.error('Error in /
|
|
231
|
+
console.error('Error in /accept_invites:', error);
|
|
232
232
|
const escapedError = escapeMarkdown(error.message);
|
|
233
233
|
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `ā Error fetching invitations: ${escapedError}\n\nMake sure \`gh\` CLI is installed and authenticated\\.`, { parse_mode: 'MarkdownV2' });
|
|
234
234
|
}
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -683,14 +683,15 @@ bot.command('help', async ctx => {
|
|
|
683
683
|
message += '*/hive* - ā Disabled\n\n';
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
+
message += '`/solve_queue` - Show solve queue status\n';
|
|
686
687
|
message += '*/limits* - Show usage limits\n';
|
|
687
688
|
message += '*/version* - Show bot and runtime versions\n';
|
|
688
|
-
message += '
|
|
689
|
+
message += '`/accept_invites` - Accept all pending GitHub invitations\n';
|
|
689
690
|
message += '*/merge* - Merge queue (experimental)\n';
|
|
690
691
|
message += 'Usage: `/merge <github-repo-url>`\n';
|
|
691
692
|
message += "Merges all PRs with 'ready' label sequentially.\n";
|
|
692
693
|
message += '*/help* - Show this help message\n\n';
|
|
693
|
-
message += 'ā ļø *Note:* /solve, /hive, /limits, /version, /accept\\_invites and /merge commands only work in group chats.\n\n';
|
|
694
|
+
message += 'ā ļø *Note:* /solve, /hive, /solve\\_queue, /limits, /version, /accept\\_invites and /merge commands only work in group chats.\n\n';
|
|
694
695
|
message += 'š§ *Common Options:*\n';
|
|
695
696
|
message += '⢠`--model <model>` or `-m` - Specify AI model (sonnet, opus, haiku, haiku-3-5, haiku-3)\n';
|
|
696
697
|
message += '⢠`--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)\n';
|
|
@@ -839,6 +840,19 @@ registerMergeCommand(bot, {
|
|
|
839
840
|
addBreadcrumb,
|
|
840
841
|
});
|
|
841
842
|
|
|
843
|
+
// Register /solve_queue command from separate module (issue #1232)
|
|
844
|
+
const { registerSolveQueueCommand } = await import('./telegram-solve-queue-command.lib.mjs');
|
|
845
|
+
const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, {
|
|
846
|
+
VERBOSE,
|
|
847
|
+
isOldMessage,
|
|
848
|
+
isForwardedOrReply,
|
|
849
|
+
isGroupChat,
|
|
850
|
+
isChatAuthorized,
|
|
851
|
+
addBreadcrumb,
|
|
852
|
+
getSolveQueue,
|
|
853
|
+
getRunningClaudeProcesses,
|
|
854
|
+
});
|
|
855
|
+
|
|
842
856
|
// Named handler for /solve command - extracted for reuse by text-based fallback (issue #1207)
|
|
843
857
|
async function handleSolveCommand(ctx) {
|
|
844
858
|
if (VERBOSE) {
|
|
@@ -1023,7 +1037,7 @@ async function handleSolveCommand(ctx) {
|
|
|
1023
1037
|
const existingItem = solveQueue.findByUrl(normalizedUrl);
|
|
1024
1038
|
if (existingItem) {
|
|
1025
1039
|
const statusText = existingItem.status === 'starting' || existingItem.status === 'started' ? 'being processed' : 'already in the queue';
|
|
1026
|
-
await ctx.reply(`ā This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\nš” Use /
|
|
1040
|
+
await ctx.reply(`ā This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\nš” Use /solve_queue to check the queue status.`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
1027
1041
|
return;
|
|
1028
1042
|
}
|
|
1029
1043
|
|
|
@@ -1261,6 +1275,8 @@ bot.on('message', async (ctx, next) => {
|
|
|
1261
1275
|
const handlers = {
|
|
1262
1276
|
solve: handleSolveCommand,
|
|
1263
1277
|
hive: handleHiveCommand,
|
|
1278
|
+
solve_queue: handleSolveQueueCommand,
|
|
1279
|
+
solvequeue: handleSolveQueueCommand,
|
|
1264
1280
|
};
|
|
1265
1281
|
|
|
1266
1282
|
const handler = handlers[extracted.command];
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram /solve_queue command implementation
|
|
3
|
+
*
|
|
4
|
+
* This module provides the /solve_queue command functionality for the Telegram bot,
|
|
5
|
+
* allowing users to view the current solve queue status.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Shows pending, processing, completed, and failed queue items
|
|
9
|
+
* - Per-tool queue breakdown (claude, opencode, etc.)
|
|
10
|
+
* - Lists currently processing and waiting items
|
|
11
|
+
* - Running Claude process count
|
|
12
|
+
*
|
|
13
|
+
* @see https://github.com/link-assistant/hive-mind/issues/1232
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Registers the /solve_queue command handler with the bot
|
|
18
|
+
* @param {Object} bot - The Telegraf bot instance
|
|
19
|
+
* @param {Object} options - Options object
|
|
20
|
+
* @param {boolean} options.VERBOSE - Whether to enable verbose logging
|
|
21
|
+
* @param {Function} options.isOldMessage - Function to check if message is old
|
|
22
|
+
* @param {Function} options.isForwardedOrReply - Function to check if message is forwarded/reply
|
|
23
|
+
* @param {Function} options.isGroupChat - Function to check if chat is a group
|
|
24
|
+
* @param {Function} options.isChatAuthorized - Function to check if chat is authorized
|
|
25
|
+
* @param {Function} options.addBreadcrumb - Function to add breadcrumbs for monitoring
|
|
26
|
+
* @param {Function} options.getSolveQueue - Function to get the solve queue instance
|
|
27
|
+
* @param {Function} options.getRunningClaudeProcesses - Function to get running claude processes
|
|
28
|
+
* @returns {{ handleSolveQueueCommand: Function }} The command handler for use in text fallback
|
|
29
|
+
*/
|
|
30
|
+
export function registerSolveQueueCommand(bot, options) {
|
|
31
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, addBreadcrumb, getSolveQueue, getRunningClaudeProcesses } = options;
|
|
32
|
+
|
|
33
|
+
async function handleSolveQueueCommand(ctx) {
|
|
34
|
+
VERBOSE && console.log('[VERBOSE] /solve_queue command received');
|
|
35
|
+
|
|
36
|
+
await addBreadcrumb({
|
|
37
|
+
category: 'telegram.command',
|
|
38
|
+
message: '/solve_queue command received',
|
|
39
|
+
level: 'info',
|
|
40
|
+
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Ignore messages sent before bot started
|
|
44
|
+
if (isOldMessage(ctx)) {
|
|
45
|
+
VERBOSE && console.log('[VERBOSE] /solve_queue ignored: old message');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Ignore forwarded or reply messages
|
|
50
|
+
if (isForwardedOrReply(ctx)) {
|
|
51
|
+
VERBOSE && console.log('[VERBOSE] /solve_queue ignored: forwarded or reply');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!isGroupChat(ctx)) {
|
|
56
|
+
VERBOSE && console.log('[VERBOSE] /solve_queue ignored: not a group chat');
|
|
57
|
+
await ctx.reply('ā The /solve_queue command only works in group chats. Please add this bot to a group and make it an admin.', {
|
|
58
|
+
reply_to_message_id: ctx.message.message_id,
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const chatId = ctx.chat.id;
|
|
64
|
+
if (!isChatAuthorized(chatId)) {
|
|
65
|
+
VERBOSE && console.log('[VERBOSE] /solve_queue ignored: chat not authorized');
|
|
66
|
+
await ctx.reply(`ā This chat (ID: ${chatId}) is not authorized to use this bot. Please contact the bot administrator.`, {
|
|
67
|
+
reply_to_message_id: ctx.message.message_id,
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
VERBOSE && console.log('[VERBOSE] /solve_queue passed all checks, generating status...');
|
|
73
|
+
|
|
74
|
+
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
75
|
+
const claudeProcs = await getRunningClaudeProcesses(VERBOSE);
|
|
76
|
+
|
|
77
|
+
// Use the queue's built-in detailed status formatter
|
|
78
|
+
let message = solveQueue.formatDetailedStatus();
|
|
79
|
+
|
|
80
|
+
// Add running Claude processes info
|
|
81
|
+
message += `\nš„ļø Running Claude processes: ${claudeProcs.count}`;
|
|
82
|
+
|
|
83
|
+
await ctx.reply(message, {
|
|
84
|
+
parse_mode: 'Markdown',
|
|
85
|
+
reply_to_message_id: ctx.message.message_id,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Match /solve_queue, /solve-queue, or /solvequeue (case-insensitive)
|
|
90
|
+
// Note: Telegram Bot API only supports underscores in command names, not hyphens.
|
|
91
|
+
// The entity-based matching handles /solve_queue and /solvequeue.
|
|
92
|
+
// /solve-queue is handled by the text-based fallback in telegram-bot.mjs (issue #1232).
|
|
93
|
+
bot.command(/^solve[_-]?queue$/i, handleSolveQueueCommand);
|
|
94
|
+
|
|
95
|
+
return { handleSolveQueueCommand };
|
|
96
|
+
}
|
|
@@ -37,11 +37,12 @@ export const QUEUE_CONFIG = {
|
|
|
37
37
|
RAM_THRESHOLD: 0.65, // Enqueue if RAM usage >= 65%
|
|
38
38
|
// CPU threshold uses 5-minute load average, not instantaneous CPU usage
|
|
39
39
|
CPU_THRESHOLD: 0.65, // Enqueue if 5-minute load average >= 65% of CPU count
|
|
40
|
-
DISK_THRESHOLD: 0.9, // One-at-a-time if disk usage >= 90
|
|
40
|
+
DISK_THRESHOLD: 0.9, // One-at-a-time if disk usage >= 90%, tuned for VM with 100 GB drive
|
|
41
41
|
|
|
42
42
|
// API limit thresholds (usage ratios: 0.0 - 1.0)
|
|
43
43
|
// All thresholds use >= comparison (inclusive)
|
|
44
|
-
|
|
44
|
+
// Fine-tuned for Claude MAX $200 subscription
|
|
45
|
+
CLAUDE_5_HOUR_SESSION_THRESHOLD: 0.65, // One-at-a-time if 5-hour limit >= 65%
|
|
45
46
|
CLAUDE_WEEKLY_THRESHOLD: 0.97, // One-at-a-time if weekly limit >= 97%
|
|
46
47
|
GITHUB_API_THRESHOLD: 0.75, // Enqueue if GitHub >= 75% with parallel claude
|
|
47
48
|
|
|
@@ -92,9 +93,9 @@ export async function getRunningClaudeProcesses(verbose = false) {
|
|
|
92
93
|
.filter(p => p.pid);
|
|
93
94
|
|
|
94
95
|
if (verbose) {
|
|
95
|
-
console.log(`[VERBOSE] /
|
|
96
|
+
console.log(`[VERBOSE] /solve_queue found ${processes.length} running claude processes`);
|
|
96
97
|
if (processes.length > 0) {
|
|
97
|
-
console.log(`[VERBOSE] /
|
|
98
|
+
console.log(`[VERBOSE] /solve_queue processes: ${JSON.stringify(processes)}`);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -104,7 +105,7 @@ export async function getRunningClaudeProcesses(verbose = false) {
|
|
|
104
105
|
};
|
|
105
106
|
} catch (error) {
|
|
106
107
|
if (verbose) {
|
|
107
|
-
console.error('[VERBOSE] /
|
|
108
|
+
console.error('[VERBOSE] /solve_queue error counting claude processes:', error.message);
|
|
108
109
|
}
|
|
109
110
|
return { count: 0, processes: [] };
|
|
110
111
|
}
|
|
@@ -333,7 +334,7 @@ export class SolveQueue {
|
|
|
333
334
|
*/
|
|
334
335
|
log(message) {
|
|
335
336
|
if (this.verbose) {
|
|
336
|
-
console.log(`[VERBOSE] /
|
|
337
|
+
console.log(`[VERBOSE] /solve_queue: ${message}`);
|
|
337
338
|
}
|
|
338
339
|
}
|
|
339
340
|
|
|
@@ -837,7 +838,7 @@ export class SolveQueue {
|
|
|
837
838
|
|
|
838
839
|
this.consumerTask = this.runConsumer();
|
|
839
840
|
this.consumerTask.catch(error => {
|
|
840
|
-
console.error('[
|
|
841
|
+
console.error('[solve_queue] Consumer error:', error);
|
|
841
842
|
this.consumerTask = null;
|
|
842
843
|
});
|
|
843
844
|
}
|
|
@@ -933,7 +934,7 @@ export class SolveQueue {
|
|
|
933
934
|
|
|
934
935
|
// Execute in background
|
|
935
936
|
this.executeItem(item).catch(error => {
|
|
936
|
-
console.error(`[
|
|
937
|
+
console.error(`[solve_queue] Execution error for ${item.id}:`, error);
|
|
937
938
|
});
|
|
938
939
|
}
|
|
939
940
|
}
|
|
@@ -1011,7 +1012,7 @@ export class SolveQueue {
|
|
|
1011
1012
|
} catch (error) {
|
|
1012
1013
|
// Log message edit failures for debugging
|
|
1013
1014
|
// See: https://github.com/link-assistant/hive-mind/issues/1062
|
|
1014
|
-
console.error(`[
|
|
1015
|
+
console.error(`[solve_queue] Failed to update message for item ${item.id}: ${error.message}`);
|
|
1015
1016
|
}
|
|
1016
1017
|
}
|
|
1017
1018
|
}
|
|
@@ -1022,7 +1023,7 @@ export class SolveQueue {
|
|
|
1022
1023
|
} catch (error) {
|
|
1023
1024
|
item.setFailed(error);
|
|
1024
1025
|
this.stats.totalFailed++;
|
|
1025
|
-
console.error(`[
|
|
1026
|
+
console.error(`[solve_queue] Item failed: ${item.id}`, error);
|
|
1026
1027
|
|
|
1027
1028
|
// Try to update message with error
|
|
1028
1029
|
const { chatId, messageId } = item.messageInfo || {};
|
|
@@ -1032,7 +1033,7 @@ export class SolveQueue {
|
|
|
1032
1033
|
} catch (editError) {
|
|
1033
1034
|
// Log the edit failure for debugging
|
|
1034
1035
|
// See: https://github.com/link-assistant/hive-mind/issues/1062
|
|
1035
|
-
console.error(`[
|
|
1036
|
+
console.error(`[solve_queue] Failed to update error message for item ${item.id}: ${editError.message}`);
|
|
1036
1037
|
}
|
|
1037
1038
|
}
|
|
1038
1039
|
} finally {
|