@link-assistant/hive-mind 1.32.3 → 1.34.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.md +7 -6
- package/package.json +1 -1
- package/src/github.lib.mjs +46 -49
- package/src/model-info.lib.mjs +360 -0
- package/src/option-suggestions.lib.mjs +1 -0
- package/src/solve.auto-merge.lib.mjs +3 -0
- package/src/solve.branch-errors.lib.mjs +29 -9
- package/src/solve.config.lib.mjs +5 -0
- package/src/solve.error-handlers.lib.mjs +3 -0
- package/src/solve.execution.lib.mjs +3 -0
- package/src/solve.interrupt.lib.mjs +3 -0
- package/src/solve.mjs +26 -24
- package/src/solve.repo-setup.lib.mjs +197 -20
- package/src/solve.repository.lib.mjs +3 -2
- package/src/solve.results.lib.mjs +9 -0
- package/src/solve.watch.lib.mjs +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.34.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 614c3d9: Add model information display in PR/issue log comments. Shows actual models used (extracted from CLI JSON output) vs requested model. Main model is bolded when it matches the request; a warning appears when it doesn't. Supporting models are listed separately. Uses models.dev API for full model name, provider, and knowledge cutoff. Replaces duplicated tool name mapping with unified getToolDisplayName() helper.
|
|
8
|
+
|
|
9
|
+
## 1.33.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- f7a2fdd: Add --auto-init-repository option to automatically initialize empty repositories by creating a simple README.md file, enabling branch creation and pull request workflows on repositories with no commits
|
|
14
|
+
|
|
3
15
|
## 1.32.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -341,12 +341,13 @@ solve <issue-url> [options]
|
|
|
341
341
|
|
|
342
342
|
**Other useful options:**
|
|
343
343
|
|
|
344
|
-
| Option
|
|
345
|
-
|
|
|
346
|
-
| `--tool`
|
|
347
|
-
| `--verbose`
|
|
348
|
-
| `--attach-logs`
|
|
349
|
-
| `--
|
|
344
|
+
| Option | Alias | Description | Default |
|
|
345
|
+
| ------------------------ | ----- | ------------------------------------------------ | ------- |
|
|
346
|
+
| `--tool` | | AI tool (claude, opencode, codex, agent) | claude |
|
|
347
|
+
| `--verbose` | `-v` | Enable verbose logging | false |
|
|
348
|
+
| `--attach-logs` | | Attach logs to PR (⚠️ may expose sensitive data) | false |
|
|
349
|
+
| `--auto-init-repository` | | Auto-initialize empty repos (creates README.md) | false |
|
|
350
|
+
| `--help` | `-h` | Show all available options | - |
|
|
350
351
|
|
|
351
352
|
> **📖 Full options list**: See [docs/CONFIGURATION.md](./docs/CONFIGURATION.md#solve-options) for all available options including forking, auto-continue, watch mode, and experimental features.
|
|
352
353
|
|
package/package.json
CHANGED
package/src/github.lib.mjs
CHANGED
|
@@ -10,26 +10,16 @@ import { isSafeToken, isHexInSafeContext, getGitHubTokensFromFiles, getGitHubTok
|
|
|
10
10
|
export { isSafeToken, isHexInSafeContext, getGitHubTokensFromFiles, getGitHubTokensFromCommand, sanitizeLogContent }; // Re-export for backward compatibility
|
|
11
11
|
import { uploadLogWithGhUploadLog } from './log-upload.lib.mjs';
|
|
12
12
|
import { formatResetTimeWithRelative } from './usage-limit.lib.mjs'; // See: https://github.com/link-assistant/hive-mind/issues/1236
|
|
13
|
+
// Import model info helpers (Issue #1225)
|
|
14
|
+
import { getToolDisplayName, getModelInfoForComment } from './model-info.lib.mjs';
|
|
15
|
+
// Re-export for use by other modules
|
|
16
|
+
export { getToolDisplayName };
|
|
13
17
|
|
|
14
|
-
/**
|
|
15
|
-
* Build cost estimation string for log comments
|
|
16
|
-
* Issue #1250: Enhanced to show both public pricing estimate and actual provider cost
|
|
17
|
-
*
|
|
18
|
-
* @param {number|null} totalCostUSD - Public pricing estimate
|
|
19
|
-
* @param {number|null} anthropicTotalCostUSD - Cost calculated by Anthropic (Claude-specific)
|
|
20
|
-
* @param {Object|null} pricingInfo - Pricing info from agent tool
|
|
21
|
-
* - opencodeCost: Actual billed cost from OpenCode Zen (for agent tool)
|
|
22
|
-
* - isOpencodeFreeModel: Whether OpenCode Zen provides this model for free
|
|
23
|
-
* - originalProvider: Original provider for pricing reference
|
|
24
|
-
* - baseModelName: Base model name if pricing was derived from base model (Issue #1250)
|
|
25
|
-
* @returns {string} Formatted cost info string for markdown (empty if no data available)
|
|
26
|
-
*/
|
|
18
|
+
/** Build cost estimation string for log comments (Issue #1250) */
|
|
27
19
|
const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) => {
|
|
28
|
-
// Issue #1015: Don't show cost section when all values are unknown (clutters output)
|
|
29
20
|
const hasPublic = totalCostUSD !== null && totalCostUSD !== undefined;
|
|
30
21
|
const hasAnthropic = anthropicTotalCostUSD !== null && anthropicTotalCostUSD !== undefined;
|
|
31
22
|
const hasPricing = pricingInfo && (pricingInfo.modelName || pricingInfo.tokenUsage || pricingInfo.isFreeModel || pricingInfo.isOpencodeFreeModel);
|
|
32
|
-
// Issue #1250: Check for OpenCode Zen actual cost
|
|
33
23
|
const hasOpencodeCost = pricingInfo?.opencodeCost !== null && pricingInfo?.opencodeCost !== undefined;
|
|
34
24
|
if (!hasPublic && !hasAnthropic && !hasPricing && !hasOpencodeCost) return '';
|
|
35
25
|
let costInfo = '\n\n💰 **Cost estimation:**';
|
|
@@ -37,15 +27,10 @@ const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) =
|
|
|
37
27
|
costInfo += `\n- Model: ${pricingInfo.modelName}`;
|
|
38
28
|
if (pricingInfo.provider) costInfo += `\n- Provider: ${pricingInfo.provider}`;
|
|
39
29
|
}
|
|
40
|
-
// Issue #1250: Show public pricing estimate based on original provider prices
|
|
41
30
|
if (hasPublic) {
|
|
42
|
-
// Issue #1250: For free models accessed via OpenCode Zen, show pricing based on base model
|
|
43
|
-
// Only show as completely free if the base model also has no pricing
|
|
44
31
|
if (pricingInfo?.isFreeModel && totalCostUSD === 0 && !pricingInfo?.baseModelName) {
|
|
45
32
|
costInfo += '\n- Public pricing estimate: $0.00 (Free model)';
|
|
46
33
|
} else {
|
|
47
|
-
// Show actual public pricing estimate with original provider reference
|
|
48
|
-
// Issue #1250: Include base model reference when pricing comes from base model
|
|
49
34
|
let pricingRef = '';
|
|
50
35
|
if (pricingInfo?.baseModelName && pricingInfo?.originalProvider) {
|
|
51
36
|
pricingRef = ` (based on ${pricingInfo.originalProvider} ${pricingInfo.baseModelName} prices)`;
|
|
@@ -57,7 +42,6 @@ const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) =
|
|
|
57
42
|
} else if (hasPricing) {
|
|
58
43
|
costInfo += '\n- Public pricing estimate: unknown';
|
|
59
44
|
}
|
|
60
|
-
// Issue #1250: Show actual cost from OpenCode Zen for agent tool
|
|
61
45
|
if (hasOpencodeCost) {
|
|
62
46
|
if (pricingInfo.isOpencodeFreeModel) {
|
|
63
47
|
costInfo += '\n- Calculated by OpenCode Zen: $0.00 (Free model)';
|
|
@@ -360,26 +344,7 @@ Thank you! 🙏`;
|
|
|
360
344
|
return false;
|
|
361
345
|
}
|
|
362
346
|
};
|
|
363
|
-
/**
|
|
364
|
-
* Attaches a log file to a GitHub PR or issue as a comment
|
|
365
|
-
* @param {Object} options - Configuration options
|
|
366
|
-
* @param {string} options.logFile - Path to the log file
|
|
367
|
-
* @param {string} options.targetType - 'pr' or 'issue'
|
|
368
|
-
* @param {number} options.targetNumber - PR or issue number
|
|
369
|
-
* @param {string} options.owner - Repository owner
|
|
370
|
-
* @param {string} options.repo - Repository name
|
|
371
|
-
* @param {Function} options.$ - Command execution function
|
|
372
|
-
* @param {Function} options.log - Logging function
|
|
373
|
-
* @param {Function} options.sanitizeLogContent - Function to sanitize log content
|
|
374
|
-
* @param {boolean} [options.verbose=false] - Enable verbose logging
|
|
375
|
-
* @param {string} [options.errorMessage] - Error message to include in comment (for failure logs)
|
|
376
|
-
* @param {string} [options.customTitle] - Custom title for the comment (defaults to "🤖 Solution Draft Log")
|
|
377
|
-
* @param {boolean} [options.isUsageLimit] - Whether this is a usage limit error
|
|
378
|
-
* @param {string} [options.limitResetTime] - Time when usage limit resets
|
|
379
|
-
* @param {string} [options.toolName] - Name of the tool (claude, codex, opencode)
|
|
380
|
-
* @param {string} [options.resumeCommand] - Command to resume the session
|
|
381
|
-
* @returns {Promise<boolean>} - True if upload succeeded
|
|
382
|
-
*/
|
|
347
|
+
/** Attaches a log file to a GitHub PR or issue as a comment. Returns true if upload succeeded. */
|
|
383
348
|
export async function attachLogToGitHub(options) {
|
|
384
349
|
const fs = (await use('fs')).promises;
|
|
385
350
|
const {
|
|
@@ -413,6 +378,9 @@ export async function attachLogToGitHub(options) {
|
|
|
413
378
|
pricingInfo = null,
|
|
414
379
|
// Issue #1088: Track error_during_execution for "Finished with errors" state
|
|
415
380
|
errorDuringExecution = false,
|
|
381
|
+
// Issue #1225: Model information for PR comments
|
|
382
|
+
requestedModel = null, // The --model flag value (e.g., "opus", "sonnet")
|
|
383
|
+
tool = null, // The tool used (e.g., "claude", "agent", "codex", "opencode")
|
|
416
384
|
} = options;
|
|
417
385
|
const targetName = targetType === 'pr' ? 'Pull Request' : 'Issue';
|
|
418
386
|
const ghCommand = targetType === 'pr' ? 'pr' : 'issue';
|
|
@@ -433,6 +401,8 @@ export async function attachLogToGitHub(options) {
|
|
|
433
401
|
// Calculate token usage if sessionId and tempDir are provided
|
|
434
402
|
// For agent tool, publicPricingEstimate is already provided, so we skip Claude-specific calculation
|
|
435
403
|
let totalCostUSD = publicPricingEstimate;
|
|
404
|
+
// Issue #1225: Collect actual model IDs from Claude session JSON output
|
|
405
|
+
let actualModelIds = null;
|
|
436
406
|
if (totalCostUSD === null && sessionId && tempDir && !errorMessage) {
|
|
437
407
|
try {
|
|
438
408
|
const { calculateSessionTokens } = await import('./claude.lib.mjs');
|
|
@@ -444,6 +414,13 @@ export async function attachLogToGitHub(options) {
|
|
|
444
414
|
await log(` 💰 Calculated cost: $${totalCostUSD.toFixed(6)}`, { verbose: true });
|
|
445
415
|
}
|
|
446
416
|
}
|
|
417
|
+
// Extract actual model IDs from session data (Issue #1225)
|
|
418
|
+
if (tokenUsage.modelUsage && Object.keys(tokenUsage.modelUsage).length > 0) {
|
|
419
|
+
actualModelIds = Object.keys(tokenUsage.modelUsage);
|
|
420
|
+
if (verbose) {
|
|
421
|
+
await log(` 🤖 Actual models used: ${actualModelIds.join(', ')}`, { verbose: true });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
447
424
|
}
|
|
448
425
|
} catch (tokenError) {
|
|
449
426
|
// Don't fail the entire upload if token calculation fails
|
|
@@ -452,6 +429,25 @@ export async function attachLogToGitHub(options) {
|
|
|
452
429
|
}
|
|
453
430
|
}
|
|
454
431
|
}
|
|
432
|
+
// For agent tool, extract actual model ID from pricingInfo (Issue #1225)
|
|
433
|
+
if (!actualModelIds && pricingInfo?.modelId) {
|
|
434
|
+
actualModelIds = [pricingInfo.modelId];
|
|
435
|
+
}
|
|
436
|
+
// Issue #1225: Fetch model information for comment using actual models from CLI output
|
|
437
|
+
let modelInfoString = '';
|
|
438
|
+
if (requestedModel || tool || actualModelIds) {
|
|
439
|
+
try {
|
|
440
|
+
modelInfoString = await getModelInfoForComment({ requestedModel, tool, pricingInfo, actualModelIds });
|
|
441
|
+
if (verbose && modelInfoString) {
|
|
442
|
+
await log(' 🤖 Model info fetched for comment', { verbose: true });
|
|
443
|
+
}
|
|
444
|
+
} catch (modelInfoError) {
|
|
445
|
+
// Non-critical: continue without model info
|
|
446
|
+
if (verbose) {
|
|
447
|
+
await log(` ⚠️ Could not fetch model info: ${modelInfoError.message}`, { verbose: true });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
455
451
|
// Read and sanitize log content
|
|
456
452
|
const rawLogContent = await fs.readFile(logFile, 'utf8');
|
|
457
453
|
if (verbose) {
|
|
@@ -523,7 +519,7 @@ ${resumeCommand}
|
|
|
523
519
|
}
|
|
524
520
|
}
|
|
525
521
|
|
|
526
|
-
logComment +=
|
|
522
|
+
logComment += `${modelInfoString}
|
|
527
523
|
|
|
528
524
|
<details>
|
|
529
525
|
<summary>Click to expand execution log (${Math.round(logStats.size / 1024)}KB)</summary>
|
|
@@ -542,7 +538,7 @@ ${logContent}
|
|
|
542
538
|
The automated solution draft encountered an error:
|
|
543
539
|
\`\`\`
|
|
544
540
|
${errorMessage}
|
|
545
|
-
|
|
541
|
+
\`\`\`${modelInfoString}
|
|
546
542
|
|
|
547
543
|
<details>
|
|
548
544
|
<summary>Click to expand failure log (${Math.round(logStats.size / 1024)}KB)</summary>
|
|
@@ -559,7 +555,7 @@ ${logContent}
|
|
|
559
555
|
// Issue #1088: "Finished with errors" format - work may have been completed but errors occurred
|
|
560
556
|
const costInfo = buildCostInfoString(totalCostUSD, anthropicTotalCostUSD, pricingInfo);
|
|
561
557
|
logComment = `## ⚠️ Solution Draft Finished with Errors
|
|
562
|
-
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}
|
|
558
|
+
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${modelInfoString}
|
|
563
559
|
|
|
564
560
|
**Note**: The session encountered errors during execution, but some work may have been completed. Please review the changes carefully.
|
|
565
561
|
|
|
@@ -592,7 +588,7 @@ ${logContent}
|
|
|
592
588
|
sessionNote = '\n\n**Note**: This session was manually resumed using the --resume flag.';
|
|
593
589
|
}
|
|
594
590
|
logComment = `## ${title}
|
|
595
|
-
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${sessionNote}
|
|
591
|
+
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${modelInfoString}${sessionNote}
|
|
596
592
|
|
|
597
593
|
<details>
|
|
598
594
|
<summary>Click to expand solution draft log (${Math.round(logStats.size / 1024)}KB)</summary>
|
|
@@ -716,7 +712,7 @@ ${resumeCommand}
|
|
|
716
712
|
}
|
|
717
713
|
}
|
|
718
714
|
|
|
719
|
-
logUploadComment +=
|
|
715
|
+
logUploadComment += `${modelInfoString}
|
|
720
716
|
|
|
721
717
|
📎 **Execution log uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
|
|
722
718
|
🔗 [View complete execution log](${logUrl})
|
|
@@ -729,7 +725,7 @@ ${resumeCommand}
|
|
|
729
725
|
The automated solution draft encountered an error:
|
|
730
726
|
\`\`\`
|
|
731
727
|
${errorMessage}
|
|
732
|
-
|
|
728
|
+
\`\`\`${modelInfoString}
|
|
733
729
|
📎 **Failure log uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
|
|
734
730
|
🔗 [View complete failure log](${logUrl})
|
|
735
731
|
---
|
|
@@ -738,7 +734,7 @@ ${errorMessage}
|
|
|
738
734
|
// Issue #1088: "Finished with errors" format - work may have been completed but errors occurred
|
|
739
735
|
const costInfo = buildCostInfoString(totalCostUSD, anthropicTotalCostUSD, pricingInfo);
|
|
740
736
|
logUploadComment = `## ⚠️ Solution Draft Finished with Errors
|
|
741
|
-
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}
|
|
737
|
+
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${modelInfoString}
|
|
742
738
|
|
|
743
739
|
**Note**: The session encountered errors during execution, but some work may have been completed. Please review the changes carefully.
|
|
744
740
|
|
|
@@ -764,7 +760,7 @@ This log file contains the complete execution trace of the AI ${targetType === '
|
|
|
764
760
|
sessionNote = '\n**Note**: This session was manually resumed using the --resume flag.\n';
|
|
765
761
|
}
|
|
766
762
|
logUploadComment = `## ${title}
|
|
767
|
-
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}
|
|
763
|
+
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${modelInfoString}
|
|
768
764
|
${sessionNote}📎 **Log file uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
|
|
769
765
|
🔗 [View complete solution draft log](${logUrl})
|
|
770
766
|
---
|
|
@@ -1482,6 +1478,7 @@ export default {
|
|
|
1482
1478
|
checkGitHubPermissions,
|
|
1483
1479
|
checkRepositoryWritePermission,
|
|
1484
1480
|
attachLogToGitHub,
|
|
1481
|
+
getToolDisplayName,
|
|
1485
1482
|
uploadLogWithGhUploadLog,
|
|
1486
1483
|
fetchAllIssuesWithPagination,
|
|
1487
1484
|
fetchProjectIssues,
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Model information library for hive-mind
|
|
5
|
+
* Provides unified model display, verification, and metadata fetching
|
|
6
|
+
* for all tools (Claude, Agent, OpenCode, Codex).
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/link-assistant/hive-mind/issues/1225
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Check if use is already defined (when imported from solve.mjs)
|
|
12
|
+
// If not, fetch it (when running standalone)
|
|
13
|
+
if (typeof globalThis.use === 'undefined') {
|
|
14
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
import { log } from './lib.mjs';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Map tool identifier to user-friendly display name.
|
|
21
|
+
* Replaces duplicated ternary chains across the codebase.
|
|
22
|
+
* @param {string|null} tool - The tool identifier (claude, codex, opencode, agent)
|
|
23
|
+
* @returns {string} User-friendly display name
|
|
24
|
+
*/
|
|
25
|
+
export const getToolDisplayName = tool => {
|
|
26
|
+
const name = (tool || '').toString().toLowerCase();
|
|
27
|
+
switch (name) {
|
|
28
|
+
case 'claude':
|
|
29
|
+
return 'Claude';
|
|
30
|
+
case 'codex':
|
|
31
|
+
return 'Codex';
|
|
32
|
+
case 'opencode':
|
|
33
|
+
return 'OpenCode';
|
|
34
|
+
case 'agent':
|
|
35
|
+
return 'Agent';
|
|
36
|
+
default:
|
|
37
|
+
return 'AI tool';
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Cached models.dev API response to avoid repeated network requests.
|
|
43
|
+
* The cache is per-process and cleared when the process exits.
|
|
44
|
+
*/
|
|
45
|
+
let modelsDevCache = null;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Fetch the full models.dev API data with caching.
|
|
49
|
+
* @returns {Promise<Object|null>} The full API response or null on failure
|
|
50
|
+
*/
|
|
51
|
+
const fetchModelsDevApi = async () => {
|
|
52
|
+
if (modelsDevCache) return modelsDevCache;
|
|
53
|
+
try {
|
|
54
|
+
const https = (await globalThis.use('https')).default;
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
https
|
|
57
|
+
.get('https://models.dev/api.json', res => {
|
|
58
|
+
let data = '';
|
|
59
|
+
res.on('data', chunk => {
|
|
60
|
+
data += chunk;
|
|
61
|
+
});
|
|
62
|
+
res.on('end', () => {
|
|
63
|
+
try {
|
|
64
|
+
modelsDevCache = JSON.parse(data);
|
|
65
|
+
resolve(modelsDevCache);
|
|
66
|
+
} catch (parseError) {
|
|
67
|
+
reject(parseError);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
})
|
|
71
|
+
.on('error', err => {
|
|
72
|
+
reject(err);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Fetch model metadata from models.dev API.
|
|
82
|
+
* Returns enriched model information including name, provider, version, and knowledge cutoff.
|
|
83
|
+
* @param {string} modelId - The model ID (e.g., "claude-opus-4-6", "opencode/grok-code")
|
|
84
|
+
* @returns {Promise<Object|null>} Model metadata or null if not found
|
|
85
|
+
*/
|
|
86
|
+
export const fetchModelInfoForComment = async modelId => {
|
|
87
|
+
if (!modelId) return null;
|
|
88
|
+
try {
|
|
89
|
+
const apiData = await fetchModelsDevApi();
|
|
90
|
+
if (!apiData) return null;
|
|
91
|
+
|
|
92
|
+
// Normalize model ID: strip provider prefix for lookup (e.g., "anthropic/claude-3-5-sonnet" -> "claude-3-5-sonnet")
|
|
93
|
+
const lookupId = modelId.includes('/') ? modelId.split('/').pop() : modelId;
|
|
94
|
+
|
|
95
|
+
// Check Anthropic provider first (most common for Claude tools)
|
|
96
|
+
if (apiData.anthropic?.models?.[lookupId]) {
|
|
97
|
+
const modelInfo = { ...apiData.anthropic.models[lookupId] };
|
|
98
|
+
modelInfo.provider = apiData.anthropic.name || 'Anthropic';
|
|
99
|
+
return modelInfo;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Search across all providers
|
|
103
|
+
for (const provider of Object.values(apiData)) {
|
|
104
|
+
if (provider.models && provider.models[lookupId]) {
|
|
105
|
+
const modelInfo = { ...provider.models[lookupId] };
|
|
106
|
+
modelInfo.provider = provider.name || provider.id;
|
|
107
|
+
return modelInfo;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Try the full modelId (with provider prefix) as well
|
|
112
|
+
if (lookupId !== modelId) {
|
|
113
|
+
for (const provider of Object.values(apiData)) {
|
|
114
|
+
if (provider.models && provider.models[modelId]) {
|
|
115
|
+
const modelInfo = { ...provider.models[modelId] };
|
|
116
|
+
modelInfo.provider = provider.name || provider.id;
|
|
117
|
+
return modelInfo;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Normalize model ID for comparison purposes (strip suffixes, lowercase).
|
|
130
|
+
* @param {string} modelId - A model ID or alias
|
|
131
|
+
* @returns {string} Normalized ID
|
|
132
|
+
*/
|
|
133
|
+
const normalizeForComparison = modelId => {
|
|
134
|
+
if (!modelId) return '';
|
|
135
|
+
return modelId
|
|
136
|
+
.toLowerCase()
|
|
137
|
+
.replace(/\[1m\]$/i, '')
|
|
138
|
+
.trim();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if a requested model alias matches an actual model ID.
|
|
143
|
+
* @param {string} requestedModel - The --model flag value (alias or full ID)
|
|
144
|
+
* @param {string} actualModelId - The actual model ID from CLI output
|
|
145
|
+
* @param {string|null} tool - The tool being used
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
const doesRequestedMatchActual = (requestedModel, actualModelId, tool) => {
|
|
149
|
+
if (!requestedModel || !actualModelId) return false;
|
|
150
|
+
const resolvedRequested = resolveModelId(requestedModel, tool);
|
|
151
|
+
const normResolved = normalizeForComparison(resolvedRequested);
|
|
152
|
+
const normActual = normalizeForComparison(actualModelId);
|
|
153
|
+
// Direct match
|
|
154
|
+
if (normResolved === normActual) return true;
|
|
155
|
+
// Partial match: resolved starts with actual or vice versa (for date-suffixed IDs)
|
|
156
|
+
if (normActual.startsWith(normResolved) || normResolved.startsWith(normActual)) return true;
|
|
157
|
+
return false;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build model information string for PR/issue comments.
|
|
162
|
+
* Displays the requested model vs actual models used from CLI JSON output.
|
|
163
|
+
* The main model is bolded if it matches the requested model.
|
|
164
|
+
* A warning is shown if the main model doesn't match the requested model.
|
|
165
|
+
*
|
|
166
|
+
* @param {Object} options - Model info options
|
|
167
|
+
* @param {string|null} options.requestedModel - The model requested via --model flag (e.g., "opus")
|
|
168
|
+
* @param {string|null} options.tool - The tool used (claude, agent, opencode, codex)
|
|
169
|
+
* @param {Object|null} options.pricingInfo - Pricing info from tool result (agent tool provides modelId)
|
|
170
|
+
* @param {Object|null} options.modelInfo - Pre-fetched model metadata from models.dev (for first actual model)
|
|
171
|
+
* @param {Array<{modelId: string, modelInfo: Object|null}>|null} options.modelsUsed - Actual models used from CLI JSON output
|
|
172
|
+
* @returns {string} Formatted markdown string for model info section (empty if no data available)
|
|
173
|
+
*/
|
|
174
|
+
export const buildModelInfoString = ({ requestedModel = null, tool = null, pricingInfo = null, modelInfo = null, modelsUsed = null } = {}) => {
|
|
175
|
+
const hasRequested = requestedModel !== null && requestedModel !== undefined;
|
|
176
|
+
const hasModelsUsed = Array.isArray(modelsUsed) && modelsUsed.length > 0;
|
|
177
|
+
const hasModelInfo = modelInfo !== null;
|
|
178
|
+
const hasPricingModel = pricingInfo?.modelId || pricingInfo?.modelName;
|
|
179
|
+
|
|
180
|
+
if (!hasRequested && !hasModelsUsed && !hasModelInfo && !hasPricingModel) return '';
|
|
181
|
+
|
|
182
|
+
let info = '\n\n🤖 **Models used:**';
|
|
183
|
+
|
|
184
|
+
// Display tool name
|
|
185
|
+
if (tool) {
|
|
186
|
+
info += `\n- Tool: ${getToolDisplayName(tool)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Display requested model (--model flag value)
|
|
190
|
+
if (hasRequested) {
|
|
191
|
+
info += `\n- Requested: \`${requestedModel}\``;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (hasModelsUsed) {
|
|
195
|
+
// The first model is considered the "main" model
|
|
196
|
+
const [mainEntry, ...supportingEntries] = modelsUsed;
|
|
197
|
+
const mainModelId = mainEntry.modelId;
|
|
198
|
+
const mainModelMeta = mainEntry.modelInfo;
|
|
199
|
+
|
|
200
|
+
const mainMatches = hasRequested ? doesRequestedMatchActual(requestedModel, mainModelId, tool) : true;
|
|
201
|
+
|
|
202
|
+
// Build main model line
|
|
203
|
+
const mainModelName = mainModelMeta?.name || mainModelId;
|
|
204
|
+
const mainModelProvider = mainModelMeta?.provider || null;
|
|
205
|
+
const mainModelKnowledge = mainModelMeta?.knowledge || null;
|
|
206
|
+
|
|
207
|
+
if (mainMatches) {
|
|
208
|
+
info += `\n- **Main model: ${mainModelName}** (ID: \`${mainModelId}\`${mainModelProvider ? `, ${mainModelProvider}` : ''}${mainModelKnowledge ? `, cutoff: ${mainModelKnowledge}` : ''})`;
|
|
209
|
+
} else {
|
|
210
|
+
// Main model doesn't match requested - show warning
|
|
211
|
+
info += `\n- **Main model: ${mainModelName}** (ID: \`${mainModelId}\`${mainModelProvider ? `, ${mainModelProvider}` : ''}${mainModelKnowledge ? `, cutoff: ${mainModelKnowledge}` : ''})`;
|
|
212
|
+
if (hasRequested) {
|
|
213
|
+
info += `\n- ⚠️ **Warning**: Main model \`${mainModelId}\` does not match requested model \`${requestedModel}\``;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Display supporting models
|
|
218
|
+
if (supportingEntries.length > 0) {
|
|
219
|
+
info += '\n- Supporting models:';
|
|
220
|
+
for (const entry of supportingEntries) {
|
|
221
|
+
const name = entry.modelInfo?.name || entry.modelId;
|
|
222
|
+
const provider = entry.modelInfo?.provider || null;
|
|
223
|
+
info += `\n - ${name} (\`${entry.modelId}\`${provider ? `, ${provider}` : ''})`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} else if (hasModelInfo) {
|
|
227
|
+
// Fallback: single model info from models.dev (no actual CLI output data)
|
|
228
|
+
const mainModelName = modelInfo.name || (pricingInfo?.modelId ? pricingInfo.modelId : null) || 'Unknown';
|
|
229
|
+
info += `\n- Model: ${mainModelName}`;
|
|
230
|
+
if (modelInfo.id) info += ` (ID: \`${modelInfo.id}\`)`;
|
|
231
|
+
if (modelInfo.provider) info += `\n- Provider: ${modelInfo.provider}`;
|
|
232
|
+
if (modelInfo.knowledge) info += `\n- Knowledge cutoff: ${modelInfo.knowledge}`;
|
|
233
|
+
} else if (hasPricingModel) {
|
|
234
|
+
// Fallback to pricingInfo when no models.dev data
|
|
235
|
+
const modelId = pricingInfo.modelId || null;
|
|
236
|
+
const modelName = pricingInfo.modelName || modelId || 'Unknown';
|
|
237
|
+
if (modelId && modelId !== modelName) {
|
|
238
|
+
info += `\n- Model: ${modelName} (ID: \`${modelId}\`)`;
|
|
239
|
+
} else {
|
|
240
|
+
info += `\n- Model: ${modelName}`;
|
|
241
|
+
}
|
|
242
|
+
if (pricingInfo.provider) info += `\n- Provider: ${pricingInfo.provider}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return info;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Resolve the full model ID from a user-provided alias using the model mapping.
|
|
250
|
+
* @param {string|null} requestedModel - The model alias (e.g., "opus", "sonnet")
|
|
251
|
+
* @param {string|null} tool - The tool being used
|
|
252
|
+
* @returns {string|null} The full model ID or null
|
|
253
|
+
*/
|
|
254
|
+
export const resolveModelId = (requestedModel, tool) => {
|
|
255
|
+
if (!requestedModel) return null;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
// Use model-mapping.lib.mjs mappings as authoritative source
|
|
259
|
+
const modelMaps = {
|
|
260
|
+
claude: {
|
|
261
|
+
sonnet: 'claude-sonnet-4-6',
|
|
262
|
+
opus: 'claude-opus-4-5-20251101',
|
|
263
|
+
haiku: 'claude-haiku-4-5-20251001',
|
|
264
|
+
'opus-4-6': 'claude-opus-4-6',
|
|
265
|
+
'opus-4-5': 'claude-opus-4-5-20251101',
|
|
266
|
+
'sonnet-4-6': 'claude-sonnet-4-6',
|
|
267
|
+
'sonnet-4-5': 'claude-sonnet-4-5-20250929',
|
|
268
|
+
'haiku-4-5': 'claude-haiku-4-5-20251001',
|
|
269
|
+
},
|
|
270
|
+
agent: {
|
|
271
|
+
grok: 'opencode/grok-code',
|
|
272
|
+
'grok-code': 'opencode/grok-code',
|
|
273
|
+
sonnet: 'anthropic/claude-3-5-sonnet',
|
|
274
|
+
opus: 'anthropic/claude-3-opus',
|
|
275
|
+
haiku: 'anthropic/claude-3-5-haiku',
|
|
276
|
+
},
|
|
277
|
+
opencode: {
|
|
278
|
+
gpt4: 'openai/gpt-4',
|
|
279
|
+
gpt4o: 'openai/gpt-4o',
|
|
280
|
+
sonnet: 'anthropic/claude-3-5-sonnet',
|
|
281
|
+
opus: 'anthropic/claude-3-opus',
|
|
282
|
+
grok: 'opencode/grok-code',
|
|
283
|
+
},
|
|
284
|
+
codex: {
|
|
285
|
+
gpt5: 'gpt-5',
|
|
286
|
+
'gpt-5': 'gpt-5',
|
|
287
|
+
o3: 'o3',
|
|
288
|
+
gpt4: 'gpt-4',
|
|
289
|
+
gpt4o: 'gpt-4o',
|
|
290
|
+
sonnet: 'claude-3-5-sonnet',
|
|
291
|
+
opus: 'claude-3-opus',
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const toolName = (tool || 'claude').toString().toLowerCase();
|
|
296
|
+
const map = modelMaps[toolName];
|
|
297
|
+
if (map) {
|
|
298
|
+
// Strip [1m] suffix if present (1M context window flag)
|
|
299
|
+
const cleanModel = requestedModel.replace(/\[1m\]$/i, '');
|
|
300
|
+
return map[cleanModel.toLowerCase()] || cleanModel;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return requestedModel;
|
|
304
|
+
} catch {
|
|
305
|
+
return requestedModel;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Fetch model info and build the complete model information string for PR comments.
|
|
311
|
+
* Uses actual models from CLI JSON output when available.
|
|
312
|
+
*
|
|
313
|
+
* @param {Object} options
|
|
314
|
+
* @param {string|null} options.requestedModel - The --model flag value
|
|
315
|
+
* @param {string|null} options.tool - The tool used (claude, agent, opencode, codex)
|
|
316
|
+
* @param {Object|null} options.pricingInfo - Pricing info from tool result
|
|
317
|
+
* @param {Array<string>|null} options.actualModelIds - Actual model IDs from CLI JSON output
|
|
318
|
+
* For Claude: from tokenUsage.modelUsage keys (model IDs used in session)
|
|
319
|
+
* For Agent: from pricingInfo.modelId
|
|
320
|
+
* @returns {Promise<string>} Formatted markdown model info section
|
|
321
|
+
*/
|
|
322
|
+
export const getModelInfoForComment = async ({ requestedModel = null, tool = null, pricingInfo = null, actualModelIds = null } = {}) => {
|
|
323
|
+
// Determine the list of actual model IDs to display
|
|
324
|
+
// Priority: explicit actualModelIds > pricingInfo.modelId > resolve from requestedModel
|
|
325
|
+
let modelIds = [];
|
|
326
|
+
|
|
327
|
+
if (Array.isArray(actualModelIds) && actualModelIds.length > 0) {
|
|
328
|
+
modelIds = actualModelIds;
|
|
329
|
+
} else if (pricingInfo?.modelId) {
|
|
330
|
+
// Agent tool provides pricingInfo.modelId as the actual model used
|
|
331
|
+
modelIds = [pricingInfo.modelId];
|
|
332
|
+
} else if (requestedModel) {
|
|
333
|
+
// Fallback: resolve from requested model alias
|
|
334
|
+
const resolved = resolveModelId(requestedModel, tool);
|
|
335
|
+
if (resolved) modelIds = [resolved];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Fetch model metadata from models.dev for each model ID
|
|
339
|
+
const modelsUsed = [];
|
|
340
|
+
for (const modelId of modelIds) {
|
|
341
|
+
let meta = null;
|
|
342
|
+
try {
|
|
343
|
+
meta = await fetchModelInfoForComment(modelId);
|
|
344
|
+
} catch {
|
|
345
|
+
await log(' ⚠️ Could not fetch model info from models.dev', { verbose: true });
|
|
346
|
+
}
|
|
347
|
+
modelsUsed.push({ modelId, modelInfo: meta });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Determine which modelInfo to pass for legacy fallback (first model's metadata)
|
|
351
|
+
const firstModelInfo = modelsUsed.length > 0 ? modelsUsed[0].modelInfo : null;
|
|
352
|
+
|
|
353
|
+
return buildModelInfoString({
|
|
354
|
+
requestedModel,
|
|
355
|
+
tool,
|
|
356
|
+
pricingInfo,
|
|
357
|
+
modelInfo: modelsUsed.length === 0 ? firstModelInfo : null, // only used as fallback when no modelsUsed
|
|
358
|
+
modelsUsed: modelsUsed.length > 0 ? modelsUsed : null,
|
|
359
|
+
});
|
|
360
|
+
};
|
|
@@ -790,6 +790,9 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
790
790
|
anthropicTotalCostUSD: latestAnthropicCost,
|
|
791
791
|
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
792
792
|
pricingInfo: toolResult.pricingInfo,
|
|
793
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
794
|
+
requestedModel: argv.model,
|
|
795
|
+
tool: argv.tool || 'claude',
|
|
793
796
|
});
|
|
794
797
|
await log(formatAligned('', '✅ Session log uploaded to PR', '', 2));
|
|
795
798
|
}
|
|
@@ -232,15 +232,35 @@ export async function handleBranchCreationError({ branchName, errorOutput, tempD
|
|
|
232
232
|
await log(` ${line}`);
|
|
233
233
|
}
|
|
234
234
|
await log('');
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
235
|
+
|
|
236
|
+
// Check if this is an empty repository error (no commits to branch from)
|
|
237
|
+
const isEmptyRepoError = errorOutput.includes('is not a commit') || errorOutput.includes('not a valid object name') || errorOutput.includes('unknown revision');
|
|
238
|
+
if (isEmptyRepoError) {
|
|
239
|
+
await log(' 💡 Root cause:');
|
|
240
|
+
await log(' The repository appears to be empty (no commits).');
|
|
241
|
+
await log(' Cannot create a branch from a non-existent commit.');
|
|
242
|
+
await log('');
|
|
243
|
+
await log(' 🔧 How to fix:');
|
|
244
|
+
await log(' Use the --auto-init-repository flag to automatically initialize the repository:');
|
|
245
|
+
if (owner && repo) {
|
|
246
|
+
await log(` solve https://github.com/${owner}/${repo}/issues/<number> --auto-init-repository`);
|
|
247
|
+
} else {
|
|
248
|
+
await log(' solve <issue-url> --auto-init-repository');
|
|
249
|
+
}
|
|
250
|
+
await log('');
|
|
251
|
+
await log(' This will create a simple README.md file to make the repository non-empty,');
|
|
252
|
+
await log(' allowing branch creation and pull request workflows to proceed.');
|
|
253
|
+
} else {
|
|
254
|
+
await log(' 💡 Possible causes:');
|
|
255
|
+
await log(' • Branch name already exists');
|
|
256
|
+
await log(' • Uncommitted changes in repository');
|
|
257
|
+
await log(' • Git configuration issues');
|
|
258
|
+
await log('');
|
|
259
|
+
await log(' 🔧 How to fix:');
|
|
260
|
+
await log(' 1. Try running the command again (uses random names)');
|
|
261
|
+
await log(` 2. Check git status: cd ${tempDir} && git status`);
|
|
262
|
+
await log(` 3. View existing branches: cd ${tempDir} && git branch -a`);
|
|
263
|
+
}
|
|
244
264
|
}
|
|
245
265
|
|
|
246
266
|
export async function handleBranchVerificationError({ isContinueMode, branchName, actualBranch, prNumber, owner, repo, tempDir, formatAligned, log, $ }) {
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -362,6 +362,11 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
362
362
|
description: 'Guide Claude to use agent-commander CLI (start-agent) instead of native Task tool for subagent delegation. Allows using any supported agent type (claude, opencode, codex, agent) with unified API. Only works with --tool claude and requires agent-commander to be installed.',
|
|
363
363
|
default: false,
|
|
364
364
|
},
|
|
365
|
+
'auto-init-repository': {
|
|
366
|
+
type: 'boolean',
|
|
367
|
+
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.',
|
|
368
|
+
default: false,
|
|
369
|
+
},
|
|
365
370
|
'attach-solution-summary': {
|
|
366
371
|
type: 'boolean',
|
|
367
372
|
description: 'Attach the AI solution summary (from the result field) as a comment to the PR/issue after completion. The summary is extracted from the AI tool JSON output and posted under a "Solution summary" header.',
|
|
@@ -54,6 +54,9 @@ export const handleFailure = async options => {
|
|
|
54
54
|
sanitizeLogContent,
|
|
55
55
|
verbose: argv.verbose,
|
|
56
56
|
errorMessage: cleanErrorMessage(error),
|
|
57
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
58
|
+
requestedModel: argv.model,
|
|
59
|
+
tool: argv.tool || 'claude',
|
|
57
60
|
});
|
|
58
61
|
if (logUploadSuccess) {
|
|
59
62
|
await log('📎 Failure log attached to Pull Request');
|
|
@@ -194,6 +194,9 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
|
|
|
194
194
|
sanitizeLogContent,
|
|
195
195
|
verbose: argv.verbose || false,
|
|
196
196
|
errorMessage: cleanErrorMessage(error),
|
|
197
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
198
|
+
requestedModel: argv.model,
|
|
199
|
+
tool: argv.tool || 'claude',
|
|
197
200
|
});
|
|
198
201
|
|
|
199
202
|
if (logUploadSuccess) {
|
|
@@ -59,6 +59,9 @@ export const createInterruptWrapper = ({ cleanupContext, checkForUncommittedChan
|
|
|
59
59
|
sanitizeLogContent,
|
|
60
60
|
verbose: ctx.argv.verbose || false,
|
|
61
61
|
errorMessage: 'Session interrupted by user (CTRL+C)',
|
|
62
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
63
|
+
requestedModel: ctx.argv.model,
|
|
64
|
+
tool: ctx.argv.tool || 'claude',
|
|
62
65
|
});
|
|
63
66
|
} catch (uploadError) {
|
|
64
67
|
await log(`⚠️ Could not upload logs on interrupt: ${uploadError.message}`, {
|
package/src/solve.mjs
CHANGED
|
@@ -50,7 +50,7 @@ const memoryCheck = await import('./memory-check.mjs');
|
|
|
50
50
|
const lib = await import('./lib.mjs');
|
|
51
51
|
const { log, setLogFile, getLogFile, getAbsoluteLogPath, cleanErrorMessage, formatAligned, getVersionInfo } = lib;
|
|
52
52
|
const githubLib = await import('./github.lib.mjs');
|
|
53
|
-
const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
53
|
+
const { sanitizeLogContent, attachLogToGitHub, getToolDisplayName } = githubLib;
|
|
54
54
|
const validation = await import('./solve.validation.lib.mjs');
|
|
55
55
|
const { validateGitHubUrl, showAttachLogsWarning, initializeLogFile, validateUrlRequirement, validateContinueOnlyOnFeedback, performSystemChecks } = validation;
|
|
56
56
|
const autoContinue = await import('./solve.auto-continue.lib.mjs');
|
|
@@ -79,26 +79,15 @@ const exitHandler = await import('./exit-handler.lib.mjs');
|
|
|
79
79
|
const { initializeExitHandler, installGlobalExitHandlers, safeExit, logActiveHandles } = exitHandler;
|
|
80
80
|
const { createInterruptWrapper } = await import('./solve.interrupt.lib.mjs');
|
|
81
81
|
const getResourceSnapshot = memoryCheck.getResourceSnapshot;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const {
|
|
86
|
-
const
|
|
87
|
-
const {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const { startWorkSession, endWorkSession, SESSION_TYPES } = sessionLib;
|
|
92
|
-
const preparationLib = await import('./solve.preparation.lib.mjs');
|
|
93
|
-
const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions } = preparationLib;
|
|
94
|
-
|
|
95
|
-
// Import model validation library
|
|
96
|
-
const modelValidation = await import('./model-validation.lib.mjs');
|
|
97
|
-
const { validateAndExitOnInvalidModel } = modelValidation;
|
|
98
|
-
const acceptInviteLib = await import('./solve.accept-invite.lib.mjs');
|
|
99
|
-
const { autoAcceptInviteForRepo } = acceptInviteLib;
|
|
100
|
-
|
|
101
|
-
// Initialize log file EARLY (use cwd initially, will be updated after argv parsing)
|
|
82
|
+
const { handleAutoPrCreation } = await import('./solve.auto-pr.lib.mjs');
|
|
83
|
+
const { setupRepositoryAndClone, verifyDefaultBranchAndStatus } = await import('./solve.repo-setup.lib.mjs');
|
|
84
|
+
const { createOrCheckoutBranch } = await import('./solve.branch.lib.mjs');
|
|
85
|
+
const { startWorkSession, endWorkSession, SESSION_TYPES } = await import('./solve.session.lib.mjs');
|
|
86
|
+
const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions } = await import('./solve.preparation.lib.mjs');
|
|
87
|
+
const { validateAndExitOnInvalidModel } = await import('./model-validation.lib.mjs');
|
|
88
|
+
const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
|
|
89
|
+
|
|
90
|
+
// Initialize log file early (before argument parsing) to capture all output
|
|
102
91
|
const logFile = await initializeLogFile(null);
|
|
103
92
|
|
|
104
93
|
// Log version and raw command IMMEDIATELY after log file initialization (ensures they appear even if parsing fails)
|
|
@@ -534,11 +523,16 @@ try {
|
|
|
534
523
|
});
|
|
535
524
|
|
|
536
525
|
// Verify default branch and status using the new module
|
|
526
|
+
// Pass argv, owner, repo, issueUrl for empty repository auto-initialization (--auto-init-repository)
|
|
537
527
|
const defaultBranch = await verifyDefaultBranchAndStatus({
|
|
538
528
|
tempDir,
|
|
539
529
|
log,
|
|
540
530
|
formatAligned,
|
|
541
531
|
$,
|
|
532
|
+
argv,
|
|
533
|
+
owner,
|
|
534
|
+
repo,
|
|
535
|
+
issueUrl,
|
|
542
536
|
});
|
|
543
537
|
// Create or checkout branch using the new module
|
|
544
538
|
const branchName = await createOrCheckoutBranch({
|
|
@@ -939,9 +933,11 @@ try {
|
|
|
939
933
|
// Mark this as a usage limit case for proper formatting
|
|
940
934
|
isUsageLimit: true,
|
|
941
935
|
limitResetTime: global.limitResetTime,
|
|
942
|
-
toolName: (argv.tool
|
|
936
|
+
toolName: getToolDisplayName(argv.tool),
|
|
943
937
|
resumeCommand,
|
|
944
938
|
sessionId,
|
|
939
|
+
requestedModel: argv.model,
|
|
940
|
+
tool: argv.tool || 'claude',
|
|
945
941
|
});
|
|
946
942
|
|
|
947
943
|
if (logUploadSuccess) {
|
|
@@ -999,13 +995,15 @@ try {
|
|
|
999
995
|
// Mark this as a usage limit case for proper formatting
|
|
1000
996
|
isUsageLimit: true,
|
|
1001
997
|
limitResetTime: global.limitResetTime,
|
|
1002
|
-
toolName: (argv.tool
|
|
998
|
+
toolName: getToolDisplayName(argv.tool),
|
|
1003
999
|
resumeCommand,
|
|
1004
1000
|
sessionId,
|
|
1005
1001
|
// Tell attachLogToGitHub that auto-resume is enabled to suppress CLI commands in the comment
|
|
1006
1002
|
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
1007
1003
|
isAutoResumeEnabled: true,
|
|
1008
1004
|
autoResumeMode: limitContinueMode,
|
|
1005
|
+
requestedModel: argv.model,
|
|
1006
|
+
tool: argv.tool || 'claude',
|
|
1009
1007
|
});
|
|
1010
1008
|
|
|
1011
1009
|
if (logUploadSuccess) {
|
|
@@ -1094,12 +1092,14 @@ try {
|
|
|
1094
1092
|
// For usage limit, use a dedicated comment format to make it clear and actionable
|
|
1095
1093
|
isUsageLimit: !!limitReached,
|
|
1096
1094
|
limitResetTime: limitReached ? toolResult.limitResetTime : null,
|
|
1097
|
-
toolName: (argv.tool
|
|
1095
|
+
toolName: getToolDisplayName(argv.tool),
|
|
1098
1096
|
resumeCommand,
|
|
1099
1097
|
// Include sessionId so the PR comment can present it
|
|
1100
1098
|
sessionId,
|
|
1101
1099
|
// If not a usage limit case, fall back to generic failure format
|
|
1102
1100
|
errorMessage: limitReached ? undefined : `${argv.tool.toUpperCase()} execution failed`,
|
|
1101
|
+
requestedModel: argv.model,
|
|
1102
|
+
tool: argv.tool || 'claude',
|
|
1103
1103
|
});
|
|
1104
1104
|
|
|
1105
1105
|
if (logUploadSuccess) {
|
|
@@ -1351,6 +1351,8 @@ try {
|
|
|
1351
1351
|
sessionId,
|
|
1352
1352
|
tempDir,
|
|
1353
1353
|
anthropicTotalCostUSD,
|
|
1354
|
+
requestedModel: argv.model,
|
|
1355
|
+
tool: argv.tool || 'claude',
|
|
1354
1356
|
});
|
|
1355
1357
|
|
|
1356
1358
|
if (logUploadSuccess) {
|
|
@@ -56,7 +56,7 @@ async function setupPrForkRemote(tempDir, argv, prForkOwner, repo, isContinueMod
|
|
|
56
56
|
return await setupPrForkFn(tempDir, argv, prForkOwner, repo, isContinueMode, owner);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned,
|
|
59
|
+
export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned, $, argv, owner, repo, issueUrl }) {
|
|
60
60
|
// Verify we're on the default branch and get its name
|
|
61
61
|
const defaultBranchResult = await $({ cwd: tempDir })`git branch --show-current`;
|
|
62
62
|
|
|
@@ -66,27 +66,128 @@ export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned
|
|
|
66
66
|
throw new Error('Failed to get current branch');
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
let defaultBranch = defaultBranchResult.stdout.toString().trim();
|
|
70
70
|
if (!defaultBranch) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
71
|
+
// Repository is likely empty (no commits) - detect and handle
|
|
72
|
+
const isEmptyRepo = await detectEmptyRepository(tempDir, $);
|
|
73
|
+
|
|
74
|
+
if (isEmptyRepo && argv && argv.autoInitRepository && owner && repo) {
|
|
75
|
+
// --auto-init-repository is enabled, try to initialize
|
|
76
|
+
await log('');
|
|
77
|
+
await log(`${formatAligned('⚠️', 'EMPTY REPOSITORY', 'detected')}`, { level: 'warn' });
|
|
78
|
+
await log(`${formatAligned('', '', `Repository ${owner}/${repo} contains no commits`)}`);
|
|
79
|
+
await log(`${formatAligned('', '', '--auto-init-repository is enabled, attempting initialization...')}`);
|
|
80
|
+
await log('');
|
|
81
|
+
|
|
82
|
+
const repository = await import('./solve.repository.lib.mjs');
|
|
83
|
+
const { tryInitializeEmptyRepository } = repository;
|
|
84
|
+
const initialized = await tryInitializeEmptyRepository(owner, repo);
|
|
85
|
+
|
|
86
|
+
if (initialized) {
|
|
87
|
+
await log('');
|
|
88
|
+
await log(`${formatAligned('🔄', 'Re-fetching:', 'Pulling initialized repository...')}`);
|
|
89
|
+
// Wait for GitHub to process the new file
|
|
90
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
91
|
+
|
|
92
|
+
// Re-fetch the origin to get the new commit
|
|
93
|
+
const fetchResult = await $({ cwd: tempDir })`git fetch origin`;
|
|
94
|
+
if (fetchResult.code !== 0) {
|
|
95
|
+
await log(`${formatAligned('❌', 'Fetch failed:', 'Could not fetch after initialization')}`, { level: 'error' });
|
|
96
|
+
throw new Error('Failed to fetch after empty repository initialization');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Determine default branch name from the remote
|
|
100
|
+
const remoteHeadResult = await $({ cwd: tempDir })`git remote show origin`;
|
|
101
|
+
let remoteBranch = 'main'; // default fallback
|
|
102
|
+
if (remoteHeadResult.code === 0) {
|
|
103
|
+
const remoteOutput = remoteHeadResult.stdout.toString();
|
|
104
|
+
const headMatch = remoteOutput.match(/HEAD branch:\s*(\S+)/);
|
|
105
|
+
if (headMatch) {
|
|
106
|
+
remoteBranch = headMatch[1];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Checkout the remote branch locally
|
|
111
|
+
const checkoutResult = await $({ cwd: tempDir })`git checkout -b ${remoteBranch} origin/${remoteBranch}`;
|
|
112
|
+
if (checkoutResult.code !== 0) {
|
|
113
|
+
// Try alternative: maybe the branch already exists locally somehow
|
|
114
|
+
const altResult = await $({ cwd: tempDir })`git checkout ${remoteBranch}`;
|
|
115
|
+
if (altResult.code !== 0) {
|
|
116
|
+
await log(`${formatAligned('❌', 'Checkout failed:', `Could not checkout ${remoteBranch} after initialization`)}`, { level: 'error' });
|
|
117
|
+
throw new Error('Failed to checkout branch after empty repository initialization');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
defaultBranch = remoteBranch;
|
|
122
|
+
await log(`${formatAligned('✅', 'Repository initialized:', `Now on branch ${defaultBranch}`)}`);
|
|
123
|
+
await log(`\n${formatAligned('📌', 'Default branch:', defaultBranch)}`);
|
|
124
|
+
} else {
|
|
125
|
+
// Auto-init failed - provide helpful message with --auto-init-repository context
|
|
126
|
+
await log('');
|
|
127
|
+
await log(`${formatAligned('❌', 'AUTO-INIT FAILED', '')}`, { level: 'error' });
|
|
128
|
+
await log('');
|
|
129
|
+
await log(' 🔍 What happened:');
|
|
130
|
+
await log(` Repository ${owner}/${repo} is empty (no commits).`);
|
|
131
|
+
await log(' --auto-init-repository was enabled but initialization failed.');
|
|
132
|
+
await log(' You may not have write access to create files in the repository.');
|
|
133
|
+
await log('');
|
|
134
|
+
await log(' 💡 How to fix:');
|
|
135
|
+
await log(' Option 1: Ask repository owner to add initial content');
|
|
136
|
+
await log(' Even a simple README.md file would allow branch creation');
|
|
137
|
+
await log('');
|
|
138
|
+
await log(` Option 2: Manually initialize: gh api repos/${owner}/${repo}/contents/README.md \\`);
|
|
139
|
+
await log(' --method PUT --field message="Initialize repository" \\');
|
|
140
|
+
await log(' --field content="$(echo "# repo" | base64)"');
|
|
141
|
+
await log('');
|
|
142
|
+
|
|
143
|
+
// Post a comment on the issue informing about the empty repository
|
|
144
|
+
await tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ });
|
|
145
|
+
|
|
146
|
+
throw new Error('Empty repository auto-initialization failed');
|
|
147
|
+
}
|
|
148
|
+
} else if (isEmptyRepo) {
|
|
149
|
+
// Empty repo detected but --auto-init-repository is not enabled
|
|
150
|
+
await log('');
|
|
151
|
+
await log(`${formatAligned('❌', 'EMPTY REPOSITORY DETECTED', '')}`, { level: 'error' });
|
|
152
|
+
await log('');
|
|
153
|
+
await log(' 🔍 What happened:');
|
|
154
|
+
await log(` The repository${owner && repo ? ` ${owner}/${repo}` : ''} is empty (no commits).`);
|
|
155
|
+
await log(' Cannot create branches or pull requests on an empty repository.');
|
|
156
|
+
await log('');
|
|
157
|
+
await log(' 💡 How to fix:');
|
|
158
|
+
await log(' Option 1: Use --auto-init-repository flag to automatically create a README.md');
|
|
159
|
+
await log(` solve <issue-url> --auto-init-repository`);
|
|
160
|
+
await log('');
|
|
161
|
+
await log(' Option 2: Ask repository owner to add initial content');
|
|
162
|
+
await log(' Even a simple README.md file would allow branch creation');
|
|
163
|
+
await log('');
|
|
164
|
+
|
|
165
|
+
// Post a comment on the issue informing about the empty repository
|
|
166
|
+
await tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ });
|
|
167
|
+
|
|
168
|
+
throw new Error('Empty repository detected - use --auto-init-repository to initialize');
|
|
169
|
+
} else {
|
|
170
|
+
// Not an empty repo, some other issue with branch detection
|
|
171
|
+
await log('');
|
|
172
|
+
await log(`${formatAligned('❌', 'DEFAULT BRANCH DETECTION FAILED', '')}`, { level: 'error' });
|
|
173
|
+
await log('');
|
|
174
|
+
await log(' 🔍 What happened:');
|
|
175
|
+
await log(" Unable to determine the repository's default branch.");
|
|
176
|
+
await log('');
|
|
177
|
+
await log(' 💡 This might mean:');
|
|
178
|
+
await log(' • Unusual repository configuration');
|
|
179
|
+
await log(' • Git command issues');
|
|
180
|
+
await log('');
|
|
181
|
+
await log(' 🔧 How to fix:');
|
|
182
|
+
await log(' 1. Check repository status');
|
|
183
|
+
await log(` 2. Verify locally: cd ${tempDir} && git branch`);
|
|
184
|
+
await log(` 3. Check remote: cd ${tempDir} && git branch -r`);
|
|
185
|
+
await log('');
|
|
186
|
+
throw new Error('Default branch detection failed');
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
await log(`\n${formatAligned('📌', 'Default branch:', defaultBranch)}`);
|
|
88
190
|
}
|
|
89
|
-
await log(`\n${formatAligned('📌', 'Default branch:', defaultBranch)}`);
|
|
90
191
|
|
|
91
192
|
// Ensure we're on a clean default branch
|
|
92
193
|
const statusResult = await $({ cwd: tempDir })`git status --porcelain`;
|
|
@@ -106,3 +207,79 @@ export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned
|
|
|
106
207
|
|
|
107
208
|
return defaultBranch;
|
|
108
209
|
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Try to post a comment on the issue informing the user about the empty repository.
|
|
213
|
+
* This is a non-critical operation - errors are silently ignored.
|
|
214
|
+
* When --auto-init-repository succeeds, no comment is posted (no action needed from the user).
|
|
215
|
+
*/
|
|
216
|
+
async function tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ }) {
|
|
217
|
+
if (!issueUrl) return;
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const issueMatch = issueUrl.match(/\/issues\/(\d+)/);
|
|
221
|
+
if (!issueMatch) return;
|
|
222
|
+
|
|
223
|
+
const issueNumber = issueMatch[1];
|
|
224
|
+
await log(`${formatAligned('💬', 'Creating comment:', 'Informing about empty repository...')}`);
|
|
225
|
+
|
|
226
|
+
const commentBody = `## ⚠️ Repository Initialization Required
|
|
227
|
+
|
|
228
|
+
Hello! I attempted to work on this issue, but encountered a problem:
|
|
229
|
+
|
|
230
|
+
**Issue**: The repository is empty (no commits) and branches cannot be created.
|
|
231
|
+
**Reason**: Git cannot create branches in a repository with no commits.
|
|
232
|
+
|
|
233
|
+
### 🔧 How to resolve:
|
|
234
|
+
|
|
235
|
+
**Option 1: Use \`--auto-init-repository\` flag**
|
|
236
|
+
Re-run the solver with the \`--auto-init-repository\` flag to automatically create a simple README.md:
|
|
237
|
+
\`\`\`
|
|
238
|
+
solve ${issueUrl} --auto-init-repository
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
**Option 2: Initialize the repository yourself**
|
|
242
|
+
Please add initial content to the repository. Even a simple README.md (even if it is empty or contains just the title) file would make it possible to create branches and work on this issue.
|
|
243
|
+
|
|
244
|
+
Once the repository contains at least one commit with any file, I'll be able to proceed with solving this issue.
|
|
245
|
+
|
|
246
|
+
Thank you!`;
|
|
247
|
+
|
|
248
|
+
const commentResult = await $`gh issue comment ${issueNumber} --repo ${owner}/${repo} --body ${commentBody}`;
|
|
249
|
+
if (commentResult.code === 0) {
|
|
250
|
+
await log(`${formatAligned('✅', 'Comment created:', `Posted to issue #${issueNumber}`)}`);
|
|
251
|
+
} else {
|
|
252
|
+
await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
// Silently ignore comment creation errors - not critical to the process
|
|
256
|
+
await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Detect if a cloned repository is empty (has no commits).
|
|
262
|
+
* An empty repository has no branches and no commits.
|
|
263
|
+
*/
|
|
264
|
+
async function detectEmptyRepository(tempDir, $) {
|
|
265
|
+
// Check if there are any commits in the repository
|
|
266
|
+
const logResult = await $({ cwd: tempDir })`git rev-parse HEAD 2>&1`;
|
|
267
|
+
if (logResult.code !== 0) {
|
|
268
|
+
// git rev-parse HEAD fails when there are no commits
|
|
269
|
+
const output = (logResult.stdout || logResult.stderr || '').toString();
|
|
270
|
+
if (output.includes('unknown revision') || output.includes('bad default revision') || output.includes('does not have any commits')) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Also check if there are any remote branches
|
|
276
|
+
const remoteBranchResult = await $({ cwd: tempDir })`git branch -r`;
|
|
277
|
+
if (remoteBranchResult.code === 0) {
|
|
278
|
+
const branches = remoteBranchResult.stdout.toString().trim();
|
|
279
|
+
if (!branches) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
@@ -317,8 +317,9 @@ export const setupTempDirectory = async (argv, workspaceInfo = null) => {
|
|
|
317
317
|
};
|
|
318
318
|
|
|
319
319
|
// Try to initialize an empty repository by creating a simple README.md
|
|
320
|
-
// This makes the repository forkable
|
|
321
|
-
|
|
320
|
+
// This makes the repository forkable and allows branch creation
|
|
321
|
+
// Exported for use in solve.repo-setup.lib.mjs (direct access path for empty repos)
|
|
322
|
+
export const tryInitializeEmptyRepository = async (owner, repo) => {
|
|
322
323
|
try {
|
|
323
324
|
await log(`${formatAligned('🔧', 'Auto-fix:', 'Attempting to initialize empty repository...')}`);
|
|
324
325
|
|
|
@@ -675,6 +675,9 @@ Fixes ${issueRef}
|
|
|
675
675
|
errorDuringExecution,
|
|
676
676
|
// Issue #1152: Pass sessionType for differentiated log comments
|
|
677
677
|
sessionType,
|
|
678
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
679
|
+
requestedModel: argv.model,
|
|
680
|
+
tool: argv.tool || 'claude',
|
|
678
681
|
});
|
|
679
682
|
}
|
|
680
683
|
|
|
@@ -754,6 +757,9 @@ Fixes ${issueRef}
|
|
|
754
757
|
errorDuringExecution,
|
|
755
758
|
// Issue #1152: Pass sessionType for differentiated log comments
|
|
756
759
|
sessionType,
|
|
760
|
+
// Issue #1225: Pass model and tool info for issue comments
|
|
761
|
+
requestedModel: argv.model,
|
|
762
|
+
tool: argv.tool || 'claude',
|
|
757
763
|
});
|
|
758
764
|
}
|
|
759
765
|
|
|
@@ -838,6 +844,9 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
|
|
|
838
844
|
sanitizeLogContent,
|
|
839
845
|
verbose: argv.verbose || false,
|
|
840
846
|
errorMessage: cleanErrorMessage(error),
|
|
847
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
848
|
+
requestedModel: argv.model,
|
|
849
|
+
tool: argv.tool || 'claude',
|
|
841
850
|
});
|
|
842
851
|
|
|
843
852
|
if (logUploadSuccess) {
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -297,6 +297,9 @@ export const watchForFeedback = async params => {
|
|
|
297
297
|
// Mark if this was a usage limit failure
|
|
298
298
|
isUsageLimit: toolResult.limitReached,
|
|
299
299
|
limitResetTime: toolResult.limitResetTime,
|
|
300
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
301
|
+
requestedModel: argv.model,
|
|
302
|
+
tool: argv.tool || 'claude',
|
|
300
303
|
});
|
|
301
304
|
|
|
302
305
|
if (logUploadSuccess) {
|
|
@@ -363,6 +366,9 @@ export const watchForFeedback = async params => {
|
|
|
363
366
|
// Pass agent tool pricing data when available
|
|
364
367
|
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
365
368
|
pricingInfo: toolResult.pricingInfo,
|
|
369
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
370
|
+
requestedModel: argv.model,
|
|
371
|
+
tool: argv.tool || 'claude',
|
|
366
372
|
});
|
|
367
373
|
|
|
368
374
|
if (logUploadSuccess) {
|