@link-assistant/hive-mind 1.36.1 ā 1.37.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 +7 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +11 -11
- package/src/config.lib.mjs +19 -4
- package/src/github.lib.mjs +9 -14
- package/src/hive.config.lib.mjs +1 -1
- package/src/hive.mjs +20 -19
- package/src/models/index.mjs +3 -2
- package/src/solve.config.lib.mjs +30 -2
- package/src/solve.mjs +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.37.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f02c1fc: fix synthetic model appearing in PR comments by filtering internal Claude CLI router entries (Issue #1486)
|
|
8
|
+
- dd87b23: Add opusplan model support and --plan-model option for flexible plan/execution model pairing
|
|
9
|
+
|
|
3
10
|
## 1.36.1
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -859,6 +859,12 @@ ps axjf
|
|
|
859
859
|
pkill -f gh-issue-solver-1773073065743
|
|
860
860
|
```
|
|
861
861
|
|
|
862
|
+
### Kill all headless browsers spawned by ms-playwright
|
|
863
|
+
|
|
864
|
+
```bash
|
|
865
|
+
pkill -f ms-playwright/chromium_headless_shell-1200
|
|
866
|
+
```
|
|
867
|
+
|
|
862
868
|
That can be done, but not recommended as reboot have better effect.
|
|
863
869
|
|
|
864
870
|
## š License
|
package/package.json
CHANGED
package/src/claude.lib.mjs
CHANGED
|
@@ -586,13 +586,13 @@ export const calculateSessionTokens = async (sessionId, tempDir) => {
|
|
|
586
586
|
// Read the entire file
|
|
587
587
|
const fileContent = await fs.readFile(sessionFile, 'utf8');
|
|
588
588
|
const lines = fileContent.trim().split('\n');
|
|
589
|
-
// Parse each line and accumulate token counts per model
|
|
590
589
|
for (const line of lines) {
|
|
591
590
|
if (!line.trim()) continue;
|
|
592
591
|
try {
|
|
593
592
|
const entry = JSON.parse(line);
|
|
594
593
|
if (entry.message && entry.message.usage && entry.message.model) {
|
|
595
594
|
const model = entry.message.model;
|
|
595
|
+
if (model.startsWith('<') && model.endsWith('>')) continue; // Issue #1486: skip <synthetic> etc.
|
|
596
596
|
const usage = entry.message.usage;
|
|
597
597
|
// Initialize model entry if it doesn't exist
|
|
598
598
|
if (!modelUsage[model]) {
|
|
@@ -842,11 +842,12 @@ export const executeClaudeCommand = async params => {
|
|
|
842
842
|
} else if (argv.interactiveMode) {
|
|
843
843
|
await log('ā ļø Interactive mode: Disabled - missing PR info (owner/repo/prNumber)', { verbose: true });
|
|
844
844
|
}
|
|
845
|
-
// Build claude command with optional resume flag
|
|
846
845
|
let execCommand;
|
|
847
846
|
const mappedModel = mapModelToId(argv.model);
|
|
848
|
-
|
|
849
|
-
|
|
847
|
+
const resolvedPlanModel = argv.planModel ? mapModelToId(argv.planModel) : undefined; // Issue #1223
|
|
848
|
+
const effectiveModel = resolvedPlanModel ? 'opusplan' : mappedModel;
|
|
849
|
+
const resolvedExecutionModel = resolvedPlanModel ? mappedModel : undefined;
|
|
850
|
+
let claudeArgs = `--output-format stream-json --verbose --dangerously-skip-permissions --model ${effectiveModel}`;
|
|
850
851
|
if (argv.resume) {
|
|
851
852
|
await log(`š Resuming from session: ${argv.resume}`);
|
|
852
853
|
claudeArgs = `--resume ${argv.resume} ${claudeArgs}`;
|
|
@@ -862,22 +863,22 @@ export const executeClaudeCommand = async params => {
|
|
|
862
863
|
}
|
|
863
864
|
try {
|
|
864
865
|
const { thinkingBudget: resolvedThinkingBudget, thinkLevel, isNewVersion, maxBudget } = await resolveThinkingSettings(argv, log);
|
|
865
|
-
const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model:
|
|
866
|
+
const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model: effectiveModel, thinkLevel, maxBudget, planModel: resolvedPlanModel, executionModel: resolvedExecutionModel });
|
|
866
867
|
if (argv.verbose) claudeEnv.ANTHROPIC_LOG = 'debug';
|
|
867
|
-
const modelMaxOutputTokens = getMaxOutputTokensForModel(
|
|
868
|
+
const modelMaxOutputTokens = getMaxOutputTokensForModel(effectiveModel);
|
|
868
869
|
if (argv.verbose) {
|
|
869
870
|
await log(`š CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${modelMaxOutputTokens}, MCP_TIMEOUT: ${claudeCode.mcpTimeout}ms, MCP_TOOL_TIMEOUT: ${claudeCode.mcpToolTimeout}ms, ANTHROPIC_LOG: debug`, { verbose: true });
|
|
871
|
+
if (resolvedPlanModel) await log(`š opusplan: plan=${resolvedPlanModel}, exec=${resolvedExecutionModel}`, { verbose: true });
|
|
870
872
|
if (resolvedThinkingBudget !== undefined) await log(`š MAX_THINKING_TOKENS: ${resolvedThinkingBudget}`, { verbose: true });
|
|
871
873
|
if (claudeEnv.CLAUDE_CODE_EFFORT_LEVEL) await log(`š CLAUDE_CODE_EFFORT_LEVEL: ${claudeEnv.CLAUDE_CODE_EFFORT_LEVEL}`, { verbose: true });
|
|
872
874
|
if (!isNewVersion && thinkLevel) await log(`š Thinking level (via keywords): ${thinkLevel}`, { verbose: true });
|
|
873
875
|
}
|
|
876
|
+
const simpleEscapedSystem = systemPrompt.replace(/"/g, '\\"');
|
|
874
877
|
if (argv.resume) {
|
|
875
878
|
const simpleEscapedPrompt = prompt.replace(/"/g, '\\"');
|
|
876
|
-
|
|
877
|
-
execCommand = $({ cwd: tempDir, mirror: false, env: claudeEnv })`${claudePath} --resume ${argv.resume} --output-format stream-json --verbose --dangerously-skip-permissions --model ${mappedModel} -p "${simpleEscapedPrompt}" --append-system-prompt "${simpleEscapedSystem}"`;
|
|
879
|
+
execCommand = $({ cwd: tempDir, mirror: false, env: claudeEnv })`${claudePath} --resume ${argv.resume} --output-format stream-json --verbose --dangerously-skip-permissions --model ${effectiveModel} -p "${simpleEscapedPrompt}" --append-system-prompt "${simpleEscapedSystem}"`;
|
|
878
880
|
} else {
|
|
879
|
-
|
|
880
|
-
execCommand = $({ cwd: tempDir, stdin: prompt, mirror: false, env: claudeEnv })`${claudePath} --output-format stream-json --verbose --dangerously-skip-permissions --model ${mappedModel} --append-system-prompt "${simpleEscapedSystem}"`;
|
|
881
|
+
execCommand = $({ cwd: tempDir, stdin: prompt, mirror: false, env: claudeEnv })`${claudePath} --output-format stream-json --verbose --dangerously-skip-permissions --model ${effectiveModel} --append-system-prompt "${simpleEscapedSystem}"`;
|
|
881
882
|
}
|
|
882
883
|
await log(`${formatAligned('š', 'Command details:', '')}`);
|
|
883
884
|
await log(formatAligned('š', 'Working directory:', tempDir, 2));
|
|
@@ -1262,7 +1263,6 @@ export const executeClaudeCommand = async params => {
|
|
|
1262
1263
|
sessionId,
|
|
1263
1264
|
resumeCommand: argv.url ? `${process.argv[0]} ${process.argv[1]} --auto-continue ${argv.url}` : null,
|
|
1264
1265
|
});
|
|
1265
|
-
|
|
1266
1266
|
for (const line of messageLines) {
|
|
1267
1267
|
await log(line, { level: 'warning' });
|
|
1268
1268
|
}
|
package/src/config.lib.mjs
CHANGED
|
@@ -174,9 +174,10 @@ export const DEFAULT_MAX_THINKING_BUDGET_OPUS_46 = parseIntWithDefault('HIVE_MIN
|
|
|
174
174
|
export const isOpus46OrLater = model => {
|
|
175
175
|
if (!model) return false;
|
|
176
176
|
const normalizedModel = model.toLowerCase();
|
|
177
|
-
// Check for explicit opus-4-6 or later versions
|
|
177
|
+
// Check for explicit opus-4-6 or later versions, or opusplan (Issue #1223)
|
|
178
178
|
// Note: The 'opus' alias now maps to Opus 4.6 (Issue #1433), so we also check for the alias directly
|
|
179
|
-
|
|
179
|
+
// opusplan uses Opus for planning, so it should get Opus-level settings
|
|
180
|
+
return normalizedModel === 'opus' || normalizedModel === 'opusplan' || normalizedModel.includes('opus-4-6') || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-5');
|
|
180
181
|
};
|
|
181
182
|
|
|
182
183
|
/**
|
|
@@ -318,6 +319,10 @@ export const supportsThinkingBudget = (version, minVersion = '2.1.12') => {
|
|
|
318
319
|
// Also sets MCP_TIMEOUT and MCP_TOOL_TIMEOUT for MCP tool execution (see issue #1066)
|
|
319
320
|
// Supports model-specific max output tokens for Opus 4.6 (Issue #1221)
|
|
320
321
|
// Sets CLAUDE_CODE_EFFORT_LEVEL for Opus 4.6 models (Issue #1238)
|
|
322
|
+
// Supports planModel/executionModel for opusplan mode (Issue #1223)
|
|
323
|
+
// See: https://code.claude.com/docs/en/model-config
|
|
324
|
+
// ANTHROPIC_DEFAULT_OPUS_MODEL ā model used in plan mode (and for 'opus' alias)
|
|
325
|
+
// ANTHROPIC_DEFAULT_SONNET_MODEL ā model used in execution mode (and for 'sonnet' alias)
|
|
321
326
|
export const getClaudeEnv = (options = {}) => {
|
|
322
327
|
// Get max output tokens based on model (Issue #1221)
|
|
323
328
|
const maxOutputTokens = options.model ? getMaxOutputTokensForModel(options.model) : claudeCode.maxOutputTokens;
|
|
@@ -327,8 +332,6 @@ export const getClaudeEnv = (options = {}) => {
|
|
|
327
332
|
CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(maxOutputTokens),
|
|
328
333
|
// MCP timeout configurations to prevent tool calls from hanging indefinitely
|
|
329
334
|
// See: https://github.com/link-assistant/hive-mind/issues/1066
|
|
330
|
-
// MCP_TIMEOUT: Timeout for MCP server startup
|
|
331
|
-
// MCP_TOOL_TIMEOUT: Timeout for MCP tool execution (the one that prevents stuck tools)
|
|
332
335
|
MCP_TIMEOUT: String(claudeCode.mcpTimeout),
|
|
333
336
|
MCP_TOOL_TIMEOUT: String(claudeCode.mcpToolTimeout),
|
|
334
337
|
};
|
|
@@ -354,6 +357,17 @@ export const getClaudeEnv = (options = {}) => {
|
|
|
354
357
|
env.CLAUDE_CODE_EFFORT_LEVEL = effortLevel;
|
|
355
358
|
}
|
|
356
359
|
}
|
|
360
|
+
// Set ANTHROPIC_DEFAULT_OPUS_MODEL when planModel is specified (Issue #1223)
|
|
361
|
+
// This tells Claude Code which model to use during plan mode in opusplan
|
|
362
|
+
if (options.planModel) {
|
|
363
|
+
env.ANTHROPIC_DEFAULT_OPUS_MODEL = String(options.planModel);
|
|
364
|
+
}
|
|
365
|
+
// Set ANTHROPIC_DEFAULT_SONNET_MODEL when executionModel is specified (Issue #1223)
|
|
366
|
+
// This tells Claude Code which model to use during execution mode in opusplan
|
|
367
|
+
// Enables combinations like --plan-model opus --model haiku
|
|
368
|
+
if (options.executionModel) {
|
|
369
|
+
env.ANTHROPIC_DEFAULT_SONNET_MODEL = String(options.executionModel);
|
|
370
|
+
}
|
|
357
371
|
|
|
358
372
|
return env;
|
|
359
373
|
};
|
|
@@ -413,6 +427,7 @@ const defaultAvailableModels = `(
|
|
|
413
427
|
opus
|
|
414
428
|
sonnet
|
|
415
429
|
haiku
|
|
430
|
+
opusplan
|
|
416
431
|
)`;
|
|
417
432
|
|
|
418
433
|
export const modelConfig = {
|
package/src/github.lib.mjs
CHANGED
|
@@ -390,9 +390,7 @@ export async function attachLogToGitHub(options) {
|
|
|
390
390
|
if (useLargeFileMode && verbose) {
|
|
391
391
|
await log(` š Large log file (${Math.round(logStats.size / 1024 / 1024)}MB), will use gh-upload-log`, { verbose: true });
|
|
392
392
|
}
|
|
393
|
-
|
|
394
|
-
let totalCostUSD = publicPricingEstimate;
|
|
395
|
-
// Issue #1225: Collect actual model IDs from Claude session JSON output
|
|
393
|
+
let totalCostUSD = publicPricingEstimate; // Issue #1225: token usage + actual model IDs
|
|
396
394
|
let actualModelIds = null;
|
|
397
395
|
if (totalCostUSD === null && sessionId && tempDir && !errorMessage) {
|
|
398
396
|
try {
|
|
@@ -401,23 +399,15 @@ export async function attachLogToGitHub(options) {
|
|
|
401
399
|
if (tokenUsage) {
|
|
402
400
|
if (tokenUsage.totalCostUSD !== null && tokenUsage.totalCostUSD !== undefined) {
|
|
403
401
|
totalCostUSD = tokenUsage.totalCostUSD;
|
|
404
|
-
if (verbose) {
|
|
405
|
-
await log(` š° Calculated cost: $${totalCostUSD.toFixed(6)}`, { verbose: true });
|
|
406
|
-
}
|
|
402
|
+
if (verbose) await log(` š° Calculated cost: $${totalCostUSD.toFixed(6)}`, { verbose: true });
|
|
407
403
|
}
|
|
408
|
-
// Extract actual model IDs from session data (Issue #1225)
|
|
409
404
|
if (tokenUsage.modelUsage && Object.keys(tokenUsage.modelUsage).length > 0) {
|
|
410
405
|
actualModelIds = Object.keys(tokenUsage.modelUsage);
|
|
411
|
-
if (verbose) {
|
|
412
|
-
await log(` š¤ Actual models used: ${actualModelIds.join(', ')}`, { verbose: true });
|
|
413
|
-
}
|
|
406
|
+
if (verbose) await log(` š¤ Actual models used: ${actualModelIds.join(', ')}`, { verbose: true });
|
|
414
407
|
}
|
|
415
408
|
}
|
|
416
409
|
} catch (tokenError) {
|
|
417
|
-
|
|
418
|
-
if (verbose) {
|
|
419
|
-
await log(` ā ļø Could not calculate token cost: ${tokenError.message}`, { verbose: true });
|
|
420
|
-
}
|
|
410
|
+
if (verbose) await log(` ā ļø Could not calculate token cost: ${tokenError.message}`, { verbose: true });
|
|
421
411
|
}
|
|
422
412
|
}
|
|
423
413
|
// Issue #1454: Use resultModelUsage from result JSON when it has more models (includes subagent models)
|
|
@@ -433,6 +423,11 @@ export async function attachLogToGitHub(options) {
|
|
|
433
423
|
if (!actualModelIds && pricingInfo?.modelId) {
|
|
434
424
|
actualModelIds = [pricingInfo.modelId];
|
|
435
425
|
}
|
|
426
|
+
// Issue #1486: Filter out internal/synthetic model entries (e.g., "<synthetic>" from Claude CLI's inference router)
|
|
427
|
+
if (actualModelIds) {
|
|
428
|
+
actualModelIds = actualModelIds.filter(id => !(id.startsWith('<') && id.endsWith('>')));
|
|
429
|
+
if (actualModelIds.length === 0) actualModelIds = null;
|
|
430
|
+
}
|
|
436
431
|
// Issue #1225: Fetch model information for comment using actual models from CLI output
|
|
437
432
|
let modelInfoString = '';
|
|
438
433
|
if (requestedModel || tool || actualModelIds) {
|
package/src/hive.config.lib.mjs
CHANGED
package/src/hive.mjs
CHANGED
|
@@ -152,9 +152,7 @@ if (isDirectExecution) {
|
|
|
152
152
|
// Strategy 2: Fallback to gh api --paginate approach (comprehensive but slower)
|
|
153
153
|
await log(' š Using gh api --paginate approach for comprehensive coverage...', { verbose: true });
|
|
154
154
|
|
|
155
|
-
//
|
|
156
|
-
// This approach uses the GitHub API directly to fetch all repositories without any limits
|
|
157
|
-
// Include isArchived field to filter out archived repositories
|
|
155
|
+
// Get list of ALL repositories using gh api with --paginate (includes isArchived for filtering)
|
|
158
156
|
let repoListCmd;
|
|
159
157
|
if (scope === 'organization') {
|
|
160
158
|
repoListCmd = `gh api orgs/${owner}/repos --paginate --jq '.[] | {name: .name, owner: .owner.login, isArchived: .archived}'`;
|
|
@@ -436,8 +434,6 @@ if (isDirectExecution) {
|
|
|
436
434
|
initializeExitHandler(getAbsoluteLogPath, log);
|
|
437
435
|
installGlobalExitHandlers();
|
|
438
436
|
|
|
439
|
-
// Unhandled error handlers are now managed by exit-handler.lib.mjs
|
|
440
|
-
|
|
441
437
|
// Validate GitHub URL requirement
|
|
442
438
|
if (!githubUrl) {
|
|
443
439
|
await log('ā GitHub URL is required', { level: 'error' });
|
|
@@ -470,18 +466,28 @@ if (isDirectExecution) {
|
|
|
470
466
|
}
|
|
471
467
|
}
|
|
472
468
|
|
|
473
|
-
//
|
|
474
|
-
|
|
469
|
+
// --plan flag expansion: shortcut for --plan-model opus --worker-model sonnet (Issue #1223)
|
|
470
|
+
if (argv.plan) {
|
|
471
|
+
if (!rawArgs.includes('--plan-model')) argv.planModel = 'opus';
|
|
472
|
+
if (!rawArgs.includes('--model') && !rawArgs.includes('-m') && !rawArgs.includes('--worker-model')) argv.model = 'sonnet';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Validate model names EARLY (simple string check, always runs)
|
|
475
476
|
const tool = argv.tool || 'claude';
|
|
476
477
|
await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
478
|
+
if (argv.planModel) {
|
|
479
|
+
if (tool !== 'claude') {
|
|
480
|
+
await log(`ā --plan-model is only supported with --tool claude (current tool: ${tool})`, { level: 'error' });
|
|
481
|
+
await safeExit(1, '--plan-model requires --tool claude');
|
|
482
|
+
}
|
|
483
|
+
await validateAndExitOnInvalidModel(argv.planModel, tool, safeExit);
|
|
484
|
+
}
|
|
477
485
|
|
|
478
486
|
// Handle -s (--skip-issues-with-prs) and --auto-continue interaction
|
|
479
|
-
// Detect if user explicitly passed --auto-continue or --no-auto-continue
|
|
480
487
|
const hasExplicitAutoContinue = rawArgs.includes('--auto-continue');
|
|
481
488
|
const hasExplicitNoAutoContinue = rawArgs.includes('--no-auto-continue');
|
|
482
489
|
|
|
483
490
|
if (argv.skipIssuesWithPrs) {
|
|
484
|
-
// If user explicitly passed --auto-continue with -s, that's a conflict
|
|
485
491
|
if (hasExplicitAutoContinue) {
|
|
486
492
|
await log('ā Conflicting options: --skip-issues-with-prs and --auto-continue cannot be used together', {
|
|
487
493
|
level: 'error',
|
|
@@ -492,8 +498,7 @@ if (isDirectExecution) {
|
|
|
492
498
|
await safeExit(1, 'Error occurred');
|
|
493
499
|
}
|
|
494
500
|
|
|
495
|
-
//
|
|
496
|
-
// This is because -s means "skip issues with PRs" which conflicts with auto-continue
|
|
501
|
+
// -s implies disabling auto-continue unless explicitly set
|
|
497
502
|
if (!hasExplicitNoAutoContinue) {
|
|
498
503
|
argv.autoContinue = false;
|
|
499
504
|
}
|
|
@@ -778,10 +783,8 @@ if (isDirectExecution) {
|
|
|
778
783
|
}
|
|
779
784
|
if (argv.skipToolConnectionCheck || argv.toolConnectionCheck === false) args.push('--skip-tool-connection-check');
|
|
780
785
|
if (argv.dryRun) args.push('--dry-run');
|
|
781
|
-
if (argv.autoCleanup) args.push('--auto-cleanup');
|
|
782
|
-
|
|
783
|
-
// Options already handled above or deprecated aliases (skip in generic loop)
|
|
784
|
-
const SKIP_AUTO_FORWARD = new Set(['model', 'base-branch', 'skip-tool-connection-check', 'tool-connection-check', 'skip-tool-check', 'skip-claude-check', 'tool-check', 'dry-run', 'auto-cleanup']);
|
|
786
|
+
if (argv.autoCleanup) args.push('--auto-cleanup');
|
|
787
|
+
const SKIP_AUTO_FORWARD = new Set(['model', 'worker-model', 'base-branch', 'skip-tool-connection-check', 'tool-connection-check', 'skip-tool-check', 'skip-claude-check', 'tool-check', 'dry-run', 'auto-cleanup']);
|
|
785
788
|
|
|
786
789
|
for (const optionName of getSolvePassthroughOptionNames()) {
|
|
787
790
|
if (SKIP_AUTO_FORWARD.has(optionName)) continue;
|
|
@@ -1431,8 +1434,7 @@ if (isDirectExecution) {
|
|
|
1431
1434
|
await safeExit(0, 'Process completed');
|
|
1432
1435
|
}
|
|
1433
1436
|
|
|
1434
|
-
//
|
|
1435
|
-
// validateClaudeConnection is now imported from lib.mjs
|
|
1437
|
+
// validateClaudeConnection is imported from lib.mjs
|
|
1436
1438
|
|
|
1437
1439
|
// Handle graceful shutdown
|
|
1438
1440
|
process.on('SIGINT', () => gracefulShutdown('interrupt'));
|
|
@@ -1484,8 +1486,7 @@ if (isDirectExecution) {
|
|
|
1484
1486
|
await safeExit(1, 'Error occurred');
|
|
1485
1487
|
}
|
|
1486
1488
|
} catch (fatalError) {
|
|
1487
|
-
// Handle
|
|
1488
|
-
// This prevents silent failures when the script hangs or crashes
|
|
1489
|
+
// Handle fatal errors during initialization or execution
|
|
1489
1490
|
console.error('\nā Fatal error occurred during hive initialization or execution');
|
|
1490
1491
|
console.error(` ${fatalError.message || fatalError}`);
|
|
1491
1492
|
if (fatalError.stack) {
|
package/src/models/index.mjs
CHANGED
|
@@ -30,6 +30,7 @@ export const claudeModels = {
|
|
|
30
30
|
haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
31
31
|
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
32
32
|
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
33
|
+
opusplan: 'opusplan', // Special mode: Opus for planning, Sonnet for execution (Issue #1223)
|
|
33
34
|
// Shorter version aliases (Issue #1221, Issue #1329 - PR comment feedback)
|
|
34
35
|
'sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 short alias (Issue #1329)
|
|
35
36
|
'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias
|
|
@@ -258,7 +259,7 @@ export const isModelCompatibleWithTool = (tool, model) => {
|
|
|
258
259
|
|
|
259
260
|
switch (tool) {
|
|
260
261
|
case 'claude':
|
|
261
|
-
return mappedModel.startsWith('claude-');
|
|
262
|
+
return mappedModel.startsWith('claude-') || mappedModel === 'opusplan';
|
|
262
263
|
case 'agent':
|
|
263
264
|
return mappedModel.includes('/') || Object.keys(agentModels).includes(model);
|
|
264
265
|
case 'opencode':
|
|
@@ -293,7 +294,7 @@ export const getValidModelsForTool = tool => {
|
|
|
293
294
|
// Primary (non-alias, non-deprecated) short names shown in CLI help descriptions
|
|
294
295
|
// These are the recommended model names users should see in --model help text
|
|
295
296
|
export const primaryModelNames = {
|
|
296
|
-
claude: ['opus', 'sonnet', 'haiku'],
|
|
297
|
+
claude: ['opus', 'sonnet', 'haiku', 'opusplan'],
|
|
297
298
|
opencode: ['grok', 'gpt4o'],
|
|
298
299
|
codex: ['gpt5', 'gpt5-codex', 'o3'],
|
|
299
300
|
agent: ['minimax-m2.5-free', 'big-pickle', 'gpt-5-nano', 'glm-5-free', 'deepseek-r1-free'],
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -284,6 +284,21 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
284
284
|
choices: ['claude', 'opencode', 'codex', 'agent'],
|
|
285
285
|
default: 'claude',
|
|
286
286
|
},
|
|
287
|
+
plan: {
|
|
288
|
+
type: 'boolean',
|
|
289
|
+
description: 'Enable plan mode: uses opus for planning, sonnet for execution (shortcut for --plan-model opus --worker-model sonnet). Only works with --tool claude.',
|
|
290
|
+
default: false,
|
|
291
|
+
},
|
|
292
|
+
'plan-model': {
|
|
293
|
+
type: 'string',
|
|
294
|
+
description: 'Model to use for plan mode (e.g., opus). When specified, auto-switches to opusplan mode and sets ANTHROPIC_DEFAULT_OPUS_MODEL. Use with --model/--worker-model to set separate plan and execution models (e.g., --plan-model opus --model sonnet). Only works with --tool claude.',
|
|
295
|
+
default: undefined,
|
|
296
|
+
},
|
|
297
|
+
'worker-model': {
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'Alias for --model: Model to use for execution/worker mode when --plan-model is specified. When used with --plan-model, sets ANTHROPIC_DEFAULT_SONNET_MODEL for Claude Code opusplan mode.',
|
|
300
|
+
default: undefined,
|
|
301
|
+
},
|
|
287
302
|
'execute-tool-with-bun': {
|
|
288
303
|
type: 'boolean',
|
|
289
304
|
description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage)',
|
|
@@ -441,7 +456,7 @@ export const createYargsConfig = yargsInstance => {
|
|
|
441
456
|
.option('model', {
|
|
442
457
|
type: 'string',
|
|
443
458
|
description: buildModelOptionDescription(),
|
|
444
|
-
alias: 'm',
|
|
459
|
+
alias: ['m', 'worker-model'],
|
|
445
460
|
default: currentParsedArgs => {
|
|
446
461
|
// Dynamic default based on tool selection (Issue #1473: centralized in models/index.mjs)
|
|
447
462
|
return defaultModels[currentParsedArgs?.tool] || defaultModels.claude;
|
|
@@ -549,7 +564,20 @@ export const parseArguments = async (yargs, hideBin) => {
|
|
|
549
564
|
// Post-processing: Fix model default for opencode and codex tools
|
|
550
565
|
// Yargs doesn't properly handle dynamic defaults based on other arguments,
|
|
551
566
|
// so we need to handle this manually after parsing
|
|
552
|
-
const modelExplicitlyProvided = rawArgs.includes('--model') || rawArgs.includes('-m');
|
|
567
|
+
const modelExplicitlyProvided = rawArgs.includes('--model') || rawArgs.includes('-m') || rawArgs.includes('--worker-model');
|
|
568
|
+
const planModelExplicitlyProvided = rawArgs.includes('--plan-model');
|
|
569
|
+
|
|
570
|
+
// --plan flag expansion (Issue #1223)
|
|
571
|
+
// When --plan is set, it acts as a shortcut for --plan-model opus --worker-model sonnet
|
|
572
|
+
// Explicit --plan-model and --model/--worker-model values take precedence
|
|
573
|
+
if (argv && argv.plan) {
|
|
574
|
+
if (!planModelExplicitlyProvided) {
|
|
575
|
+
argv.planModel = 'opus';
|
|
576
|
+
}
|
|
577
|
+
if (!modelExplicitlyProvided) {
|
|
578
|
+
argv.model = 'sonnet';
|
|
579
|
+
}
|
|
580
|
+
}
|
|
553
581
|
|
|
554
582
|
// Normalize alias flags: legacy --skip-tool-check and --skip-claude-check behave like --skip-tool-connection-check
|
|
555
583
|
if (argv) {
|
package/src/solve.mjs
CHANGED
|
@@ -211,6 +211,15 @@ if (!(await validateContinueOnlyOnFeedback(argv, isPrUrl, isIssueUrl))) {
|
|
|
211
211
|
const tool = argv.tool || 'claude';
|
|
212
212
|
await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
213
213
|
|
|
214
|
+
// Validate --plan-model if provided (Issue #1223)
|
|
215
|
+
if (argv.planModel) {
|
|
216
|
+
if (tool !== 'claude') {
|
|
217
|
+
await log(`ā --plan-model is only supported with --tool claude (current tool: ${tool})`, { level: 'error' });
|
|
218
|
+
await safeExit(1, '--plan-model requires --tool claude');
|
|
219
|
+
}
|
|
220
|
+
await validateAndExitOnInvalidModel(argv.planModel, tool, safeExit);
|
|
221
|
+
}
|
|
222
|
+
|
|
214
223
|
// Perform all system checks (skip tool connection check in dry-run or when --skip-tool-connection-check; model validation always runs)
|
|
215
224
|
const skipToolConnectionCheck = argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false;
|
|
216
225
|
if (!(await performSystemChecks(argv.minDiskSpace || 2048, skipToolConnectionCheck, argv.model, argv))) {
|