@link-assistant/hive-mind 1.62.1 → 1.64.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 +5 -2
- package/README.md +20 -15
- package/README.ru.md +5 -2
- package/README.zh.md +18 -15
- package/package.json +2 -2
- package/src/agent-commander.lib.mjs +361 -0
- package/src/bidirectional-interactive.lib.mjs +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/gemini.lib.mjs +611 -0
- package/src/gemini.prompts.lib.mjs +236 -0
- package/src/hive.config.lib.mjs +1 -1
- package/src/interactive-mode.lib.mjs +1 -1
- package/src/models/index.mjs +58 -11
- package/src/qwen.prompts.lib.mjs +2 -2
- package/src/solve.config.lib.mjs +11 -5
- package/src/solve.mjs +39 -74
- package/src/solve.restart-shared.lib.mjs +75 -2
- package/src/solve.results.lib.mjs +1 -1
- package/src/solve.validation.lib.mjs +20 -1
- package/src/task.config.lib.mjs +1 -1
- package/src/task.mjs +1 -1
- package/src/telegram-bot.mjs +4 -4
- package/src/telegram-solve-command.lib.mjs +1 -0
- package/src/telegram-solve-queue-command.lib.mjs +1 -1
- package/src/telegram-solve-queue.helpers.lib.mjs +12 -1
- package/src/telegram-solve-queue.lib.mjs +37 -20
- package/src/tool-connection-validation.lib.mjs +3 -0
- package/src/usage-limit.lib.mjs +1 -1
package/src/solve.mjs
CHANGED
|
@@ -89,10 +89,18 @@ setupStdioLogInterceptor(); // Issue #1549: capture ALL terminal output in log f
|
|
|
89
89
|
|
|
90
90
|
// Early logs go to cwd; custom log dir takes effect after argv is parsed
|
|
91
91
|
// Conditionally import tool-specific functions after argv is parsed
|
|
92
|
+
// If --use-agent-commander is enabled, use agent-commander's checkForUncommittedChanges
|
|
92
93
|
let checkForUncommittedChanges;
|
|
93
|
-
|
|
94
|
+
let agentCommanderLib = null;
|
|
95
|
+
if (argv.useAgentCommander) {
|
|
96
|
+
agentCommanderLib = await import('./agent-commander.lib.mjs');
|
|
97
|
+
checkForUncommittedChanges = agentCommanderLib.checkForUncommittedChanges;
|
|
98
|
+
} else if (argv.tool === 'opencode') {
|
|
94
99
|
const opencodeLib = await import('./opencode.lib.mjs');
|
|
95
100
|
checkForUncommittedChanges = opencodeLib.checkForUncommittedChanges;
|
|
101
|
+
} else if (argv.tool === 'gemini') {
|
|
102
|
+
const geminiLib = await import('./gemini.lib.mjs');
|
|
103
|
+
checkForUncommittedChanges = geminiLib.checkForUncommittedChanges;
|
|
96
104
|
} else if (argv.tool === 'codex') {
|
|
97
105
|
const codexLib = await import('./codex.lib.mjs');
|
|
98
106
|
checkForUncommittedChanges = codexLib.checkForUncommittedChanges;
|
|
@@ -664,73 +672,26 @@ try {
|
|
|
664
672
|
|
|
665
673
|
// Execute tool command with all prompts and settings
|
|
666
674
|
let toolResult;
|
|
667
|
-
if (argv.tool === 'opencode') {
|
|
668
|
-
const opencodeLib = await import('./opencode.lib.mjs');
|
|
669
|
-
const { executeOpenCode, checkPlaywrightMcpAvailability: checkOpenCodePlaywrightMcp } = opencodeLib;
|
|
670
|
-
const opencodePath = process.env.OPENCODE_PATH || 'opencode';
|
|
671
|
-
await resolvePlaywrightMcp(checkOpenCodePlaywrightMcp);
|
|
672
675
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
tempDir,
|
|
680
|
-
workspaceTmpDir,
|
|
681
|
-
isContinueMode,
|
|
682
|
-
mergeStateStatus,
|
|
683
|
-
forkedRepo,
|
|
684
|
-
feedbackLines,
|
|
685
|
-
forkActionsUrl,
|
|
686
|
-
owner,
|
|
687
|
-
repo,
|
|
688
|
-
argv,
|
|
689
|
-
log,
|
|
690
|
-
setLogFile,
|
|
691
|
-
getLogFile,
|
|
692
|
-
formatAligned,
|
|
693
|
-
getResourceSnapshot,
|
|
694
|
-
opencodePath,
|
|
695
|
-
$,
|
|
696
|
-
});
|
|
697
|
-
} else if (argv.tool === 'codex') {
|
|
698
|
-
const codexLib = await import('./codex.lib.mjs');
|
|
699
|
-
const { executeCodex, checkPlaywrightMcpAvailability } = codexLib;
|
|
700
|
-
const codexPath = process.env.CODEX_PATH || 'codex';
|
|
701
|
-
await resolvePlaywrightMcp(checkPlaywrightMcpAvailability);
|
|
676
|
+
// If --use-agent-commander is enabled, use agent-commander for all tools
|
|
677
|
+
if (argv.useAgentCommander) {
|
|
678
|
+
// Ensure agent-commander is available
|
|
679
|
+
if (!agentCommanderLib) {
|
|
680
|
+
agentCommanderLib = await import('./agent-commander.lib.mjs');
|
|
681
|
+
}
|
|
702
682
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
forkedRepo,
|
|
714
|
-
feedbackLines,
|
|
715
|
-
forkActionsUrl,
|
|
716
|
-
owner,
|
|
717
|
-
repo,
|
|
718
|
-
argv,
|
|
719
|
-
log,
|
|
720
|
-
setLogFile,
|
|
721
|
-
getLogFile,
|
|
722
|
-
formatAligned,
|
|
723
|
-
getResourceSnapshot,
|
|
724
|
-
codexPath,
|
|
725
|
-
$,
|
|
726
|
-
});
|
|
727
|
-
} else if (argv.tool === 'agent') {
|
|
728
|
-
const agentLib = await import('./agent.lib.mjs');
|
|
729
|
-
const { executeAgent, checkPlaywrightMcpAvailability: checkAgentPlaywrightMcp } = agentLib;
|
|
730
|
-
const agentPath = process.env.AGENT_PATH || 'agent';
|
|
731
|
-
await resolvePlaywrightMcp(checkAgentPlaywrightMcp);
|
|
683
|
+
const isAvailable = await agentCommanderLib.isAgentCommanderAvailable();
|
|
684
|
+
if (!isAvailable) {
|
|
685
|
+
await log('\n[agent-commander] agent-commander is not installed.', { level: 'error' });
|
|
686
|
+
await log(' Install it with: npm install agent-commander', { level: 'error' });
|
|
687
|
+
await log(' Or remove the --use-agent-commander flag to use embedded tool logic.', { level: 'error' });
|
|
688
|
+
await safeExit(1, 'agent-commander not available');
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
await log(`\n[agent-commander] Using agent-commander for ${argv.tool || 'claude'} execution`);
|
|
692
|
+
await agentCommanderLib.resolvePlaywrightMcpForAgentCommander({ argv, log, tool: argv.tool || 'claude' });
|
|
732
693
|
|
|
733
|
-
toolResult = await
|
|
694
|
+
toolResult = await agentCommanderLib.executeWithAgentCommander({
|
|
734
695
|
issueUrl,
|
|
735
696
|
issueNumber,
|
|
736
697
|
prNumber,
|
|
@@ -751,16 +712,20 @@ try {
|
|
|
751
712
|
getLogFile,
|
|
752
713
|
formatAligned,
|
|
753
714
|
getResourceSnapshot,
|
|
754
|
-
agentPath,
|
|
755
715
|
$,
|
|
756
716
|
});
|
|
757
|
-
} else if (
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
717
|
+
} else if (['opencode', 'codex', 'agent', 'gemini', 'qwen'].includes(argv.tool)) {
|
|
718
|
+
const toolDispatch = {
|
|
719
|
+
opencode: { lib: './opencode.lib.mjs', execFn: 'executeOpenCode', envVar: 'OPENCODE_PATH', defaultBin: 'opencode', pathKey: 'opencodePath' },
|
|
720
|
+
codex: { lib: './codex.lib.mjs', execFn: 'executeCodex', envVar: 'CODEX_PATH', defaultBin: 'codex', pathKey: 'codexPath' },
|
|
721
|
+
agent: { lib: './agent.lib.mjs', execFn: 'executeAgent', envVar: 'AGENT_PATH', defaultBin: 'agent', pathKey: 'agentPath' },
|
|
722
|
+
gemini: { lib: './gemini.lib.mjs', execFn: 'executeGemini', envVar: 'GEMINI_PATH', defaultBin: 'gemini', pathKey: 'geminiPath' },
|
|
723
|
+
qwen: { lib: './qwen.lib.mjs', execFn: 'executeQwen', envVar: 'QWEN_PATH', defaultBin: 'qwen', pathKey: 'qwenPath' },
|
|
724
|
+
}[argv.tool];
|
|
725
|
+
const toolLib = await import(toolDispatch.lib);
|
|
726
|
+
await resolvePlaywrightMcp(toolLib.checkPlaywrightMcpAvailability);
|
|
727
|
+
|
|
728
|
+
toolResult = await toolLib[toolDispatch.execFn]({
|
|
764
729
|
issueUrl,
|
|
765
730
|
issueNumber,
|
|
766
731
|
prNumber,
|
|
@@ -781,7 +746,7 @@ try {
|
|
|
781
746
|
getLogFile,
|
|
782
747
|
formatAligned,
|
|
783
748
|
getResourceSnapshot,
|
|
784
|
-
|
|
749
|
+
[toolDispatch.pathKey]: process.env[toolDispatch.envVar] || toolDispatch.defaultBin,
|
|
785
750
|
$,
|
|
786
751
|
});
|
|
787
752
|
} else {
|
|
@@ -167,7 +167,7 @@ export const getUncommittedChangesDetails = async tempDir => {
|
|
|
167
167
|
};
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
* Execute the AI tool (Claude, OpenCode, Codex, Agent, Qwen) for a restart iteration
|
|
170
|
+
* Execute the AI tool (Claude, OpenCode, Codex, Agent, Gemini, Qwen) for a restart iteration
|
|
171
171
|
* This is the shared tool execution logic used by both watch mode and auto-restart-until-mergeable mode
|
|
172
172
|
* @param {Object} params - Execution parameters
|
|
173
173
|
* @returns {Promise<Object>} - Tool execution result
|
|
@@ -183,7 +183,34 @@ export const executeToolIteration = async params => {
|
|
|
183
183
|
await cascadePlaywrightMcpDisable(argv, log);
|
|
184
184
|
|
|
185
185
|
let toolResult;
|
|
186
|
-
if (argv.
|
|
186
|
+
if (argv.useAgentCommander) {
|
|
187
|
+
const agentCommanderLib = await import('./agent-commander.lib.mjs');
|
|
188
|
+
await agentCommanderLib.resolvePlaywrightMcpForAgentCommander({ argv, log, tool: argv.tool || 'claude' });
|
|
189
|
+
|
|
190
|
+
toolResult = await agentCommanderLib.executeWithAgentCommander({
|
|
191
|
+
issueUrl,
|
|
192
|
+
issueNumber,
|
|
193
|
+
prNumber,
|
|
194
|
+
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
195
|
+
branchName,
|
|
196
|
+
tempDir,
|
|
197
|
+
workspaceTmpDir: params.workspaceTmpDir,
|
|
198
|
+
isContinueMode: true,
|
|
199
|
+
mergeStateStatus,
|
|
200
|
+
forkedRepo: argv.fork,
|
|
201
|
+
feedbackLines,
|
|
202
|
+
forkActionsUrl: null,
|
|
203
|
+
owner,
|
|
204
|
+
repo,
|
|
205
|
+
argv,
|
|
206
|
+
log,
|
|
207
|
+
formatAligned,
|
|
208
|
+
getResourceSnapshot,
|
|
209
|
+
setLogFile: () => {},
|
|
210
|
+
getLogFile: () => '',
|
|
211
|
+
$,
|
|
212
|
+
});
|
|
213
|
+
} else if (argv.tool === 'opencode') {
|
|
187
214
|
// Use OpenCode
|
|
188
215
|
const opencodeExecLib = await import('./opencode.lib.mjs');
|
|
189
216
|
const { executeOpenCode, checkPlaywrightMcpAvailability } = opencodeExecLib;
|
|
@@ -208,6 +235,7 @@ export const executeToolIteration = async params => {
|
|
|
208
235
|
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
209
236
|
branchName,
|
|
210
237
|
tempDir,
|
|
238
|
+
workspaceTmpDir,
|
|
211
239
|
isContinueMode: true,
|
|
212
240
|
mergeStateStatus,
|
|
213
241
|
forkedRepo: argv.fork,
|
|
@@ -246,6 +274,7 @@ export const executeToolIteration = async params => {
|
|
|
246
274
|
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
247
275
|
branchName,
|
|
248
276
|
tempDir,
|
|
277
|
+
workspaceTmpDir,
|
|
249
278
|
isContinueMode: true,
|
|
250
279
|
mergeStateStatus,
|
|
251
280
|
forkedRepo: argv.fork,
|
|
@@ -287,6 +316,7 @@ export const executeToolIteration = async params => {
|
|
|
287
316
|
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
288
317
|
branchName,
|
|
289
318
|
tempDir,
|
|
319
|
+
workspaceTmpDir,
|
|
290
320
|
isContinueMode: true,
|
|
291
321
|
mergeStateStatus,
|
|
292
322
|
forkedRepo: argv.fork,
|
|
@@ -301,6 +331,48 @@ export const executeToolIteration = async params => {
|
|
|
301
331
|
agentPath,
|
|
302
332
|
$,
|
|
303
333
|
});
|
|
334
|
+
} else if (argv.tool === 'gemini') {
|
|
335
|
+
// Use Gemini
|
|
336
|
+
const geminiExecLib = await import('./gemini.lib.mjs');
|
|
337
|
+
const { executeGemini, checkPlaywrightMcpAvailability } = geminiExecLib;
|
|
338
|
+
const geminiPath = argv.geminiPath || 'gemini';
|
|
339
|
+
|
|
340
|
+
if (argv.promptPlaywrightMcp) {
|
|
341
|
+
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
342
|
+
if (playwrightMcpAvailable) {
|
|
343
|
+
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
344
|
+
} else {
|
|
345
|
+
await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
|
|
346
|
+
argv.promptPlaywrightMcp = false;
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
toolResult = await executeGemini({
|
|
353
|
+
issueUrl,
|
|
354
|
+
issueNumber,
|
|
355
|
+
prNumber,
|
|
356
|
+
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
357
|
+
branchName,
|
|
358
|
+
tempDir,
|
|
359
|
+
workspaceTmpDir,
|
|
360
|
+
isContinueMode: true,
|
|
361
|
+
mergeStateStatus,
|
|
362
|
+
forkedRepo: argv.fork,
|
|
363
|
+
feedbackLines,
|
|
364
|
+
forkActionsUrl: null,
|
|
365
|
+
owner,
|
|
366
|
+
repo,
|
|
367
|
+
argv,
|
|
368
|
+
log,
|
|
369
|
+
setLogFile: () => {},
|
|
370
|
+
getLogFile: () => '',
|
|
371
|
+
formatAligned,
|
|
372
|
+
getResourceSnapshot,
|
|
373
|
+
geminiPath,
|
|
374
|
+
$,
|
|
375
|
+
});
|
|
304
376
|
} else if (argv.tool === 'qwen') {
|
|
305
377
|
// Use Qwen Code
|
|
306
378
|
const qwenExecLib = await import('./qwen.lib.mjs');
|
|
@@ -371,6 +443,7 @@ export const executeToolIteration = async params => {
|
|
|
371
443
|
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
372
444
|
branchName,
|
|
373
445
|
tempDir,
|
|
446
|
+
workspaceTmpDir,
|
|
374
447
|
isContinueMode: true,
|
|
375
448
|
mergeStateStatus,
|
|
376
449
|
forkedRepo: argv.fork,
|
|
@@ -46,7 +46,7 @@ export const { buildClaudeResumeCommand, buildClaudeInitialCommand } = claudeCom
|
|
|
46
46
|
* @param {Object} options
|
|
47
47
|
* @param {string} options.issueUrl - The issue URL passed to solve.mjs
|
|
48
48
|
* @param {string} options.sessionId - The session ID to resume
|
|
49
|
-
* @param {string|null} [options.tool] - Tool name (codex, opencode, agent)
|
|
49
|
+
* @param {string|null} [options.tool] - Tool name (codex, opencode, agent, gemini)
|
|
50
50
|
* @param {string|null} [options.model] - Model name to preserve
|
|
51
51
|
* @param {string|null} [options.fallbackModel] - Explicit fallback model to preserve
|
|
52
52
|
* @param {string|null} [options.tempDir] - Working directory to preserve
|
|
@@ -295,7 +295,18 @@ export const performSystemChecks = async (minDiskSpace = 2048, skipToolConnectio
|
|
|
295
295
|
// Skip tool connection validation if in dry-run mode or explicitly requested
|
|
296
296
|
if (!skipToolConnection) {
|
|
297
297
|
let isToolConnected = false;
|
|
298
|
-
if (argv.
|
|
298
|
+
if (argv.useAgentCommander) {
|
|
299
|
+
const agentCommanderLib = await import('./agent-commander.lib.mjs');
|
|
300
|
+
isToolConnected = await agentCommanderLib.validateAgentCommanderConnection({
|
|
301
|
+
tool: argv.tool || 'claude',
|
|
302
|
+
model,
|
|
303
|
+
log,
|
|
304
|
+
});
|
|
305
|
+
if (!isToolConnected) {
|
|
306
|
+
await log('❌ Cannot proceed without agent-commander tool connection', { level: 'error' });
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
} else if (argv.tool === 'opencode') {
|
|
299
310
|
// Validate OpenCode connection
|
|
300
311
|
const opencodeLib = await import('./opencode.lib.mjs');
|
|
301
312
|
isToolConnected = await opencodeLib.validateOpenCodeConnection(model);
|
|
@@ -303,6 +314,14 @@ export const performSystemChecks = async (minDiskSpace = 2048, skipToolConnectio
|
|
|
303
314
|
await log('❌ Cannot proceed without OpenCode connection', { level: 'error' });
|
|
304
315
|
return false;
|
|
305
316
|
}
|
|
317
|
+
} else if (argv.tool === 'gemini') {
|
|
318
|
+
// Validate Gemini connection
|
|
319
|
+
const geminiLib = await import('./gemini.lib.mjs');
|
|
320
|
+
isToolConnected = await geminiLib.validateGeminiConnection(model);
|
|
321
|
+
if (!isToolConnected) {
|
|
322
|
+
await log('❌ Cannot proceed without Gemini CLI connection', { level: 'error' });
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
306
325
|
} else if (argv.tool === 'codex') {
|
|
307
326
|
// Validate Codex connection
|
|
308
327
|
const codexLib = await import('./codex.lib.mjs');
|
package/src/task.config.lib.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildModelOptionDescription, defaultModels } from './models/index.mjs';
|
|
2
2
|
import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
|
|
3
3
|
|
|
4
|
-
export const TASK_TOOL_CHOICES = ['claude', 'codex', 'opencode', 'agent'];
|
|
4
|
+
export const TASK_TOOL_CHOICES = ['claude', 'codex', 'opencode', 'agent', 'gemini', 'qwen'];
|
|
5
5
|
|
|
6
6
|
export function getDefaultTaskModel(tool) {
|
|
7
7
|
return defaultModels[tool] || defaultModels.claude;
|
package/src/task.mjs
CHANGED
|
@@ -33,7 +33,7 @@ if (earlyArgs.length === 0 || earlyArgs.includes('--help') || earlyArgs.includes
|
|
|
33
33
|
console.log(' --only-decompose Only run decomposition mode');
|
|
34
34
|
console.log(' --split Split a GitHub issue into smaller issues');
|
|
35
35
|
console.log(' --split-count Number of issues to split into [default: 2]');
|
|
36
|
-
console.log(' --tool AI tool for agent-commander read-only mode (claude, codex, opencode, agent, qwen) [default: claude]');
|
|
36
|
+
console.log(' --tool AI tool for agent-commander read-only mode (claude, codex, opencode, agent, qwen, gemini) [default: claude]');
|
|
37
37
|
console.log(' --model, -m Model to use');
|
|
38
38
|
console.log(' --isolation agent-commander isolation mode [default: screen]');
|
|
39
39
|
console.log(' --dry-run Print split output without creating GitHub issues');
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -342,7 +342,7 @@ function isForwardedOrReply(ctx) {
|
|
|
342
342
|
/**
|
|
343
343
|
* Validates the model name in the args array and returns an error message if invalid
|
|
344
344
|
* @param {string[]} args - Array of command arguments
|
|
345
|
-
* @param {string} tool - The tool to validate against ('claude' or '
|
|
345
|
+
* @param {string} tool - The tool to validate against ('claude', 'opencode', 'codex', 'agent', or 'gemini')
|
|
346
346
|
* @returns {string|null} Error message if invalid, null if valid or no model specified
|
|
347
347
|
*/
|
|
348
348
|
function validateModelInArgs(args, tool = 'claude') {
|
|
@@ -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*, */qwen*) - Solve a GitHub issue\n';
|
|
528
|
+
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */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,7 +535,7 @@ bot.command('help', async ctx => {
|
|
|
535
535
|
}
|
|
536
536
|
message += '\n';
|
|
537
537
|
} else {
|
|
538
|
-
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */qwen*) - ❌ Disabled\n\n';
|
|
538
|
+
message += '*/solve* (aliases: */do*, */continue*, */claude*, */codex*, */opencode*, */agent*, */gemini*, */qwen*) - ❌ Disabled\n\n';
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
if (taskEnabled) {
|
|
@@ -576,7 +576,7 @@ bot.command('help', async ctx => {
|
|
|
576
576
|
message += '🔔 *Session Notifications:* Completion notifications are automatic; use /subscribe for private DM forwards.\n';
|
|
577
577
|
if (ISOLATION_BACKEND) message += `🔒 *Isolation Mode:* \`${ISOLATION_BACKEND}\` (experimental)\n`;
|
|
578
578
|
message += '\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';
|
|
579
|
+
message += '⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /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';
|
|
580
580
|
message += '🔧 *Common Options:*\n';
|
|
581
581
|
message += `• \`--model <model>\` or \`-m\` - ${buildModelOptionDescription()}\n`;
|
|
582
582
|
message += '• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)\n';
|
|
@@ -14,6 +14,7 @@ export const TOOL_SOLVE_COMMAND_ALIASES = Object.freeze({
|
|
|
14
14
|
opencode: 'opencode',
|
|
15
15
|
agent: 'agent',
|
|
16
16
|
qwen: 'qwen',
|
|
17
|
+
gemini: 'gemini',
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
export const SOLVE_COMMAND_NAMES = Object.freeze(['solve', 'do', 'continue', ...Object.keys(TOOL_SOLVE_COMMAND_ALIASES)]);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Shows pending, processing, completed, and failed queue items
|
|
9
|
-
* - Per-tool queue breakdown (claude, opencode, etc.)
|
|
9
|
+
* - Per-tool queue breakdown (claude, opencode, codex, agent, gemini, etc.)
|
|
10
10
|
* - Lists currently processing and waiting items
|
|
11
11
|
* - Running Claude process count
|
|
12
12
|
*
|
|
@@ -7,7 +7,7 @@ const execAsync = promisify(exec);
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Count running processes by name.
|
|
10
|
-
* @param {string} processName - Process name to search for (e.g., 'claude', 'agent')
|
|
10
|
+
* @param {string} processName - Process name to search for (e.g., 'claude', 'agent', 'codex', 'gemini')
|
|
11
11
|
* @param {boolean} verbose - Whether to log verbose output
|
|
12
12
|
* @returns {Promise<{count: number, processes: string[]}>}
|
|
13
13
|
*/
|
|
@@ -84,6 +84,15 @@ export async function getRunningQwenProcesses(verbose = false) {
|
|
|
84
84
|
return getRunningProcesses('qwen', verbose);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Count running gemini processes.
|
|
89
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
90
|
+
* @returns {Promise<{count: number, processes: string[]}>}
|
|
91
|
+
*/
|
|
92
|
+
export async function getRunningGeminiProcesses(verbose = false) {
|
|
93
|
+
return getRunningProcesses('gemini', verbose);
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
/**
|
|
88
97
|
* Format a threshold as percentage for display.
|
|
89
98
|
* @param {number} ratio - Ratio (0.0 - 1.0)
|
|
@@ -156,6 +165,8 @@ export function formatWaitingReason(metric, currentValue, threshold) {
|
|
|
156
165
|
return 'Codex process is already running';
|
|
157
166
|
case 'qwen_running':
|
|
158
167
|
return 'Qwen Code process is already running';
|
|
168
|
+
case 'gemini_running':
|
|
169
|
+
return 'Gemini CLI process is already running';
|
|
159
170
|
default:
|
|
160
171
|
return `${metric} threshold exceeded`;
|
|
161
172
|
}
|
|
@@ -16,8 +16,8 @@
|
|
|
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, getRunningQwenProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
20
|
-
import { formatDuration, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
19
|
+
export { formatDuration, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
20
|
+
import { formatDuration, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses } 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';
|
|
23
23
|
import { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
|
|
@@ -128,7 +128,7 @@ class SolveQueueItem {
|
|
|
128
128
|
* Solve Queue - Producer/Consumer queue for /solve commands
|
|
129
129
|
*
|
|
130
130
|
* Uses separate queues for each tool type to ensure:
|
|
131
|
-
* - Claude tasks never block agent tasks (and vice versa)
|
|
131
|
+
* - Claude tasks never block agent, codex, gemini, or qwen tasks (and vice versa)
|
|
132
132
|
* - Each tool queue maintains FIFO order
|
|
133
133
|
* - Each tool has independent rate limiting
|
|
134
134
|
*
|
|
@@ -143,13 +143,14 @@ export class SolveQueue {
|
|
|
143
143
|
this.getRunningIsolatedSessionsFn = options.getRunningIsolatedSessions || getRunningIsolatedSessions;
|
|
144
144
|
this.autoStart = options.autoStart !== false;
|
|
145
145
|
|
|
146
|
-
// Separate queues per tool type - claude tasks never block
|
|
146
|
+
// Separate queues per tool type - claude tasks never block other tool tasks
|
|
147
147
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
148
148
|
this.queues = {
|
|
149
149
|
claude: [],
|
|
150
150
|
agent: [],
|
|
151
151
|
codex: [],
|
|
152
152
|
qwen: [],
|
|
153
|
+
gemini: [],
|
|
153
154
|
};
|
|
154
155
|
this.processing = new Map();
|
|
155
156
|
this.completed = [];
|
|
@@ -162,6 +163,7 @@ export class SolveQueue {
|
|
|
162
163
|
agent: null,
|
|
163
164
|
codex: null,
|
|
164
165
|
qwen: null,
|
|
166
|
+
gemini: null,
|
|
165
167
|
};
|
|
166
168
|
// Legacy: keep for compatibility with existing code that uses lastStartTime
|
|
167
169
|
this.lastStartTime = null;
|
|
@@ -184,7 +186,7 @@ export class SolveQueue {
|
|
|
184
186
|
|
|
185
187
|
/**
|
|
186
188
|
* Get the queue array for a specific tool, creating it if needed
|
|
187
|
-
* @param {string} tool - Tool type ('claude', 'agent', etc.)
|
|
189
|
+
* @param {string} tool - Tool type ('claude', 'agent', 'codex', 'gemini', etc.)
|
|
188
190
|
* @returns {Array} The queue array for this tool
|
|
189
191
|
*/
|
|
190
192
|
getToolQueue(tool) {
|
|
@@ -333,7 +335,7 @@ export class SolveQueue {
|
|
|
333
335
|
/**
|
|
334
336
|
* Count processing items by tool type
|
|
335
337
|
* Used for tool-specific limit checking - e.g., Claude limits only count Claude processing items
|
|
336
|
-
* @param {string} tool - Tool type to count ('claude', 'agent', etc.)
|
|
338
|
+
* @param {string} tool - Tool type to count ('claude', 'agent', 'codex', 'gemini', etc.)
|
|
337
339
|
* @returns {number} Count of processing items with the specified tool
|
|
338
340
|
* @see https://github.com/link-assistant/hive-mind/issues/1159
|
|
339
341
|
*/
|
|
@@ -378,6 +380,7 @@ export class SolveQueue {
|
|
|
378
380
|
|
|
379
381
|
if (check.canStart) {
|
|
380
382
|
const item = toolQueue[0];
|
|
383
|
+
if (!item) continue;
|
|
381
384
|
// Also check one-at-a-time mode for this specific tool
|
|
382
385
|
// For tool-specific one-at-a-time, only count that tool's processing items
|
|
383
386
|
const toolProcessingCount = this.getProcessingCountByTool(tool);
|
|
@@ -397,7 +400,7 @@ export class SolveQueue {
|
|
|
397
400
|
* Reject all items in a tool queue and notify users.
|
|
398
401
|
* Called when a 'reject' strategy threshold is exceeded for queued items.
|
|
399
402
|
*
|
|
400
|
-
* @param {string} tool - Tool type (e.g., 'claude', 'agent')
|
|
403
|
+
* @param {string} tool - Tool type (e.g., 'claude', 'agent', 'codex', 'gemini')
|
|
401
404
|
* @param {SolveQueueItem[]} toolQueue - The tool's queue array
|
|
402
405
|
* @param {string} rejectReason - Reason for rejection
|
|
403
406
|
* @see https://github.com/link-assistant/hive-mind/issues/1555
|
|
@@ -523,8 +526,8 @@ export class SolveQueue {
|
|
|
523
526
|
*
|
|
524
527
|
* Logic per issue #1159:
|
|
525
528
|
* - Different tools have different limits. Claude limits only apply to 'claude' tool.
|
|
526
|
-
* - Processing count for Claude limits only includes Claude items, not agent items.
|
|
527
|
-
* - This allows
|
|
529
|
+
* - Processing count for Claude limits only includes Claude items, not agent/codex/gemini/qwen items.
|
|
530
|
+
* - This allows non-Claude tasks to run in parallel when Claude limits are reached.
|
|
528
531
|
*
|
|
529
532
|
* Logic per issue #1253:
|
|
530
533
|
* - All thresholds now support configurable strategies (reject, enqueue, dequeue-one-at-a-time)
|
|
@@ -533,7 +536,7 @@ export class SolveQueue {
|
|
|
533
536
|
* - 'dequeue-one-at-a-time' allows one command while blocking subsequent
|
|
534
537
|
*
|
|
535
538
|
* @param {Object} options - Options for the check
|
|
536
|
-
* @param {string} options.tool - The tool being used ('claude', 'agent', etc.)
|
|
539
|
+
* @param {string} options.tool - The tool being used ('claude', 'agent', 'codex', 'gemini', 'qwen', etc.)
|
|
537
540
|
* @returns {Promise<{canStart: boolean, rejected?: boolean, rejectReason?: string, reason?: string, reasons?: string[], oneAtATime?: boolean}>}
|
|
538
541
|
*/
|
|
539
542
|
async canStartCommand(options = {}) {
|
|
@@ -564,9 +567,11 @@ export class SolveQueue {
|
|
|
564
567
|
const codexProcessCount = externalProcessing.byTool.codex || 0;
|
|
565
568
|
const agentProcessCount = externalProcessing.byTool.agent || 0;
|
|
566
569
|
const qwenProcessCount = externalProcessing.byTool.qwen || 0;
|
|
570
|
+
const geminiProcessCount = externalProcessing.byTool.gemini || 0;
|
|
567
571
|
const hasRunningClaude = claudeProcessCount > 0;
|
|
568
572
|
const hasRunningCodex = codexProcessCount > 0;
|
|
569
573
|
const hasRunningQwen = qwenProcessCount > 0;
|
|
574
|
+
const hasRunningGemini = geminiProcessCount > 0;
|
|
570
575
|
|
|
571
576
|
// Calculate total processing count for system resources (all tools)
|
|
572
577
|
// System resources (RAM, CPU, disk) apply to all tools
|
|
@@ -574,11 +579,12 @@ export class SolveQueue {
|
|
|
574
579
|
|
|
575
580
|
// Calculate Claude-specific processing count for Claude API limits
|
|
576
581
|
// Only counts Claude items in queue + external claude processes
|
|
577
|
-
//
|
|
582
|
+
// Non-Claude items don't count against Claude's one-at-a-time limit
|
|
578
583
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
579
584
|
const claudeProcessingCount = this.getProcessingCountByTool('claude');
|
|
580
585
|
const codexProcessingCount = this.getProcessingCountByTool('codex');
|
|
581
586
|
const qwenProcessingCount = this.getProcessingCountByTool('qwen');
|
|
587
|
+
const geminiProcessingCount = this.getProcessingCountByTool('gemini');
|
|
582
588
|
|
|
583
589
|
// Track claude_running as a metric (but don't add to reasons yet)
|
|
584
590
|
if (hasRunningClaude) {
|
|
@@ -590,6 +596,9 @@ export class SolveQueue {
|
|
|
590
596
|
if (hasRunningQwen) {
|
|
591
597
|
this.recordThrottle('qwen_running');
|
|
592
598
|
}
|
|
599
|
+
if (hasRunningGemini) {
|
|
600
|
+
this.recordThrottle('gemini_running');
|
|
601
|
+
}
|
|
593
602
|
|
|
594
603
|
// Check system resources with strategy support
|
|
595
604
|
// System resources apply to ALL tools, not just Claude
|
|
@@ -609,11 +618,11 @@ export class SolveQueue {
|
|
|
609
618
|
|
|
610
619
|
// Check API limits with strategy support (pass hasRunningClaude, claudeProcessingCount, and tool)
|
|
611
620
|
// Claude limits use claudeProcessingCount (only Claude items), not totalProcessing
|
|
612
|
-
// This allows
|
|
621
|
+
// This allows non-Claude tasks to proceed when Claude limits are reached
|
|
613
622
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
614
623
|
// See: https://github.com/link-assistant/hive-mind/issues/1253 (strategies)
|
|
615
|
-
const hasRunningToolProcess = tool
|
|
616
|
-
const toolProcessingCount = tool
|
|
624
|
+
const hasRunningToolProcess = (externalProcessing.byTool[tool] || 0) > 0;
|
|
625
|
+
const toolProcessingCount = this.getProcessingCountByTool(tool);
|
|
617
626
|
const limitCheck = await this.checkApiLimits(hasRunningToolProcess, toolProcessingCount, tool);
|
|
618
627
|
if (limitCheck.rejected) {
|
|
619
628
|
rejected = true;
|
|
@@ -640,6 +649,9 @@ export class SolveQueue {
|
|
|
640
649
|
if (tool === 'qwen' && hasRunningQwen && reasons.length > 0) {
|
|
641
650
|
reasons.push(formatWaitingReason('qwen_running', qwenProcessCount, 0) + ` (${qwenProcessCount} processes)`);
|
|
642
651
|
}
|
|
652
|
+
if (tool === 'gemini' && hasRunningGemini && reasons.length > 0) {
|
|
653
|
+
reasons.push(formatWaitingReason('gemini_running', geminiProcessCount, 0) + ` (${geminiProcessCount} processes)`);
|
|
654
|
+
}
|
|
643
655
|
|
|
644
656
|
const canStart = reasons.length === 0 && !rejected;
|
|
645
657
|
|
|
@@ -662,11 +674,13 @@ export class SolveQueue {
|
|
|
662
674
|
codexProcesses: codexProcessCount,
|
|
663
675
|
agentProcesses: agentProcessCount,
|
|
664
676
|
qwenProcesses: qwenProcessCount,
|
|
677
|
+
geminiProcesses: geminiProcessCount,
|
|
665
678
|
isolatedProcesses: externalProcessing.isolatedTotal,
|
|
666
679
|
totalProcessing,
|
|
667
680
|
claudeProcessingCount,
|
|
668
681
|
codexProcessingCount,
|
|
669
682
|
qwenProcessingCount,
|
|
683
|
+
geminiProcessingCount,
|
|
670
684
|
};
|
|
671
685
|
}
|
|
672
686
|
|
|
@@ -800,10 +814,10 @@ export class SolveQueue {
|
|
|
800
814
|
* - GitHub threshold blocks unconditionally when exceeded (ultimate restriction)
|
|
801
815
|
*
|
|
802
816
|
* Logic per issue #1159:
|
|
803
|
-
* - When tool is 'agent', skip Claude-specific limits entirely since
|
|
804
|
-
* rate
|
|
805
|
-
* - For Claude limits, only count Claude-specific processing items, not agent items.
|
|
806
|
-
* This allows
|
|
817
|
+
* - When tool is 'agent', 'gemini', or 'qwen', skip Claude-specific limits entirely since these tools use
|
|
818
|
+
* different rate limiting backends. Only system resources and GitHub limits apply.
|
|
819
|
+
* - For Claude limits, only count Claude-specific processing items, not agent/codex/gemini/qwen items.
|
|
820
|
+
* This allows non-Claude tasks to run in parallel even when Claude limits are reached.
|
|
807
821
|
*
|
|
808
822
|
* Logic per issue #1253:
|
|
809
823
|
* - All thresholds now support configurable strategies (reject, enqueue, dequeue-one-at-a-time)
|
|
@@ -811,7 +825,7 @@ export class SolveQueue {
|
|
|
811
825
|
*
|
|
812
826
|
* @param {boolean} hasRunningToolProcess - Whether matching tool processes are running (from pgrep)
|
|
813
827
|
* @param {number} toolProcessingCount - Count of matching tool items being processed in queue
|
|
814
|
-
* @param {string} tool - The tool being used ('claude', 'agent', etc.)
|
|
828
|
+
* @param {string} tool - The tool being used ('claude', 'agent', 'codex', 'gemini', 'qwen', etc.)
|
|
815
829
|
* @returns {Promise<{ok: boolean, reasons: string[], oneAtATime: boolean, rejected: boolean, rejectReason: string|null}>}
|
|
816
830
|
*/
|
|
817
831
|
async checkApiLimits(hasRunningToolProcess = false, toolProcessingCount = 0, tool = 'claude') {
|
|
@@ -821,7 +835,7 @@ export class SolveQueue {
|
|
|
821
835
|
let rejectReason = null;
|
|
822
836
|
|
|
823
837
|
// Apply Claude-specific limits only when tool is 'claude'
|
|
824
|
-
// Other tools (like 'agent') use different rate limiting backends and are not
|
|
838
|
+
// Other tools (like 'agent', 'gemini', and 'qwen') use different rate limiting backends and are not
|
|
825
839
|
// affected by Claude API limits (5-hour session, weekly limits)
|
|
826
840
|
// See: https://github.com/link-assistant/hive-mind/issues/1159
|
|
827
841
|
const applyClaudeLimits = tool === 'claude';
|
|
@@ -1427,6 +1441,9 @@ export default {
|
|
|
1427
1441
|
getRunningProcesses,
|
|
1428
1442
|
getRunningClaudeProcesses,
|
|
1429
1443
|
getRunningAgentProcesses,
|
|
1444
|
+
getRunningCodexProcesses,
|
|
1445
|
+
getRunningQwenProcesses,
|
|
1446
|
+
getRunningGeminiProcesses,
|
|
1430
1447
|
getRunningIsolatedSessions,
|
|
1431
1448
|
createQueueExecuteCallback,
|
|
1432
1449
|
formatDuration,
|
|
@@ -11,6 +11,9 @@ export const validateToolConnection = async ({ tool = 'claude', model, verbose =
|
|
|
11
11
|
if (tool === 'qwen') {
|
|
12
12
|
return (await import('./qwen.lib.mjs')).validateQwenConnection(model);
|
|
13
13
|
}
|
|
14
|
+
if (tool === 'gemini') {
|
|
15
|
+
return (await import('./gemini.lib.mjs')).validateGeminiConnection(model);
|
|
16
|
+
}
|
|
14
17
|
const validateClaude = validateClaudeConnection || (await import('./claude.lib.mjs')).validateClaudeConnection;
|
|
15
18
|
return validateClaude(model);
|
|
16
19
|
};
|