@link-assistant/hive-mind 1.33.0 ā 1.34.1
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 +14 -0
- package/package.json +1 -1
- package/src/github.lib.mjs +46 -49
- package/src/model-info.lib.mjs +360 -0
- package/src/solve.auto-merge.lib.mjs +3 -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 +23 -26
- package/src/solve.results.lib.mjs +9 -0
- package/src/solve.watch.lib.mjs +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.34.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0f02dc5: Better wording for auto-restart comment
|
|
8
|
+
|
|
9
|
+
Updated the auto-restart comment to say "Starting new session to review and commit or discard them" instead of "Starting new session to review and commit them". This makes the wording consistent with the system prompts that already instruct the AI to either COMMIT or REVERT (discard) uncommitted changes.
|
|
10
|
+
|
|
11
|
+
## 1.34.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
3
17
|
## 1.33.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
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
|
}
|
|
@@ -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)
|
|
@@ -944,9 +933,11 @@ try {
|
|
|
944
933
|
// Mark this as a usage limit case for proper formatting
|
|
945
934
|
isUsageLimit: true,
|
|
946
935
|
limitResetTime: global.limitResetTime,
|
|
947
|
-
toolName: (argv.tool
|
|
936
|
+
toolName: getToolDisplayName(argv.tool),
|
|
948
937
|
resumeCommand,
|
|
949
938
|
sessionId,
|
|
939
|
+
requestedModel: argv.model,
|
|
940
|
+
tool: argv.tool || 'claude',
|
|
950
941
|
});
|
|
951
942
|
|
|
952
943
|
if (logUploadSuccess) {
|
|
@@ -1004,13 +995,15 @@ try {
|
|
|
1004
995
|
// Mark this as a usage limit case for proper formatting
|
|
1005
996
|
isUsageLimit: true,
|
|
1006
997
|
limitResetTime: global.limitResetTime,
|
|
1007
|
-
toolName: (argv.tool
|
|
998
|
+
toolName: getToolDisplayName(argv.tool),
|
|
1008
999
|
resumeCommand,
|
|
1009
1000
|
sessionId,
|
|
1010
1001
|
// Tell attachLogToGitHub that auto-resume is enabled to suppress CLI commands in the comment
|
|
1011
1002
|
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
1012
1003
|
isAutoResumeEnabled: true,
|
|
1013
1004
|
autoResumeMode: limitContinueMode,
|
|
1005
|
+
requestedModel: argv.model,
|
|
1006
|
+
tool: argv.tool || 'claude',
|
|
1014
1007
|
});
|
|
1015
1008
|
|
|
1016
1009
|
if (logUploadSuccess) {
|
|
@@ -1099,12 +1092,14 @@ try {
|
|
|
1099
1092
|
// For usage limit, use a dedicated comment format to make it clear and actionable
|
|
1100
1093
|
isUsageLimit: !!limitReached,
|
|
1101
1094
|
limitResetTime: limitReached ? toolResult.limitResetTime : null,
|
|
1102
|
-
toolName: (argv.tool
|
|
1095
|
+
toolName: getToolDisplayName(argv.tool),
|
|
1103
1096
|
resumeCommand,
|
|
1104
1097
|
// Include sessionId so the PR comment can present it
|
|
1105
1098
|
sessionId,
|
|
1106
1099
|
// If not a usage limit case, fall back to generic failure format
|
|
1107
1100
|
errorMessage: limitReached ? undefined : `${argv.tool.toUpperCase()} execution failed`,
|
|
1101
|
+
requestedModel: argv.model,
|
|
1102
|
+
tool: argv.tool || 'claude',
|
|
1108
1103
|
});
|
|
1109
1104
|
|
|
1110
1105
|
if (logUploadSuccess) {
|
|
@@ -1270,8 +1265,8 @@ try {
|
|
|
1270
1265
|
await log('');
|
|
1271
1266
|
await log('š AUTO-RESTART: Uncommitted changes detected');
|
|
1272
1267
|
await log(' Starting temporary monitoring cycle (NOT --watch mode)');
|
|
1273
|
-
await log(' The tool will run once more to commit the changes');
|
|
1274
|
-
await log(' Will exit automatically after changes are committed');
|
|
1268
|
+
await log(' The tool will run once more to commit or discard the changes');
|
|
1269
|
+
await log(' Will exit automatically after changes are committed or discarded');
|
|
1275
1270
|
await log('');
|
|
1276
1271
|
}
|
|
1277
1272
|
|
|
@@ -1356,6 +1351,8 @@ try {
|
|
|
1356
1351
|
sessionId,
|
|
1357
1352
|
tempDir,
|
|
1358
1353
|
anthropicTotalCostUSD,
|
|
1354
|
+
requestedModel: argv.model,
|
|
1355
|
+
tool: argv.tool || 'claude',
|
|
1359
1356
|
});
|
|
1360
1357
|
|
|
1361
1358
|
if (logUploadSuccess) {
|
|
@@ -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
|
@@ -189,7 +189,7 @@ export const watchForFeedback = async params => {
|
|
|
189
189
|
uncommittedFilesList = '\n\n**Uncommitted files:**\n```\n' + changes.join('\n') + '\n```';
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
const commentBody = `## š Auto-restart ${autoRestartCount}/${maxAutoRestartIterations}\n\nDetected uncommitted changes from previous run. Starting new session to review and commit them.${uncommittedFilesList}\n\n---\n*Auto-restart will stop after changes are committed or after ${remainingIterations} more iteration${remainingIterations !== 1 ? 's' : ''}. Please wait until working session will end and give your feedback.*`;
|
|
192
|
+
const commentBody = `## š Auto-restart ${autoRestartCount}/${maxAutoRestartIterations}\n\nDetected uncommitted changes from previous run. Starting new session to review and commit or discard them.${uncommittedFilesList}\n\n---\n*Auto-restart will stop after changes are committed or discarded, or after ${remainingIterations} more iteration${remainingIterations !== 1 ? 's' : ''}. Please wait until working session will end and give your feedback.*`;
|
|
193
193
|
await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${commentBody}`;
|
|
194
194
|
await log(formatAligned('', 'š¬ Posted auto-restart notification to PR', '', 2));
|
|
195
195
|
} catch (commentError) {
|
|
@@ -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) {
|