@link-assistant/hive-mind 1.60.0 → 1.62.0
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 +4 -2
- package/README.md +5 -2
- package/README.ru.md +4 -2
- package/README.zh.md +17 -15
- package/package.json +1 -1
- package/src/claude.prompts.lib.mjs +2 -2
- package/src/codex.prompts.lib.mjs +2 -2
- package/src/config.lib.mjs +1 -0
- package/src/hive.config.lib.mjs +1 -1
- package/src/hive.mjs +7 -7
- package/src/models/index.mjs +46 -11
- package/src/qwen.lib.mjs +589 -0
- package/src/qwen.prompts.lib.mjs +253 -0
- package/src/solve.config.lib.mjs +5 -5
- package/src/solve.mjs +34 -0
- package/src/solve.restart-shared.lib.mjs +44 -2
- package/src/solve.validation.lib.mjs +8 -0
- package/src/task.issue-creation.lib.mjs +203 -0
- package/src/task.mjs +1 -1
- package/src/telegram-bot.mjs +8 -5
- package/src/telegram-solve-command.lib.mjs +1 -0
- package/src/telegram-solve-queue.helpers.lib.mjs +11 -0
- package/src/telegram-solve-queue.lib.mjs +16 -3
- package/src/telegram-task-command.lib.mjs +61 -3
- package/src/tool-connection-validation.lib.mjs +16 -0
- package/src/version-info.lib.mjs +1 -1
package/src/telegram-bot.mjs
CHANGED
|
@@ -525,7 +525,7 @@ bot.command('help', async ctx => {
|
|
|
525
525
|
message += '📝 *Available Commands:*\n\n';
|
|
526
526
|
|
|
527
527
|
if (solveEnabled) {
|
|
528
|
-
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*) - Solve a GitHub issue\n';
|
|
528
|
+
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */qwen*) - Solve a GitHub issue\n';
|
|
529
529
|
message += 'Usage: `/solve <github-url> [options]`\n';
|
|
530
530
|
message += 'Example: `/solve https://github.com/owner/repo/issues/123 --model sonnet`\n';
|
|
531
531
|
message += 'Tool aliases imply `--tool <tool>`: `/codex <github-url>` equals `/solve <github-url> --tool codex`\n';
|
|
@@ -535,12 +535,15 @@ bot.command('help', async ctx => {
|
|
|
535
535
|
}
|
|
536
536
|
message += '\n';
|
|
537
537
|
} else {
|
|
538
|
-
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*) - ❌ Disabled\n\n';
|
|
538
|
+
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */qwen*) - ❌ Disabled\n\n';
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
if (taskEnabled) {
|
|
542
|
-
message += '*/task*
|
|
543
|
-
message += 'Usage: `/
|
|
542
|
+
message += '*/task* - Create a GitHub issue from a repository link and issue text\n';
|
|
543
|
+
message += 'Usage: `/task <github-repository-url>` followed by issue text, or reply with `/task`\n';
|
|
544
|
+
message += 'Example: `/task https://github.com/owner/repo` then the issue text on following lines\n';
|
|
545
|
+
message += '*/split* - Split a GitHub issue into smaller issues\n';
|
|
546
|
+
message += 'Usage: `/split <github-issue-url> [options]` or `/task --split <github-issue-url>`\n';
|
|
544
547
|
message += 'Example: `/split https://github.com/owner/repo/issues/123 --split-count 2`\n\n';
|
|
545
548
|
} else {
|
|
546
549
|
message += '*/task* / */split* - ❌ Disabled\n\n';
|
|
@@ -573,7 +576,7 @@ bot.command('help', async ctx => {
|
|
|
573
576
|
message += '🔔 *Session Notifications:* Completion notifications are automatic; use /subscribe for private DM forwards.\n';
|
|
574
577
|
if (ISOLATION_BACKEND) message += `🔒 *Isolation Mode:* \`${ISOLATION_BACKEND}\` (experimental)\n`;
|
|
575
578
|
message += '\n';
|
|
576
|
-
message += '⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /subscribe and /unsubscribe work in private and group chats.\n\n';
|
|
579
|
+
message += '⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /subscribe and /unsubscribe work in private and group chats.\n\n';
|
|
577
580
|
message += '🔧 *Common Options:*\n';
|
|
578
581
|
message += `• \`--model <model>\` or \`-m\` - ${buildModelOptionDescription()}\n`;
|
|
579
582
|
message += '• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)\n';
|
|
@@ -13,6 +13,7 @@ export const TOOL_SOLVE_COMMAND_ALIASES = Object.freeze({
|
|
|
13
13
|
codex: 'codex',
|
|
14
14
|
opencode: 'opencode',
|
|
15
15
|
agent: 'agent',
|
|
16
|
+
qwen: 'qwen',
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
export const SOLVE_COMMAND_NAMES = Object.freeze(['solve', 'do', 'continue', ...Object.keys(TOOL_SOLVE_COMMAND_ALIASES)]);
|
|
@@ -75,6 +75,15 @@ export async function getRunningCodexProcesses(verbose = false) {
|
|
|
75
75
|
return getRunningProcesses('codex', verbose);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Count running qwen processes.
|
|
80
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
81
|
+
* @returns {Promise<{count: number, processes: string[]}>}
|
|
82
|
+
*/
|
|
83
|
+
export async function getRunningQwenProcesses(verbose = false) {
|
|
84
|
+
return getRunningProcesses('qwen', verbose);
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
/**
|
|
79
88
|
* Format a threshold as percentage for display.
|
|
80
89
|
* @param {number} ratio - Ratio (0.0 - 1.0)
|
|
@@ -145,6 +154,8 @@ export function formatWaitingReason(metric, currentValue, threshold) {
|
|
|
145
154
|
return 'Claude process is already running';
|
|
146
155
|
case 'codex_running':
|
|
147
156
|
return 'Codex process is already running';
|
|
157
|
+
case 'qwen_running':
|
|
158
|
+
return 'Qwen Code process is already running';
|
|
148
159
|
default:
|
|
149
160
|
return `${metric} threshold exceeded`;
|
|
150
161
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { getCachedClaudeLimits, getCachedCodexLimits, getCachedGitHubLimits, getCachedMemoryInfo, getCachedCpuInfo, getCachedDiskInfo, getLimitCache } from './limits.lib.mjs';
|
|
19
|
-
export { formatDuration, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
19
|
+
export { formatDuration, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningProcesses, getRunningQwenProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
20
20
|
import { formatDuration, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
21
21
|
export { QUEUE_CONFIG, THRESHOLD_STRATEGIES } from './queue-config.lib.mjs';
|
|
22
22
|
import { QUEUE_CONFIG } from './queue-config.lib.mjs';
|
|
@@ -149,6 +149,7 @@ export class SolveQueue {
|
|
|
149
149
|
claude: [],
|
|
150
150
|
agent: [],
|
|
151
151
|
codex: [],
|
|
152
|
+
qwen: [],
|
|
152
153
|
};
|
|
153
154
|
this.processing = new Map();
|
|
154
155
|
this.completed = [];
|
|
@@ -160,6 +161,7 @@ export class SolveQueue {
|
|
|
160
161
|
claude: null,
|
|
161
162
|
agent: null,
|
|
162
163
|
codex: null,
|
|
164
|
+
qwen: null,
|
|
163
165
|
};
|
|
164
166
|
// Legacy: keep for compatibility with existing code that uses lastStartTime
|
|
165
167
|
this.lastStartTime = null;
|
|
@@ -561,8 +563,10 @@ export class SolveQueue {
|
|
|
561
563
|
const claudeProcessCount = externalProcessing.byTool.claude || 0;
|
|
562
564
|
const codexProcessCount = externalProcessing.byTool.codex || 0;
|
|
563
565
|
const agentProcessCount = externalProcessing.byTool.agent || 0;
|
|
566
|
+
const qwenProcessCount = externalProcessing.byTool.qwen || 0;
|
|
564
567
|
const hasRunningClaude = claudeProcessCount > 0;
|
|
565
568
|
const hasRunningCodex = codexProcessCount > 0;
|
|
569
|
+
const hasRunningQwen = qwenProcessCount > 0;
|
|
566
570
|
|
|
567
571
|
// Calculate total processing count for system resources (all tools)
|
|
568
572
|
// System resources (RAM, CPU, disk) apply to all tools
|
|
@@ -574,6 +578,7 @@ export class SolveQueue {
|
|
|
574
578
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
575
579
|
const claudeProcessingCount = this.getProcessingCountByTool('claude');
|
|
576
580
|
const codexProcessingCount = this.getProcessingCountByTool('codex');
|
|
581
|
+
const qwenProcessingCount = this.getProcessingCountByTool('qwen');
|
|
577
582
|
|
|
578
583
|
// Track claude_running as a metric (but don't add to reasons yet)
|
|
579
584
|
if (hasRunningClaude) {
|
|
@@ -582,6 +587,9 @@ export class SolveQueue {
|
|
|
582
587
|
if (hasRunningCodex) {
|
|
583
588
|
this.recordThrottle('codex_running');
|
|
584
589
|
}
|
|
590
|
+
if (hasRunningQwen) {
|
|
591
|
+
this.recordThrottle('qwen_running');
|
|
592
|
+
}
|
|
585
593
|
|
|
586
594
|
// Check system resources with strategy support
|
|
587
595
|
// System resources apply to ALL tools, not just Claude
|
|
@@ -604,8 +612,8 @@ export class SolveQueue {
|
|
|
604
612
|
// This allows agent tasks to proceed when Claude limits are reached
|
|
605
613
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
606
614
|
// See: https://github.com/link-assistant/hive-mind/issues/1253 (strategies)
|
|
607
|
-
const hasRunningToolProcess = tool === 'codex' ? hasRunningCodex : hasRunningClaude;
|
|
608
|
-
const toolProcessingCount = tool === 'codex' ? codexProcessingCount : claudeProcessingCount;
|
|
615
|
+
const hasRunningToolProcess = tool === 'codex' ? hasRunningCodex : tool === 'qwen' ? hasRunningQwen : hasRunningClaude;
|
|
616
|
+
const toolProcessingCount = tool === 'codex' ? codexProcessingCount : tool === 'qwen' ? qwenProcessingCount : claudeProcessingCount;
|
|
609
617
|
const limitCheck = await this.checkApiLimits(hasRunningToolProcess, toolProcessingCount, tool);
|
|
610
618
|
if (limitCheck.rejected) {
|
|
611
619
|
rejected = true;
|
|
@@ -629,6 +637,9 @@ export class SolveQueue {
|
|
|
629
637
|
if (tool === 'codex' && hasRunningCodex && reasons.length > 0) {
|
|
630
638
|
reasons.push(formatWaitingReason('codex_running', codexProcessCount, 0) + ` (${codexProcessCount} processes)`);
|
|
631
639
|
}
|
|
640
|
+
if (tool === 'qwen' && hasRunningQwen && reasons.length > 0) {
|
|
641
|
+
reasons.push(formatWaitingReason('qwen_running', qwenProcessCount, 0) + ` (${qwenProcessCount} processes)`);
|
|
642
|
+
}
|
|
632
643
|
|
|
633
644
|
const canStart = reasons.length === 0 && !rejected;
|
|
634
645
|
|
|
@@ -650,10 +661,12 @@ export class SolveQueue {
|
|
|
650
661
|
claudeProcesses: claudeProcessCount,
|
|
651
662
|
codexProcesses: codexProcessCount,
|
|
652
663
|
agentProcesses: agentProcessCount,
|
|
664
|
+
qwenProcesses: qwenProcessCount,
|
|
653
665
|
isolatedProcesses: externalProcessing.isolatedTotal,
|
|
654
666
|
totalProcessing,
|
|
655
667
|
claudeProcessingCount,
|
|
656
668
|
codexProcessingCount,
|
|
669
|
+
qwenProcessingCount,
|
|
657
670
|
};
|
|
658
671
|
}
|
|
659
672
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildUserMention } from './buildUserMention.lib.mjs';
|
|
2
2
|
import { validateModelName } from './models/index.mjs';
|
|
3
|
+
import { createTaskIssue, parseTaskIssueCreationInput, resolveTaskIssueCreationInput } from './task.issue-creation.lib.mjs';
|
|
3
4
|
import { parseTaskIssueUrl } from './task.split.lib.mjs';
|
|
4
5
|
import { escapeMarkdown } from './telegram-markdown.lib.mjs';
|
|
5
6
|
import { extractIsolationFromArgs, isValidPerCommandIsolation } from './telegram-isolation.lib.mjs';
|
|
@@ -16,7 +17,12 @@ export function getTaskCommandNameFromText(text) {
|
|
|
16
17
|
return TASK_COMMAND_NAMES.includes(command) ? command : null;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
export function
|
|
20
|
+
export function hasTaskSplitFlag(args) {
|
|
21
|
+
return args.includes('--split') || args.some(arg => arg.startsWith('--split='));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function applyTaskCommandDefaults(args, commandName = 'task') {
|
|
25
|
+
if (commandName !== 'split') return args;
|
|
20
26
|
const hasSplit = args.includes('--split') || args.some(arg => arg.startsWith('--split='));
|
|
21
27
|
return hasSplit ? args : [...args, '--split'];
|
|
22
28
|
}
|
|
@@ -49,7 +55,8 @@ function validateTaskModel(args) {
|
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export function buildTaskCommandArgs(text) {
|
|
52
|
-
const
|
|
58
|
+
const commandName = getTaskCommandNameFromText(text) || 'task';
|
|
59
|
+
const args = applyTaskCommandDefaults(parseCommandArgs(text), commandName);
|
|
53
60
|
const issueUrl = findTaskIssueUrl(args);
|
|
54
61
|
return {
|
|
55
62
|
args: issueUrl ? moveArgumentToFront(args, issueUrl) : args,
|
|
@@ -57,8 +64,26 @@ export function buildTaskCommandArgs(text) {
|
|
|
57
64
|
};
|
|
58
65
|
}
|
|
59
66
|
|
|
67
|
+
function getReplyText(message) {
|
|
68
|
+
const reply = message?.reply_to_message;
|
|
69
|
+
if (!reply || reply.forum_topic_created) return '';
|
|
70
|
+
return reply.text || reply.caption || '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildTaskIssueCreationUsage(commandDisplay) {
|
|
74
|
+
return [`Usage: ${commandDisplay} <github-repository-url> followed by issue text.`, '', `Or reply to a message containing a repository URL and issue text with \`${commandDisplay}\`.`, '', 'To split an existing issue, use `/split <github-issue-url>` or `/task --split <github-issue-url>`.'].join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function editTelegramMessage(ctx, message, text) {
|
|
78
|
+
try {
|
|
79
|
+
await ctx.telegram.editMessageText(message.chat.id, message.message_id, undefined, text, { disable_web_page_preview: true });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(`[telegram-task-command] Failed to edit status message: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
60
85
|
export function registerTaskCommands(bot, options) {
|
|
61
|
-
const { VERBOSE, taskEnabled, addBreadcrumb, isOldMessage, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage } = options;
|
|
86
|
+
const { VERBOSE, taskEnabled, addBreadcrumb, isOldMessage, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage, createTaskIssue: createTaskIssueFn = createTaskIssue } = options;
|
|
62
87
|
|
|
63
88
|
async function handleTaskCommand(ctx) {
|
|
64
89
|
const commandName = getTaskCommandNameFromText(ctx.message?.text) || 'task';
|
|
@@ -90,6 +115,39 @@ export function registerTaskCommands(bot, options) {
|
|
|
90
115
|
return;
|
|
91
116
|
}
|
|
92
117
|
|
|
118
|
+
const parsedArgs = parseCommandArgs(ctx.message.text);
|
|
119
|
+
const splitMode = commandName === 'split' || hasTaskSplitFlag(parsedArgs);
|
|
120
|
+
|
|
121
|
+
if (!splitMode) {
|
|
122
|
+
const creationInput = resolveTaskIssueCreationInput({
|
|
123
|
+
commandText: ctx.message.text,
|
|
124
|
+
replyText: getReplyText(ctx.message),
|
|
125
|
+
});
|
|
126
|
+
const creation = parseTaskIssueCreationInput(creationInput);
|
|
127
|
+
|
|
128
|
+
if (!creation.valid) {
|
|
129
|
+
await safeReply(ctx, `❌ ${escapeMarkdown(creation.error)}\n\n${buildTaskIssueCreationUsage(commandDisplay)}`, { reply_to_message_id: ctx.message.message_id });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const statusMessage = await ctx.reply(`Creating GitHub issue in ${creation.repository.fullName}...`, {
|
|
134
|
+
reply_to_message_id: ctx.message.message_id,
|
|
135
|
+
disable_web_page_preview: true,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const createdIssue = await createTaskIssueFn({
|
|
140
|
+
repository: creation.repository,
|
|
141
|
+
title: creation.title,
|
|
142
|
+
body: creation.issueText,
|
|
143
|
+
});
|
|
144
|
+
await editTelegramMessage(ctx, statusMessage, `Created GitHub issue:\n${createdIssue.url}\n\nReply to this message with /solve to start a solution.`);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
await editTelegramMessage(ctx, statusMessage, `Error creating GitHub issue:\n${error.message || String(error)}`);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
93
151
|
const built = buildTaskCommandArgs(ctx.message.text);
|
|
94
152
|
if (!built.issueUrl) {
|
|
95
153
|
await safeReply(ctx, `❌ Missing GitHub issue URL. Usage: \`${commandDisplay} <github-issue-url> [options]\`\n\nExample: \`${commandDisplay} https://github.com/owner/repo/issues/123\``, { reply_to_message_id: ctx.message.message_id });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const validateToolConnection = async ({ tool = 'claude', model, verbose = false, validateClaudeConnection } = {}) => {
|
|
2
|
+
if (tool === 'opencode') {
|
|
3
|
+
return (await import('./opencode.lib.mjs')).validateOpenCodeConnection(model);
|
|
4
|
+
}
|
|
5
|
+
if (tool === 'codex') {
|
|
6
|
+
return (await import('./codex.lib.mjs')).validateCodexConnection(model, verbose);
|
|
7
|
+
}
|
|
8
|
+
if (tool === 'agent') {
|
|
9
|
+
return (await import('./agent.lib.mjs')).validateAgentConnection(model);
|
|
10
|
+
}
|
|
11
|
+
if (tool === 'qwen') {
|
|
12
|
+
return (await import('./qwen.lib.mjs')).validateQwenConnection(model);
|
|
13
|
+
}
|
|
14
|
+
const validateClaude = validateClaudeConnection || (await import('./claude.lib.mjs')).validateClaudeConnection;
|
|
15
|
+
return validateClaude(model);
|
|
16
|
+
};
|
package/src/version-info.lib.mjs
CHANGED
|
@@ -554,7 +554,7 @@ const VERSION_COMMANDS = [
|
|
|
554
554
|
{ key: 'agent', command: 'agent --version 2>&1' },
|
|
555
555
|
{ key: 'codex', command: 'codex --version 2>&1' },
|
|
556
556
|
{ key: 'opencode', command: 'opencode --version 2>&1' },
|
|
557
|
-
{ key: 'qwenCode', command: 'qwen
|
|
557
|
+
{ key: 'qwenCode', command: 'qwen --version 2>&1' },
|
|
558
558
|
{ key: 'gemini', command: 'gemini --version 2>&1' },
|
|
559
559
|
{ key: 'copilot', command: 'copilot --version 2>&1' },
|
|
560
560
|
|