@link-assistant/hive-mind 1.63.0 → 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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.64.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2ffb808: Add experimental `--use-agent-commander` option to delegate supported tool execution to the agent-commander library, including Claude, Codex, OpenCode, Agent, Qwen, and Gemini.
8
+
3
9
  ## 1.63.0
4
10
 
5
11
  ### Minor Changes
package/README.hi.md CHANGED
@@ -343,7 +343,7 @@ solve <issue-url> [options]
343
343
 
344
344
  | विकल्प | संक्षिप्त | विवरण | डिफ़ॉल्ट |
345
345
  | ------------------------ | --------- | ----------------------------------------------------------- | -------- |
346
- | `--tool` | | AI टूल (claude, opencode, codex, agent, gemini, qwen) | claude |
346
+ | `--tool` | | AI टूल (claude, opencode, codex, agent, qwen, gemini) | claude |
347
347
  | `--verbose` | `-v` | विस्तृत लॉगिंग सक्षम करें | false |
348
348
  | `--attach-logs` | | PR में लॉग संलग्न करें (⚠️ संवेदनशील डेटा उजागर हो सकता है) | false |
349
349
  | `--auto-init-repository` | | खाली रेपो स्वतः-आरंभ करें (README.md बनाता है) | false |
@@ -370,7 +370,7 @@ hive <github-url> [options]
370
370
 
371
371
  | विकल्प | संक्षिप्त | विवरण | डिफ़ॉल्ट |
372
372
  | ------------------------ | --------- | ----------------------------------------------------------- | -------- |
373
- | `--tool` | | AI टूल (claude, opencode, codex, agent, gemini, qwen) | claude |
373
+ | `--tool` | | AI टूल (claude, opencode, codex, agent, qwen, gemini) | claude |
374
374
  | `--concurrency` | `-c` | समानांतर वर्कर की संख्या | 2 |
375
375
  | `--skip-issues-with-prs` | `-s` | मौजूदा PR वाले इश्यू छोड़ें | false |
376
376
  | `--verbose` | `-v` | विस्तृत लॉगिंग सक्षम करें | false |
@@ -462,8 +462,8 @@ Aliases:
462
462
  /codex /solve --tool codex के बराबर है
463
463
  /opencode /solve --tool opencode के बराबर है
464
464
  /agent /solve --tool agent के बराबर है
465
- /gemini /solve --tool gemini के बराबर है
466
465
  /qwen /solve --tool qwen के बराबर है
466
+ /gemini /solve --tool gemini के बराबर है
467
467
 
468
468
  Tool alias examples:
469
469
  /codex https://github.com/owner/repo/issues/123 --model gpt-5.5
@@ -471,6 +471,7 @@ Tool alias examples:
471
471
  /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
472
472
  /gemini https://github.com/owner/repo/issues/123 --model flash
473
473
  /qwen https://github.com/owner/repo/issues/123 --model qwen3-coder-plus
474
+ /gemini https://github.com/owner/repo/issues/123 --model gemini-2.5-flash
474
475
 
475
476
  Free Models (with --tool agent):
476
477
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
package/README.md CHANGED
@@ -352,7 +352,7 @@ solve <issue-url> [options]
352
352
 
353
353
  | Option | Alias | Description | Default |
354
354
  | ------------------------ | ----- | ------------------------------------------------------ | ------- |
355
- | `--tool` | | AI tool (claude, opencode, codex, agent, gemini, qwen) | claude |
355
+ | `--tool` | | AI tool (claude, opencode, codex, agent, qwen, gemini) | claude |
356
356
  | `--verbose` | `-v` | Enable verbose logging | false |
357
357
  | `--attach-logs` | | Attach logs to PR (⚠️ may expose sensitive data) | false |
358
358
  | `--auto-init-repository` | | Auto-initialize empty repos (creates README.md) | false |
@@ -379,7 +379,7 @@ hive <github-url> [options]
379
379
 
380
380
  | Option | Alias | Description | Default |
381
381
  | ------------------------ | ----- | ------------------------------------------------------ | ------- |
382
- | `--tool` | | AI tool (claude, opencode, codex, agent, gemini, qwen) | claude |
382
+ | `--tool` | | AI tool (claude, opencode, codex, agent, qwen, gemini) | claude |
383
383
  | `--concurrency` | `-c` | Number of parallel workers | 2 |
384
384
  | `--skip-issues-with-prs` | `-s` | Skip issues with existing PRs | false |
385
385
  | `--verbose` | `-v` | Enable verbose logging | false |
@@ -469,8 +469,8 @@ Aliases:
469
469
  /codex is equivalent to /solve --tool codex
470
470
  /opencode is equivalent to /solve --tool opencode
471
471
  /agent is equivalent to /solve --tool agent
472
- /gemini is equivalent to /solve --tool gemini
473
472
  /qwen is equivalent to /solve --tool qwen
473
+ /gemini is equivalent to /solve --tool gemini
474
474
 
475
475
  Tool alias examples:
476
476
  /codex https://github.com/owner/repo/issues/123 --model gpt-5.5
@@ -478,6 +478,7 @@ Tool alias examples:
478
478
  /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
479
479
  /gemini https://github.com/owner/repo/issues/123 --model flash
480
480
  /qwen https://github.com/owner/repo/issues/123 --model qwen3-coder-plus
481
+ /gemini https://github.com/owner/repo/issues/123 --model gemini-2.5-flash
481
482
 
482
483
  Free Models (with --tool agent):
483
484
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
@@ -501,6 +502,7 @@ Current tool defaults in Hive Mind:
501
502
  | `agent` | `nemotron-3-super-free` | No extra thinking prompt is added for the default model |
502
503
  | `gemini` | `flash` | No extra thinking prompt is added for the default model |
503
504
  | `qwen` | `qwen3-coder-plus` | No extra thinking prompt is added for the default model |
505
+ | `gemini` | `gemini-2.5-flash` | No extra thinking prompt is added for the default model |
504
506
 
505
507
  See [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) for the full per-tool defaults and reasoning mappings.
506
508
 
package/README.ru.md CHANGED
@@ -343,7 +343,7 @@ solve <issue-url> [options]
343
343
 
344
344
  | Параметр | Сокр. | Описание | По умолчанию |
345
345
  | ------------------------ | ----- | --------------------------------------------------------------------- | ------------ |
346
- | `--tool` | | Инструмент ИИ (claude, opencode, codex, agent, gemini, qwen) | claude |
346
+ | `--tool` | | Инструмент ИИ (claude, opencode, codex, agent, qwen, gemini) | claude |
347
347
  | `--verbose` | `-v` | Включить подробное логирование | false |
348
348
  | `--attach-logs` | | Прикрепить логи к PR (⚠️ может раскрыть конфиденциальные данные) | false |
349
349
  | `--auto-init-repository` | | Автоматически инициализировать пустые репозитории (создаёт README.md) | false |
@@ -370,7 +370,7 @@ hive <github-url> [options]
370
370
 
371
371
  | Параметр | Сокр. | Описание | По умолчанию |
372
372
  | ------------------------ | ----- | ----------------------------------------------------------------- | ------------ |
373
- | `--tool` | | Инструмент ИИ (claude, opencode, codex, agent, gemini, qwen) | claude |
373
+ | `--tool` | | Инструмент ИИ (claude, opencode, codex, agent, qwen, gemini) | claude |
374
374
  | `--concurrency` | `-c` | Количество параллельных воркеров | 2 |
375
375
  | `--skip-issues-with-prs` | `-s` | Пропускать задачи с существующими PR | false |
376
376
  | `--verbose` | `-v` | Включить подробное логирование | false |
@@ -463,8 +463,8 @@ Aliases:
463
463
  /codex эквивалентна /solve --tool codex
464
464
  /opencode эквивалентна /solve --tool opencode
465
465
  /agent эквивалентна /solve --tool agent
466
- /gemini эквивалентна /solve --tool gemini
467
466
  /qwen эквивалентна /solve --tool qwen
467
+ /gemini эквивалентна /solve --tool gemini
468
468
 
469
469
  Tool alias examples:
470
470
  /codex https://github.com/owner/repo/issues/123 --model gpt-5.5
@@ -472,6 +472,7 @@ Tool alias examples:
472
472
  /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
473
473
  /gemini https://github.com/owner/repo/issues/123 --model flash
474
474
  /qwen https://github.com/owner/repo/issues/123 --model qwen3-coder-plus
475
+ /gemini https://github.com/owner/repo/issues/123 --model gemini-2.5-flash
475
476
 
476
477
  Free Models (with --tool agent):
477
478
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
package/README.zh.md CHANGED
@@ -343,7 +343,7 @@ solve <issue-url> [options]
343
343
 
344
344
  | 选项 | 简写 | 描述 | 默认值 |
345
345
  | ------------------------ | ---- | ------------------------------------------------------- | ------ |
346
- | `--tool` | | AI 工具(claude、opencode、codex、agent、gemini、qwen) | claude |
346
+ | `--tool` | | AI 工具(claude、opencode、codex、agent、qwen、gemini) | claude |
347
347
  | `--verbose` | `-v` | 启用详细日志 | false |
348
348
  | `--attach-logs` | | 将日志附加到 PR(⚠️ 可能暴露敏感数据) | false |
349
349
  | `--auto-init-repository` | | 自动初始化空仓库(创建 README.md) | false |
@@ -370,7 +370,7 @@ hive <github-url> [options]
370
370
 
371
371
  | 选项 | 简写 | 描述 | 默认值 |
372
372
  | ------------------------ | ---- | ------------------------------------------------------- | ------ |
373
- | `--tool` | | AI 工具(claude、opencode、codex、agent、gemini、qwen) | claude |
373
+ | `--tool` | | AI 工具(claude、opencode、codex、agent、qwen、gemini) | claude |
374
374
  | `--concurrency` | `-c` | 并行工作进程数量 | 2 |
375
375
  | `--skip-issues-with-prs` | `-s` | 跳过已有 PR 的 Issue | false |
376
376
  | `--verbose` | `-v` | 启用详细日志 | false |
@@ -459,8 +459,8 @@ Aliases:
459
459
  /codex 等同于 /solve --tool codex
460
460
  /opencode 等同于 /solve --tool opencode
461
461
  /agent 等同于 /solve --tool agent
462
- /gemini 等同于 /solve --tool gemini
463
462
  /qwen 等同于 /solve --tool qwen
463
+ /gemini 等同于 /solve --tool gemini
464
464
 
465
465
  Tool alias examples:
466
466
  /codex https://github.com/owner/repo/issues/123 --model gpt-5.5
@@ -468,6 +468,7 @@ Tool alias examples:
468
468
  /agent https://github.com/owner/repo/issues/123 --model nemotron-3-super-free
469
469
  /gemini https://github.com/owner/repo/issues/123 --model flash
470
470
  /qwen https://github.com/owner/repo/issues/123 --model qwen3-coder-plus
471
+ /gemini https://github.com/owner/repo/issues/123 --model gemini-2.5-flash
471
472
 
472
473
  Free Models (with --tool agent):
473
474
  /solve https://github.com/owner/repo/issues/123 --tool agent --model nemotron-3-super-free
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.63.0",
3
+ "version": "1.64.0",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -72,7 +72,7 @@
72
72
  "@secretlint/secretlint-rule-preset-recommend": "^11.2.5",
73
73
  "@sentry/node": "^10.15.0",
74
74
  "@sentry/profiling-node": "^10.15.0",
75
- "agent-commander": "^0.4.2",
75
+ "agent-commander": "^0.6.1",
76
76
  "dayjs": "^1.11.19",
77
77
  "decimal.js-light": "^2.5.1",
78
78
  "lino-arguments": "^0.3.0",
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Experimental agent-commander execution adapter.
4
+ *
5
+ * This module is only used when solve is called with --use-agent-commander.
6
+ * Default tool execution remains in claude.lib.mjs, codex.lib.mjs,
7
+ * opencode.lib.mjs, and agent.lib.mjs.
8
+ */
9
+
10
+ import { resolveCodexReasoningEffort } from './codex.options.lib.mjs';
11
+ import { mapModelForTool } from './models/index.mjs';
12
+ import { buildCodexDisable1mContextConfigArgs, buildCodexSubSessionSizeConfigArgs, parseSubSessionSize } from './sub-session-size.lib.mjs';
13
+ import { detectUsageLimit } from './usage-limit.lib.mjs';
14
+
15
+ export const AGENT_COMMANDER_TOOLS = new Set(['claude', 'codex', 'opencode', 'agent', 'qwen', 'gemini']);
16
+
17
+ const TOOL_EXECUTABLE_CONFIG = {
18
+ claude: { argvKey: 'claudePath', envKey: 'CLAUDE_PATH' },
19
+ codex: { argvKey: 'codexPath', envKey: 'CODEX_PATH' },
20
+ opencode: { argvKey: 'opencodePath', envKey: 'OPENCODE_PATH' },
21
+ agent: { argvKey: 'agentPath', envKey: 'AGENT_PATH' },
22
+ qwen: { argvKey: 'qwenPath', envKey: 'QWEN_PATH' },
23
+ gemini: { argvKey: 'geminiPath', envKey: 'GEMINI_PATH' },
24
+ };
25
+
26
+ const getConfiguredExecutable = (argv = {}, tool) => {
27
+ const config = TOOL_EXECUTABLE_CONFIG[tool];
28
+ if (!config) return null;
29
+ return argv[config.argvKey] || process.env[config.envKey] || null;
30
+ };
31
+
32
+ const appendExtraArgs = (options, args) => {
33
+ if (!Array.isArray(args) || args.length === 0) return;
34
+ options.extraArgs = [...(options.extraArgs || []), ...args];
35
+ };
36
+
37
+ const appendExtraEnv = (options, env) => {
38
+ const entries = Object.entries(env || {}).filter(([, value]) => value !== undefined && value !== null && value !== false);
39
+ if (entries.length === 0) return;
40
+ options.extraEnv = {
41
+ ...(options.extraEnv || {}),
42
+ ...Object.fromEntries(entries.map(([key, value]) => [key, String(value)])),
43
+ };
44
+ };
45
+
46
+ const buildClaudeToolOptions = (argv = {}) => {
47
+ const options = {};
48
+ options.verbose = !!argv.verbose;
49
+ if (argv.fallbackModel) options.fallbackModel = argv.fallbackModel;
50
+
51
+ const extraEnv = {};
52
+ if (argv.thinkingBudget !== undefined) extraEnv.MAX_THINKING_TOKENS = argv.thinkingBudget;
53
+ if (argv.disable1mContext) extraEnv.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
54
+ if (argv.showThinkingContent) extraEnv.CLAUDE_CODE_SHOW_THINKING = '1';
55
+ if (argv.planModel) extraEnv.ANTHROPIC_DEFAULT_OPUS_MODEL = argv.planModel;
56
+ appendExtraEnv(options, extraEnv);
57
+
58
+ return options;
59
+ };
60
+
61
+ const buildCodexToolOptions = (argv = {}) => {
62
+ const options = {};
63
+ const { reasoningEffort } = resolveCodexReasoningEffort(argv);
64
+ appendExtraArgs(options, ['-c', `model_reasoning_effort=${reasoningEffort}`, '-c', 'model_reasoning_summary=auto']);
65
+
66
+ appendExtraArgs(options, buildCodexDisable1mContextConfigArgs(!!argv.disable1mContext));
67
+ try {
68
+ appendExtraArgs(options, buildCodexSubSessionSizeConfigArgs(parseSubSessionSize(argv.subSessionSize)));
69
+ } catch {
70
+ // The embedded Codex path logs parse warnings later. Keep agent-commander
71
+ // command construction permissive so validation can still run.
72
+ }
73
+
74
+ return options;
75
+ };
76
+
77
+ const defaultLog = async (message, options = {}) => {
78
+ if (options.verbose && !global.verboseMode) return;
79
+ if (options.level === 'error') {
80
+ console.error(message);
81
+ } else if (options.level === 'warning' || options.level === 'warn') {
82
+ console.warn(message);
83
+ } else {
84
+ console.log(message);
85
+ }
86
+ };
87
+
88
+ const getAgentCommander = async () => {
89
+ try {
90
+ return await import('agent-commander');
91
+ } catch (error) {
92
+ throw new Error(`agent-commander is not installed or cannot be loaded. Install it with: npm install agent-commander\nOriginal error: ${error.message}`);
93
+ }
94
+ };
95
+
96
+ export const isAgentCommanderAvailable = async () => {
97
+ try {
98
+ await getAgentCommander();
99
+ return true;
100
+ } catch {
101
+ return false;
102
+ }
103
+ };
104
+
105
+ export const getAgentCommanderToolName = (argv = {}) => argv.tool || 'claude';
106
+
107
+ const resolveAgentCommanderModel = (tool, model) => (model ? mapModelForTool(tool, model) : model);
108
+
109
+ export const buildAgentCommanderToolOptions = (argv = {}, tool = getAgentCommanderToolName(argv)) => {
110
+ const options = tool === 'claude' ? buildClaudeToolOptions(argv) : tool === 'codex' ? buildCodexToolOptions(argv) : {};
111
+
112
+ const executable = getConfiguredExecutable(argv, tool);
113
+ if (executable) {
114
+ options.executable = executable;
115
+ }
116
+
117
+ if (tool === 'gemini' && argv.verbose) options.debug = true;
118
+
119
+ return options;
120
+ };
121
+
122
+ export const buildAgentCommanderControllerOptions = ({ tool, tempDir, prompt, systemPrompt, argv = {} }) => ({
123
+ tool,
124
+ workingDirectory: tempDir,
125
+ prompt,
126
+ systemPrompt,
127
+ model: resolveAgentCommanderModel(tool, argv.model),
128
+ json: tool !== 'agent',
129
+ resume: argv.resume,
130
+ toolOptions: buildAgentCommanderToolOptions(argv, tool),
131
+ });
132
+
133
+ export const validateAgentCommanderConnection = async ({ tool, model, log = defaultLog, agentCommanderModule = null }) => {
134
+ try {
135
+ const module = agentCommanderModule || (await getAgentCommander());
136
+ const { agent, isToolSupported } = module;
137
+ const toolName = tool || 'claude';
138
+
139
+ if (!AGENT_COMMANDER_TOOLS.has(toolName) || !isToolSupported({ toolName })) {
140
+ await log(`[agent-commander] Tool '${toolName}' is not supported`, { level: 'error' });
141
+ return false;
142
+ }
143
+
144
+ const controller = agent({
145
+ tool: toolName,
146
+ workingDirectory: process.cwd(),
147
+ prompt: 'connection check',
148
+ model: resolveAgentCommanderModel(toolName, model),
149
+ json: toolName !== 'agent',
150
+ toolOptions: buildAgentCommanderToolOptions({ model }, toolName),
151
+ });
152
+ await controller.start({ dryRun: true, attached: false });
153
+ await log(`[agent-commander] ${toolName} command construction validated`, { verbose: true });
154
+ return true;
155
+ } catch (error) {
156
+ await log(`[agent-commander] Connection validation failed: ${error.message}`, { level: 'error' });
157
+ return false;
158
+ }
159
+ };
160
+
161
+ const getPromptModule = async tool => {
162
+ if (tool === 'claude') return await import('./claude.prompts.lib.mjs');
163
+ if (tool === 'codex') return await import('./codex.prompts.lib.mjs');
164
+ if (tool === 'opencode') return await import('./opencode.prompts.lib.mjs');
165
+ if (tool === 'agent') return await import('./agent.prompts.lib.mjs');
166
+ if (tool === 'qwen') return await import('./qwen.prompts.lib.mjs');
167
+ if (tool === 'gemini') return await import('./gemini.prompts.lib.mjs');
168
+ throw new Error(`Unsupported tool for agent-commander: ${tool}`);
169
+ };
170
+
171
+ export const getPlaywrightMcpAvailabilityCheck = async tool => {
172
+ if (tool === 'opencode') return (await import('./opencode.lib.mjs')).checkPlaywrightMcpAvailability;
173
+ if (tool === 'codex') return (await import('./codex.lib.mjs')).checkPlaywrightMcpAvailability;
174
+ if (tool === 'agent') return (await import('./agent.lib.mjs')).checkPlaywrightMcpAvailability;
175
+ if (tool === 'qwen') return (await import('./qwen.lib.mjs')).checkPlaywrightMcpAvailability;
176
+ if (tool === 'gemini') return (await import('./gemini.lib.mjs')).checkPlaywrightMcpAvailability;
177
+ return (await import('./claude.lib.mjs')).checkPlaywrightMcpAvailability;
178
+ };
179
+
180
+ export const resolvePlaywrightMcpForAgentCommander = async ({ argv, log = defaultLog, tool = getAgentCommanderToolName(argv) }) => {
181
+ if (argv.playwrightMcp === false) return;
182
+ if (!argv.promptPlaywrightMcp) {
183
+ await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
184
+ return;
185
+ }
186
+
187
+ const checkFn = await getPlaywrightMcpAvailabilityCheck(tool);
188
+ const available = await checkFn();
189
+ if (available) {
190
+ await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
191
+ } else {
192
+ await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
193
+ argv.promptPlaywrightMcp = false;
194
+ }
195
+ };
196
+
197
+ const getParsedMessages = result => {
198
+ const parsed = result?.output?.parsed;
199
+ if (Array.isArray(parsed)) return parsed;
200
+ if (parsed && typeof parsed === 'object') return [parsed];
201
+ return [];
202
+ };
203
+
204
+ const extractResultSummary = (messages, plainOutput) => {
205
+ for (const message of [...messages].reverse()) {
206
+ if (typeof message?.result === 'string' && message.result.trim()) return message.result.trim();
207
+ if (typeof message?.summary === 'string' && message.summary.trim()) return message.summary.trim();
208
+ if (typeof message?.text === 'string' && message.text.trim()) return message.text.trim();
209
+ if (typeof message?.message === 'string' && message.message.trim()) return message.message.trim();
210
+ if (typeof message?.item?.content === 'string' && message.item.content.trim()) return message.item.content.trim();
211
+ if (Array.isArray(message?.item?.content)) {
212
+ const text = message.item.content
213
+ .map(part => part?.text || '')
214
+ .join('')
215
+ .trim();
216
+ if (text) return text;
217
+ }
218
+ }
219
+
220
+ return plainOutput?.trim() ? plainOutput.trim().slice(-4000) : null;
221
+ };
222
+
223
+ const hasErrorMessage = messages => messages.some(message => message?.is_error === true || message?.type === 'error' || message?.type === 'step_error' || message?.error);
224
+
225
+ export const summarizeAgentCommanderResult = ({ result, tool }) => {
226
+ const plainOutput = result?.output?.plain || '';
227
+ if (result?.metadata && typeof result.metadata === 'object') {
228
+ const metadata = result.metadata;
229
+ return {
230
+ success: metadata.success === true,
231
+ sessionId: metadata.sessionId || result.sessionId || null,
232
+ limitReached: !!metadata.limitReached,
233
+ limitResetTime: metadata.limitResetTime || null,
234
+ limitTimezone: metadata.limitTimezone || null,
235
+ anthropicTotalCostUSD: metadata.anthropicTotalCostUSD ?? null,
236
+ publicPricingEstimate: metadata.publicPricingEstimate ?? metadata.pricingInfo?.totalCostUSD ?? null,
237
+ pricingInfo: metadata.pricingInfo || null,
238
+ resultSummary: metadata.resultSummary || null,
239
+ resultModelUsage: metadata.resultModelUsage || null,
240
+ streamTokenUsage: metadata.streamTokenUsage || result.usage || null,
241
+ subAgentCalls: metadata.subAgentCalls || null,
242
+ errorDuringExecution: metadata.errorDuringExecution === true || result?.exitCode !== 0,
243
+ result: plainOutput,
244
+ };
245
+ }
246
+
247
+ const messages = getParsedMessages(result);
248
+ const usageLimit = detectUsageLimit(plainOutput);
249
+ const usage = result?.usage || null;
250
+ const resultMessage = [...messages].reverse().find(message => message?.type === 'result') || null;
251
+ const totalCost = typeof resultMessage?.total_cost_usd === 'number' ? resultMessage.total_cost_usd : null;
252
+ const publicPricingEstimate = tool === 'agent' && typeof usage?.totalCost === 'number' ? usage.totalCost : null;
253
+
254
+ return {
255
+ success: result?.exitCode === 0 && !usageLimit.isUsageLimit && !hasErrorMessage(messages),
256
+ sessionId: result?.sessionId || resultMessage?.session_id || null,
257
+ limitReached: usageLimit.isUsageLimit,
258
+ limitResetTime: usageLimit.resetTime,
259
+ limitTimezone: usageLimit.timezone,
260
+ anthropicTotalCostUSD: tool === 'claude' ? totalCost : null,
261
+ publicPricingEstimate,
262
+ pricingInfo: publicPricingEstimate !== null ? { totalCostUSD: publicPricingEstimate, source: 'agent-commander' } : null,
263
+ resultSummary: extractResultSummary(messages, plainOutput),
264
+ resultModelUsage: null,
265
+ streamTokenUsage: usage,
266
+ subAgentCalls: null,
267
+ errorDuringExecution: result?.exitCode !== 0 || hasErrorMessage(messages),
268
+ result: plainOutput,
269
+ };
270
+ };
271
+
272
+ export const executeWithAgentCommander = async params => {
273
+ const { agentCommanderModule = null, promptModule = null, log = defaultLog, argv, tempDir, workspaceTmpDir, ...promptParams } = params;
274
+ const tool = getAgentCommanderToolName(argv);
275
+ const module = agentCommanderModule || (await getAgentCommander());
276
+
277
+ if (!AGENT_COMMANDER_TOOLS.has(tool) || !module.isToolSupported({ toolName: tool })) {
278
+ throw new Error(`agent-commander does not support tool '${tool}'`);
279
+ }
280
+
281
+ const prompts = promptModule || (await getPromptModule(tool));
282
+ const promptBuilderParams = { ...promptParams, tempDir, workspaceTmpDir, argv };
283
+ const prompt = prompts.buildUserPrompt(promptBuilderParams);
284
+ const systemPrompt = prompts.buildSystemPrompt(promptBuilderParams);
285
+ const controllerOptions = buildAgentCommanderControllerOptions({ tool, tempDir, prompt, systemPrompt, argv });
286
+
287
+ if (argv.verbose) {
288
+ await log('\n[agent-commander] Final prompt structure:', { verbose: true });
289
+ await log(` Tool: ${tool}`, { verbose: true });
290
+ await log(` User prompt characters: ${prompt.length}`, { verbose: true });
291
+ await log(` System prompt characters: ${systemPrompt.length}`, { verbose: true });
292
+ }
293
+
294
+ const controller = module.agent(controllerOptions);
295
+ const dryRun = !!(argv.dryRun || argv.onlyPrepareCommand);
296
+ await log(`\n[agent-commander] Starting ${tool} execution${dryRun ? ' (dry-run)' : ''}...`);
297
+ await controller.start({
298
+ dryRun,
299
+ attached: true,
300
+ onOutput: chunk => {
301
+ if (chunk.type === 'stderr') process.stderr.write(chunk.data);
302
+ else process.stdout.write(chunk.data);
303
+ },
304
+ });
305
+
306
+ if (dryRun) {
307
+ return {
308
+ success: true,
309
+ sessionId: null,
310
+ limitReached: false,
311
+ limitResetTime: null,
312
+ limitTimezone: null,
313
+ anthropicTotalCostUSD: null,
314
+ publicPricingEstimate: null,
315
+ pricingInfo: null,
316
+ resultSummary: null,
317
+ };
318
+ }
319
+
320
+ const result = await controller.stop();
321
+ await log(`[agent-commander] ${tool} exited with code ${result.exitCode}`);
322
+ return summarizeAgentCommanderResult({ result, tool });
323
+ };
324
+
325
+ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchName, $, log = defaultLog, autoCommit = false, autoRestartEnabled = true) => {
326
+ await log('\n🔍 Checking for uncommitted changes...');
327
+ const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
328
+ const statusOutput = gitStatusResult.stdout?.toString().trim() || '';
329
+
330
+ if (!statusOutput) {
331
+ await log('✅ No uncommitted changes found');
332
+ return false;
333
+ }
334
+
335
+ await log('📝 Found uncommitted changes');
336
+ await log('Changes:');
337
+ for (const line of statusOutput.split('\n')) await log(` ${line}`);
338
+
339
+ if (autoCommit) {
340
+ await log('💾 Auto-committing changes (--auto-commit-uncommitted-changes is enabled)...');
341
+ const addResult = await $({ cwd: tempDir })`git add -A`;
342
+ if (addResult.code === 0) {
343
+ const commitResult = await $({ cwd: tempDir })`git commit -m ${'Auto-commit: Changes made through agent-commander during problem-solving session'}`;
344
+ if (commitResult.code === 0) {
345
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
346
+ await log(pushResult.code === 0 ? '✅ Changes pushed successfully' : `⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, { level: pushResult.code === 0 ? 'info' : 'warning' });
347
+ }
348
+ }
349
+ return false;
350
+ }
351
+
352
+ if (autoRestartEnabled) {
353
+ await log('\n⚠️ IMPORTANT: Uncommitted changes detected!');
354
+ await log(' The agent-commander controlled tool made changes that were not committed.');
355
+ await log('\n🔄 AUTO-RESTART: Restarting the tool to handle uncommitted changes...\n');
356
+ return true;
357
+ }
358
+
359
+ await log('\n⚠️ Uncommitted changes detected but auto-restart is disabled.');
360
+ return false;
361
+ };
@@ -294,13 +294,13 @@ Plan sub-agent usage.
294
294
 
295
295
  Agent Commander usage (unified subagent delegation).
296
296
  - When you need to delegate tasks to subagents, use the agent-commander CLI tool (start-agent) instead of the native Task tool.
297
- - Agent Commander provides a unified API for different agent types (claude, opencode, codex, agent, qwen) and supports various isolation modes.
297
+ - Agent Commander provides a unified API for different agent types (claude, opencode, codex, agent, qwen, gemini) and supports various isolation modes.
298
298
  - To delegate a task, use the Bash tool to run start-agent with appropriate parameters:
299
299
  \`\`\`bash
300
300
  start-agent --tool claude --working-directory "$(pwd)" --prompt "Your task description here"
301
301
  \`\`\`
302
302
  - Common start-agent parameters:
303
- --tool <name>: Agent to use (claude, opencode, codex, agent, qwen)
303
+ --tool <name>: Agent to use (claude, opencode, codex, agent, qwen, gemini)
304
304
  --working-directory <path>: Execution directory (use current directory for context)
305
305
  --prompt <text>: The task to delegate
306
306
  --model <name>: Model to use (${[...new Set(Object.values(primaryModelNames).flat())].slice(0, 5).join(', ')}, etc.)
@@ -277,13 +277,13 @@ Planning workflow usage.
277
277
 
278
278
  Agent Commander usage (unified subagent delegation).
279
279
  - When you need to delegate tasks to subagents, use the agent-commander CLI tool (start-agent) instead of relying only on native Codex collaboration.
280
- - Agent Commander provides a unified API for different agent types (claude, opencode, codex, agent, qwen) and supports various isolation modes.
280
+ - Agent Commander provides a unified API for different agent types (claude, opencode, codex, agent, qwen, gemini) and supports various isolation modes.
281
281
  - To delegate a task, use a command like:
282
282
  \`\`\`bash
283
283
  start-agent --tool codex --working-directory "$(pwd)" --prompt "Your task description here"
284
284
  \`\`\`
285
285
  - Common start-agent parameters:
286
- --tool <name>: Agent to use (claude, opencode, codex, agent, qwen)
286
+ --tool <name>: Agent to use (claude, opencode, codex, agent, qwen, gemini)
287
287
  --working-directory <path>: Execution directory (use the current directory for context)
288
288
  --prompt <text>: The task to delegate
289
289
  --model <name>: Model to use
@@ -47,7 +47,7 @@ const HIVE_CUSTOM_SOLVE_OPTIONS = {
47
47
  tool: {
48
48
  type: 'string',
49
49
  description: 'AI tool to use for solving issues',
50
- choices: ['claude', 'opencode', 'codex', 'agent', 'gemini', 'qwen'],
50
+ choices: ['claude', 'opencode', 'codex', 'agent', 'qwen', 'gemini'],
51
51
  default: 'claude',
52
52
  },
53
53
  };
@@ -129,19 +129,6 @@ export const codexModels = {
129
129
  'gpt-4o': 'gpt-4o',
130
130
  };
131
131
 
132
- // Gemini models (Google Gemini CLI)
133
- // Keep aliases aligned with the Gemini CLI model aliases documented in
134
- // docs/cli/cli-reference.md: auto, pro, flash, and flash-lite.
135
- export const geminiModels = {
136
- auto: 'auto',
137
- pro: 'gemini-2.5-pro',
138
- flash: 'gemini-2.5-flash',
139
- 'flash-lite': 'gemini-2.5-flash-lite',
140
- 'gemini-2.5-pro': 'gemini-2.5-pro',
141
- 'gemini-2.5-flash': 'gemini-2.5-flash',
142
- 'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite',
143
- };
144
-
145
132
  // Qwen Code models
146
133
  export const qwenModels = {
147
134
  qwen: 'qwen3-coder-plus',
@@ -154,14 +141,38 @@ export const qwenModels = {
154
141
  'qwen3.6-coder-plus': 'qwen3.6-coder-plus',
155
142
  };
156
143
 
144
+ // Gemini models (Google Gemini CLI)
145
+ // Keep aliases aligned with the Gemini CLI model aliases documented in
146
+ // docs/cli/cli-reference.md: auto, pro, flash, and flash-lite.
147
+ export const geminiModels = {
148
+ auto: 'auto',
149
+ gemini: 'gemini-2.5-flash',
150
+ flash: 'gemini-2.5-flash',
151
+ '2.5-flash': 'gemini-2.5-flash',
152
+ pro: 'gemini-2.5-pro',
153
+ '2.5-pro': 'gemini-2.5-pro',
154
+ lite: 'gemini-2.5-flash-lite',
155
+ '2.5-lite': 'gemini-2.5-flash-lite',
156
+ 'flash-lite': 'gemini-2.5-flash-lite',
157
+ '3-flash': 'gemini-3-flash-preview',
158
+ '3-pro': 'gemini-3-pro-preview',
159
+ 'gemini-flash': 'gemini-2.5-flash',
160
+ 'gemini-pro': 'gemini-2.5-pro',
161
+ 'gemini-2.5-flash': 'gemini-2.5-flash',
162
+ 'gemini-2.5-pro': 'gemini-2.5-pro',
163
+ 'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite',
164
+ 'gemini-3-flash-preview': 'gemini-3-flash-preview',
165
+ 'gemini-3-pro-preview': 'gemini-3-pro-preview',
166
+ };
167
+
157
168
  // Default model for each tool (Issue #1473: centralized to avoid scattered hardcoded defaults)
158
169
  export const defaultModels = {
159
170
  claude: 'sonnet',
160
171
  agent: 'nemotron-3-super-free', // Issue #1563: changed from qwen3.6-plus-free (free promotion ended) per agent PR #243
161
172
  opencode: 'grok-code-fast-1',
162
173
  codex: 'gpt-5.5',
163
- gemini: 'flash',
164
174
  qwen: 'qwen3-coder-plus',
175
+ gemini: 'flash',
165
176
  };
166
177
 
167
178
  // Models that support 1M token context window via [1m] suffix (Issue #1221, Issue #1238, Issue #1329)
@@ -241,10 +252,6 @@ export const CODEX_MODELS = {
241
252
  'gpt-4o': 'gpt-4o',
242
253
  };
243
254
 
244
- export const GEMINI_MODELS = {
245
- ...geminiModels,
246
- };
247
-
248
255
  export const QWEN_MODELS = {
249
256
  ...qwenModels,
250
257
  'qwen3-coder': 'qwen3-coder',
@@ -254,6 +261,15 @@ export const QWEN_MODELS = {
254
261
  'qwen3.6-coder-plus': 'qwen3.6-coder-plus',
255
262
  };
256
263
 
264
+ export const GEMINI_MODELS = {
265
+ ...geminiModels,
266
+ 'gemini-2.5-flash': 'gemini-2.5-flash',
267
+ 'gemini-2.5-pro': 'gemini-2.5-pro',
268
+ 'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite',
269
+ 'gemini-3-flash-preview': 'gemini-3-flash-preview',
270
+ 'gemini-3-pro-preview': 'gemini-3-pro-preview',
271
+ };
272
+
257
273
  export const AGENT_MODELS = {
258
274
  ...agentModels,
259
275
  'opencode/grok-code': 'opencode/grok-code',
@@ -275,7 +291,7 @@ export const AGENT_MODELS = {
275
291
 
276
292
  /**
277
293
  * Get the model map object for a given tool
278
- * @param {string} tool - The tool name (claude, agent, opencode, codex, gemini, qwen)
294
+ * @param {string} tool - The tool name (claude, agent, opencode, codex, qwen, gemini)
279
295
  * @returns {Object} The model mapping for the tool
280
296
  */
281
297
  export const getModelMapForTool = tool => {
@@ -299,7 +315,7 @@ export const getModelMapForTool = tool => {
299
315
 
300
316
  /**
301
317
  * Get the default model for a given tool
302
- * @param {string} tool - The tool name (claude, agent, opencode, codex, gemini, qwen)
318
+ * @param {string} tool - The tool name (claude, agent, opencode, codex, qwen, gemini)
303
319
  * @returns {string} The default model alias for the tool
304
320
  */
305
321
  export const getDefaultModelForTool = tool => {
@@ -352,7 +368,7 @@ export const resolveRuntimeDefaultModel = async (tool, options = {}) => {
352
368
 
353
369
  /**
354
370
  * Map model name to full model ID for a specific tool
355
- * @param {string} tool - The tool name (claude, agent, opencode, codex, gemini, qwen)
371
+ * @param {string} tool - The tool name (claude, agent, opencode, codex, qwen, gemini)
356
372
  * @param {string} model - The model name or alias
357
373
  * @returns {string} The full model ID
358
374
  */
@@ -377,7 +393,7 @@ export const mapModelForTool = (tool, model) => {
377
393
 
378
394
  /**
379
395
  * Validate if a model is compatible with a tool
380
- * @param {string} tool - The tool name (claude, agent, opencode, codex, gemini, qwen)
396
+ * @param {string} tool - The tool name (claude, agent, opencode, codex, qwen, gemini)
381
397
  * @param {string} model - The model name or alias
382
398
  * @returns {boolean} True if the model is compatible with the tool
383
399
  */
@@ -432,9 +448,9 @@ export const primaryModelNames = {
432
448
  claude: ['opus', 'sonnet', 'haiku', 'opusplan'],
433
449
  opencode: ['grok', 'gpt4o'],
434
450
  codex: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.3-codex-spark'],
435
- gemini: ['flash', 'pro', 'flash-lite', 'auto'],
436
451
  agent: ['nemotron-3-super-free', 'minimax-m2.5-free', 'big-pickle', 'gpt-5-nano', 'glm-5-free', 'deepseek-r1-free'],
437
452
  qwen: ['qwen3-coder-plus', 'qwen3-coder', 'qwen3-coder-flash'],
453
+ gemini: ['flash', 'pro', 'flash-lite', 'auto'],
438
454
  };
439
455
 
440
456
  /**
@@ -474,7 +490,7 @@ export const validateToolModelCompatibility = (tool, model) => {
474
490
 
475
491
  /**
476
492
  * Get the model map for a given tool (validation-extended version with full ID entries)
477
- * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'gemini', 'qwen')
493
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'qwen', 'gemini')
478
494
  * @returns {Object} The model mapping for the tool
479
495
  */
480
496
  const getValidationModelMapForTool = tool => {
@@ -497,7 +513,7 @@ const getValidationModelMapForTool = tool => {
497
513
 
498
514
  /**
499
515
  * Get the list of available model names for a tool (for display in help/error messages)
500
- * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'gemini', 'qwen')
516
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'qwen', 'gemini')
501
517
  * @returns {string[]} Array of available model short names
502
518
  */
503
519
  export const getAvailableModelNames = tool => {
@@ -636,7 +652,7 @@ export const supports1mContext = (model, tool = 'claude') => {
636
652
  * Validate a model name against the available models for a tool
637
653
  * Supports [1m] suffix for 1 million token context (Issue #1221)
638
654
  * @param {string} model - The model name to validate (e.g., "opus", "opus[1m]", "claude-opus-4-6[1m]")
639
- * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'gemini', 'qwen')
655
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'qwen', 'gemini')
640
656
  * @returns {{ valid: boolean, message?: string, suggestions?: string[], mappedModel?: string, has1mSuffix?: boolean }}
641
657
  */
642
658
  export const validateModelName = (model, tool = 'claude') => {
@@ -709,7 +725,7 @@ export const validateModelName = (model, tool = 'claude') => {
709
725
  * Validate model name and exit with error if invalid
710
726
  * This is the main entry point for model validation in solve.mjs, hive.mjs, etc.
711
727
  * @param {string} model - The model name to validate
712
- * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'qwen')
728
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent', 'qwen', 'gemini')
713
729
  * @param {Function} exitFn - Function to call for exiting (default: process.exit)
714
730
  * @returns {Promise<boolean>} True if valid, exits process if invalid
715
731
  */
@@ -744,7 +760,7 @@ export const formatAvailableModelsForHelp = (tool = 'claude') => {
744
760
 
745
761
  /**
746
762
  * Map tool identifier to user-friendly display name.
747
- * @param {string|null} tool - The tool identifier (claude, codex, opencode, agent, gemini, qwen)
763
+ * @param {string|null} tool - The tool identifier (claude, codex, opencode, agent, qwen, gemini)
748
764
  * @returns {string} User-friendly display name
749
765
  */
750
766
  export const getToolDisplayName = tool => {
@@ -883,7 +899,7 @@ const doesRequestedMatchActual = (requestedModel, actualModelId, tool) => {
883
899
  *
884
900
  * @param {Object} options - Model info options
885
901
  * @param {string|null} options.requestedModel - The model requested via --model flag
886
- * @param {string|null} options.tool - The tool used (claude, agent, opencode, codex, qwen)
902
+ * @param {string|null} options.tool - The tool used (claude, agent, opencode, codex, qwen, gemini)
887
903
  * @param {Object|null} options.pricingInfo - Pricing info from tool result
888
904
  * @param {Object|null} options.modelInfo - Pre-fetched model metadata from models.dev
889
905
  * @param {Array<{modelId: string, modelInfo: Object|null}>|null} options.modelsUsed - Actual models used from CLI JSON output
@@ -994,7 +1010,7 @@ export const resolveDefaultFallbackModel = (tool, model) => {
994
1010
  *
995
1011
  * @param {Object} options
996
1012
  * @param {string|null} options.requestedModel - The --model flag value
997
- * @param {string|null} options.tool - The tool used (claude, agent, opencode, codex, qwen)
1013
+ * @param {string|null} options.tool - The tool used (claude, agent, opencode, codex, qwen, gemini)
998
1014
  * @param {Object|null} options.pricingInfo - Pricing info from tool result
999
1015
  * @param {Array<string>|null} options.actualModelIds - Actual model IDs from CLI JSON output
1000
1016
  * @returns {Promise<string>} Formatted markdown model info section
@@ -13,7 +13,7 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
13
13
  * @returns {string} The formatted user prompt
14
14
  */
15
15
  export const buildUserPrompt = params => {
16
- const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
16
+ const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, tool = 'qwen' } = params;
17
17
 
18
18
  const promptLines = [];
19
19
 
@@ -50,7 +50,7 @@ export const buildUserPrompt = params => {
50
50
  promptLines.push('');
51
51
  }
52
52
 
53
- const thinkingPromptInstruction = getThinkingPromptInstruction({ tool: 'qwen', argv });
53
+ const thinkingPromptInstruction = getThinkingPromptInstruction({ tool, argv });
54
54
  if (thinkingPromptInstruction) {
55
55
  promptLines.push(thinkingPromptInstruction);
56
56
  }
@@ -327,7 +327,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
327
327
  tool: {
328
328
  type: 'string',
329
329
  description: 'AI tool to use for solving issues',
330
- choices: ['claude', 'opencode', 'codex', 'agent', 'gemini', 'qwen'],
330
+ choices: ['claude', 'opencode', 'codex', 'agent', 'qwen', 'gemini'],
331
331
  default: 'claude',
332
332
  },
333
333
  plan: {
@@ -352,7 +352,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
352
352
  },
353
353
  'enable-workspaces': {
354
354
  type: 'boolean',
355
- description: 'Use separate workspace directory structure with repository/ and tmp/ folders. Works with all tools (claude, opencode, codex, agent, gemini, qwen). Experimental feature.',
355
+ description: 'Use separate workspace directory structure with repository/ and tmp/ folders. Works with all tools (claude, opencode, codex, agent, qwen, gemini). Experimental feature.',
356
356
  default: false,
357
357
  },
358
358
  'interactive-mode': {
@@ -428,7 +428,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
428
428
  },
429
429
  'prompt-playwright-mcp': {
430
430
  type: 'boolean',
431
- description: 'Enable Playwright MCP browser automation hints in system prompt (enabled by default, only takes effect if Playwright MCP is installed). Use --no-prompt-playwright-mcp to disable. Supported for --tool claude, --tool codex, --tool opencode, --tool agent, --tool gemini, and --tool qwen.',
431
+ description: 'Enable Playwright MCP browser automation hints in system prompt (enabled by default, only takes effect if Playwright MCP is installed). Use --no-prompt-playwright-mcp to disable. Supported for --tool claude, --tool codex, --tool opencode, --tool agent, --tool qwen, and --tool gemini.',
432
432
  default: true,
433
433
  },
434
434
  'prompt-check-sibling-pull-requests': {
@@ -448,7 +448,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
448
448
  },
449
449
  'playwright-mcp': {
450
450
  type: 'boolean',
451
- description: 'Enable Playwright MCP server connection for this session (enabled by default). Use --no-playwright-mcp to physically disable the Playwright MCP server without affecting the global MCP registration. When disabled, also disables --prompt-playwright-mcp and --playwright-mcp-auto-cleanup. Supported for --tool claude, --tool codex, --tool opencode, --tool agent, and --tool qwen; for --tool gemini this controls prompt hints and cleanup only.',
451
+ description: 'Enable Playwright MCP server connection for this session (enabled by default). Use --no-playwright-mcp to physically disable the Playwright MCP server without affecting the global MCP registration. When disabled, also disables --prompt-playwright-mcp and --playwright-mcp-auto-cleanup. Supported for --tool claude, --tool codex, --tool opencode, --tool agent, --tool qwen, and --tool gemini.',
452
452
  default: true,
453
453
  },
454
454
  'playwright-mcp-auto-cleanup': {
@@ -468,9 +468,15 @@ export const SOLVE_OPTION_DEFINITIONS = {
468
468
  },
469
469
  'prompt-subagents-via-agent-commander': {
470
470
  type: 'boolean',
471
- description: 'Guide AI to use agent-commander CLI (start-agent) instead of native tool-specific delegation for subagent work. Allows using any supported agent type (claude, opencode, codex, agent, qwen) with a unified API. Supported for --tool claude and --tool codex and requires agent-commander to be installed.',
471
+ description: 'Guide AI to use agent-commander CLI (start-agent) instead of native tool-specific delegation for subagent work. Allows using any supported agent type (claude, opencode, codex, agent, qwen, gemini) with a unified API. Supported for --tool claude and --tool codex and requires agent-commander to be installed.',
472
472
  default: false,
473
473
  },
474
+ 'use-agent-commander': {
475
+ type: 'boolean',
476
+ description: '[EXPERIMENTAL] Execute the selected AI tool through agent-commander instead of the embedded hive-mind tool adapter. Disabled by default.',
477
+ default: false,
478
+ hidden: true,
479
+ },
474
480
  'auto-init-repository': {
475
481
  type: 'boolean',
476
482
  description: 'Automatically initialize empty repositories by creating a simple README.md file. Only works when you have write access to the repository. This allows branch creation and pull request workflows to proceed on repositories that have no commits.',
package/src/solve.mjs CHANGED
@@ -89,8 +89,13 @@ 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
- if (argv.tool === 'opencode') {
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;
96
101
  } else if (argv.tool === 'gemini') {
@@ -667,103 +672,26 @@ try {
667
672
 
668
673
  // Execute tool command with all prompts and settings
669
674
  let toolResult;
670
- if (argv.tool === 'opencode') {
671
- const opencodeLib = await import('./opencode.lib.mjs');
672
- const { executeOpenCode, checkPlaywrightMcpAvailability: checkOpenCodePlaywrightMcp } = opencodeLib;
673
- const opencodePath = process.env.OPENCODE_PATH || 'opencode';
674
- await resolvePlaywrightMcp(checkOpenCodePlaywrightMcp);
675
675
 
676
- toolResult = await executeOpenCode({
677
- issueUrl,
678
- issueNumber,
679
- prNumber,
680
- prUrl,
681
- branchName,
682
- tempDir,
683
- workspaceTmpDir,
684
- isContinueMode,
685
- mergeStateStatus,
686
- forkedRepo,
687
- feedbackLines,
688
- forkActionsUrl,
689
- owner,
690
- repo,
691
- argv,
692
- log,
693
- setLogFile,
694
- getLogFile,
695
- formatAligned,
696
- getResourceSnapshot,
697
- opencodePath,
698
- $,
699
- });
700
- } else if (argv.tool === 'codex') {
701
- const codexLib = await import('./codex.lib.mjs');
702
- const { executeCodex, checkPlaywrightMcpAvailability } = codexLib;
703
- const codexPath = process.env.CODEX_PATH || 'codex';
704
- 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
+ }
705
682
 
706
- toolResult = await executeCodex({
707
- issueUrl,
708
- issueNumber,
709
- prNumber,
710
- prUrl,
711
- branchName,
712
- tempDir,
713
- workspaceTmpDir,
714
- isContinueMode,
715
- mergeStateStatus,
716
- forkedRepo,
717
- feedbackLines,
718
- forkActionsUrl,
719
- owner,
720
- repo,
721
- argv,
722
- log,
723
- setLogFile,
724
- getLogFile,
725
- formatAligned,
726
- getResourceSnapshot,
727
- codexPath,
728
- $,
729
- });
730
- } else if (argv.tool === 'agent') {
731
- const agentLib = await import('./agent.lib.mjs');
732
- const { executeAgent, checkPlaywrightMcpAvailability: checkAgentPlaywrightMcp } = agentLib;
733
- const agentPath = process.env.AGENT_PATH || 'agent';
734
- 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
+ }
735
690
 
736
- toolResult = await executeAgent({
737
- issueUrl,
738
- issueNumber,
739
- prNumber,
740
- prUrl,
741
- branchName,
742
- tempDir,
743
- workspaceTmpDir,
744
- isContinueMode,
745
- mergeStateStatus,
746
- forkedRepo,
747
- feedbackLines,
748
- forkActionsUrl,
749
- owner,
750
- repo,
751
- argv,
752
- log,
753
- setLogFile,
754
- getLogFile,
755
- formatAligned,
756
- getResourceSnapshot,
757
- agentPath,
758
- $,
759
- });
760
- } else if (argv.tool === 'gemini') {
761
- const geminiLib = await import('./gemini.lib.mjs');
762
- const { executeGemini, checkPlaywrightMcpAvailability: checkGeminiPlaywrightMcp } = geminiLib;
763
- const geminiPath = process.env.GEMINI_PATH || 'gemini';
764
- await resolvePlaywrightMcp(checkGeminiPlaywrightMcp);
691
+ await log(`\n[agent-commander] Using agent-commander for ${argv.tool || 'claude'} execution`);
692
+ await agentCommanderLib.resolvePlaywrightMcpForAgentCommander({ argv, log, tool: argv.tool || 'claude' });
765
693
 
766
- toolResult = await executeGemini({
694
+ toolResult = await agentCommanderLib.executeWithAgentCommander({
767
695
  issueUrl,
768
696
  issueNumber,
769
697
  prNumber,
@@ -784,16 +712,20 @@ try {
784
712
  getLogFile,
785
713
  formatAligned,
786
714
  getResourceSnapshot,
787
- geminiPath,
788
715
  $,
789
716
  });
790
- } else if (argv.tool === 'qwen') {
791
- const qwenLib = await import('./qwen.lib.mjs');
792
- const { executeQwen, checkPlaywrightMcpAvailability: checkQwenPlaywrightMcp } = qwenLib;
793
- const qwenPath = process.env.QWEN_PATH || 'qwen';
794
- await resolvePlaywrightMcp(checkQwenPlaywrightMcp);
795
-
796
- toolResult = await executeQwen({
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]({
797
729
  issueUrl,
798
730
  issueNumber,
799
731
  prNumber,
@@ -814,7 +746,7 @@ try {
814
746
  getLogFile,
815
747
  formatAligned,
816
748
  getResourceSnapshot,
817
- qwenPath,
749
+ [toolDispatch.pathKey]: process.env[toolDispatch.envVar] || toolDispatch.defaultBin,
818
750
  $,
819
751
  });
820
752
  } else {
@@ -183,7 +183,34 @@ export const executeToolIteration = async params => {
183
183
  await cascadePlaywrightMcpDisable(argv, log);
184
184
 
185
185
  let toolResult;
186
- if (argv.tool === 'opencode') {
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;
@@ -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.tool === 'opencode') {
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);
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, gemini, 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');
@@ -13,8 +13,8 @@ export const TOOL_SOLVE_COMMAND_ALIASES = Object.freeze({
13
13
  codex: 'codex',
14
14
  opencode: 'opencode',
15
15
  agent: 'agent',
16
- gemini: 'gemini',
17
16
  qwen: 'qwen',
17
+ gemini: 'gemini',
18
18
  });
19
19
 
20
20
  export const SOLVE_COMMAND_NAMES = Object.freeze(['solve', 'do', 'continue', ...Object.keys(TOOL_SOLVE_COMMAND_ALIASES)]);
@@ -76,21 +76,21 @@ export async function getRunningCodexProcesses(verbose = false) {
76
76
  }
77
77
 
78
78
  /**
79
- * Count running gemini processes.
79
+ * Count running qwen processes.
80
80
  * @param {boolean} verbose - Whether to log verbose output
81
81
  * @returns {Promise<{count: number, processes: string[]}>}
82
82
  */
83
- export async function getRunningGeminiProcesses(verbose = false) {
84
- return getRunningProcesses('gemini', verbose);
83
+ export async function getRunningQwenProcesses(verbose = false) {
84
+ return getRunningProcesses('qwen', verbose);
85
85
  }
86
86
 
87
87
  /**
88
- * Count running qwen processes.
88
+ * Count running gemini processes.
89
89
  * @param {boolean} verbose - Whether to log verbose output
90
90
  * @returns {Promise<{count: number, processes: string[]}>}
91
91
  */
92
- export async function getRunningQwenProcesses(verbose = false) {
93
- return getRunningProcesses('qwen', verbose);
92
+ export async function getRunningGeminiProcesses(verbose = false) {
93
+ return getRunningProcesses('gemini', verbose);
94
94
  }
95
95
 
96
96
  /**
@@ -163,10 +163,10 @@ export function formatWaitingReason(metric, currentValue, threshold) {
163
163
  return 'Claude process is already running';
164
164
  case 'codex_running':
165
165
  return 'Codex process is already running';
166
- case 'gemini_running':
167
- return 'Gemini process is already running';
168
166
  case 'qwen_running':
169
167
  return 'Qwen Code process is already running';
168
+ case 'gemini_running':
169
+ return 'Gemini CLI process is already running';
170
170
  default:
171
171
  return `${metric} threshold exceeded`;
172
172
  }
@@ -149,8 +149,8 @@ export class SolveQueue {
149
149
  claude: [],
150
150
  agent: [],
151
151
  codex: [],
152
- gemini: [],
153
152
  qwen: [],
153
+ gemini: [],
154
154
  };
155
155
  this.processing = new Map();
156
156
  this.completed = [];
@@ -162,8 +162,8 @@ export class SolveQueue {
162
162
  claude: null,
163
163
  agent: null,
164
164
  codex: null,
165
- gemini: null,
166
165
  qwen: null,
166
+ gemini: null,
167
167
  };
168
168
  // Legacy: keep for compatibility with existing code that uses lastStartTime
169
169
  this.lastStartTime = null;
@@ -566,12 +566,12 @@ export class SolveQueue {
566
566
  const claudeProcessCount = externalProcessing.byTool.claude || 0;
567
567
  const codexProcessCount = externalProcessing.byTool.codex || 0;
568
568
  const agentProcessCount = externalProcessing.byTool.agent || 0;
569
- const geminiProcessCount = externalProcessing.byTool.gemini || 0;
570
569
  const qwenProcessCount = externalProcessing.byTool.qwen || 0;
570
+ const geminiProcessCount = externalProcessing.byTool.gemini || 0;
571
571
  const hasRunningClaude = claudeProcessCount > 0;
572
572
  const hasRunningCodex = codexProcessCount > 0;
573
- const hasRunningGemini = geminiProcessCount > 0;
574
573
  const hasRunningQwen = qwenProcessCount > 0;
574
+ const hasRunningGemini = geminiProcessCount > 0;
575
575
 
576
576
  // Calculate total processing count for system resources (all tools)
577
577
  // System resources (RAM, CPU, disk) apply to all tools
@@ -583,8 +583,8 @@ export class SolveQueue {
583
583
  // See: https://github.com/link-assistant/hive-mind/issues/1159
584
584
  const claudeProcessingCount = this.getProcessingCountByTool('claude');
585
585
  const codexProcessingCount = this.getProcessingCountByTool('codex');
586
- const geminiProcessingCount = this.getProcessingCountByTool('gemini');
587
586
  const qwenProcessingCount = this.getProcessingCountByTool('qwen');
587
+ const geminiProcessingCount = this.getProcessingCountByTool('gemini');
588
588
 
589
589
  // Track claude_running as a metric (but don't add to reasons yet)
590
590
  if (hasRunningClaude) {
@@ -593,12 +593,12 @@ export class SolveQueue {
593
593
  if (hasRunningCodex) {
594
594
  this.recordThrottle('codex_running');
595
595
  }
596
- if (hasRunningGemini) {
597
- this.recordThrottle('gemini_running');
598
- }
599
596
  if (hasRunningQwen) {
600
597
  this.recordThrottle('qwen_running');
601
598
  }
599
+ if (hasRunningGemini) {
600
+ this.recordThrottle('gemini_running');
601
+ }
602
602
 
603
603
  // Check system resources with strategy support
604
604
  // System resources apply to ALL tools, not just Claude
@@ -646,12 +646,12 @@ export class SolveQueue {
646
646
  if (tool === 'codex' && hasRunningCodex && reasons.length > 0) {
647
647
  reasons.push(formatWaitingReason('codex_running', codexProcessCount, 0) + ` (${codexProcessCount} processes)`);
648
648
  }
649
- if (tool === 'gemini' && hasRunningGemini && reasons.length > 0) {
650
- reasons.push(formatWaitingReason('gemini_running', geminiProcessCount, 0) + ` (${geminiProcessCount} processes)`);
651
- }
652
649
  if (tool === 'qwen' && hasRunningQwen && reasons.length > 0) {
653
650
  reasons.push(formatWaitingReason('qwen_running', qwenProcessCount, 0) + ` (${qwenProcessCount} processes)`);
654
651
  }
652
+ if (tool === 'gemini' && hasRunningGemini && reasons.length > 0) {
653
+ reasons.push(formatWaitingReason('gemini_running', geminiProcessCount, 0) + ` (${geminiProcessCount} processes)`);
654
+ }
655
655
 
656
656
  const canStart = reasons.length === 0 && !rejected;
657
657
 
@@ -673,14 +673,14 @@ export class SolveQueue {
673
673
  claudeProcesses: claudeProcessCount,
674
674
  codexProcesses: codexProcessCount,
675
675
  agentProcesses: agentProcessCount,
676
- geminiProcesses: geminiProcessCount,
677
676
  qwenProcesses: qwenProcessCount,
677
+ geminiProcesses: geminiProcessCount,
678
678
  isolatedProcesses: externalProcessing.isolatedTotal,
679
679
  totalProcessing,
680
680
  claudeProcessingCount,
681
681
  codexProcessingCount,
682
- geminiProcessingCount,
683
682
  qwenProcessingCount,
683
+ geminiProcessingCount,
684
684
  };
685
685
  }
686
686
 
@@ -1442,8 +1442,8 @@ export default {
1442
1442
  getRunningClaudeProcesses,
1443
1443
  getRunningAgentProcesses,
1444
1444
  getRunningCodexProcesses,
1445
- getRunningGeminiProcesses,
1446
1445
  getRunningQwenProcesses,
1446
+ getRunningGeminiProcesses,
1447
1447
  getRunningIsolatedSessions,
1448
1448
  createQueueExecuteCallback,
1449
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
  };