@link-assistant/hive-mind 1.35.0 → 1.35.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.35.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 0cfcb6a: Fix CI/CD changelog formatting when multiple PRs merge before a release (Issue #1452). The merge-changesets script now keeps each changeset as a separate file (only harmonizing bump types) instead of merging descriptions into one, so @changesets/cli produces separate bullet items. Also enhances release notes PR detection to find all related PRs via tag-range merge commit lookup.
8
+ - a689f6b: fix: use result JSON modelUsage for accurate multi-model display in GitHub comments
9
+
10
+ When Claude Code uses multiple models (e.g., main model + subagent), the completion
11
+ comment now correctly displays all models instead of just the main model.
12
+
13
+ ## 1.35.1
14
+
15
+ ### Patch Changes
16
+
17
+ - Fix misleading "Retry after: 0s" message in /limits command when Claude Usage API returns 429. Now shows "Try again later." for zero/missing retry-after values, or proper reset time format (e.g., "Resets in 5m (Mar 19, 8:00pm UTC)") for meaningful values. Also caches 429 errors to prevent repeated requests to rate-limited endpoint, and adds full request/response verbose logging for debugging.
18
+
19
+ improve Solution Draft Log comment formatting for better readability (issue #1448)
20
+
3
21
  ## 1.35.0
4
22
 
5
23
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.35.0",
3
+ "version": "1.35.2",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -848,6 +848,7 @@ export const executeClaudeCommand = async params => {
848
848
  let anthropicTotalCostUSD = null; // Capture Anthropic's official total_cost_usd from result
849
849
  let errorDuringExecution = false; // Issue #1088: Track if error_during_execution subtype occurred
850
850
  let resultSummary = null; // Issue #1263: Capture AI result summary for --attach-solution-summary
851
+ let resultModelUsage = null; // Issue #1454
851
852
  // Create interactive mode handler if enabled
852
853
  let interactiveHandler = null;
853
854
  if (argv.interactiveMode && owner && repo && prNumber) {
@@ -1028,6 +1029,7 @@ export const executeClaudeCommand = async params => {
1028
1029
  resultNumTurns = data.num_turns;
1029
1030
  await log(`šŸ“Š Session num_turns: ${resultNumTurns}`, { verbose: true });
1030
1031
  }
1032
+ if (data.subtype === 'success' && data.modelUsage) resultModelUsage = data.modelUsage; // Issue #1454
1031
1033
  if (data.is_error === true) {
1032
1034
  lastMessage = data.result || JSON.stringify(data);
1033
1035
  const subtype = data.subtype || 'unknown';
@@ -1107,7 +1109,6 @@ export const executeClaudeCommand = async params => {
1107
1109
  if (line.trim() && !line.includes('node:internal')) {
1108
1110
  await log(line, { stream: 'raw' });
1109
1111
  lastMessage = line;
1110
-
1111
1112
  // Issue #1015: Detect terms acceptance prompt (non-JSON "[ACTION REQUIRED]..." message)
1112
1113
  const termsAcceptancePattern = /\[ACTION REQUIRED\].*terms|must run.*claude.*review.*terms/i;
1113
1114
  if (termsAcceptancePattern.test(line)) {
@@ -1182,7 +1183,6 @@ export const executeClaudeCommand = async params => {
1182
1183
  await log(`\nāŒ Command not found (exit code 127) - "${claudePath}" is not installed or not in PATH\n Please ensure Claude CLI is installed: npm install -g @anthropic-ai/claude-code`, { level: 'error' });
1183
1184
  }
1184
1185
  }
1185
-
1186
1186
  // Flush any remaining queued comments from interactive mode
1187
1187
  if (interactiveHandler) {
1188
1188
  try {
@@ -1380,6 +1380,7 @@ export const executeClaudeCommand = async params => {
1380
1380
  anthropicTotalCostUSD, // Pass Anthropic's official total cost
1381
1381
  errorDuringExecution, // Issue #1088: Track if error_during_execution subtype occurred
1382
1382
  resultSummary, // Issue #1263: Include result summary for --attach-solution-summary
1383
+ resultModelUsage, // Issue #1454
1383
1384
  };
1384
1385
  } catch (error) {
1385
1386
  reportError(error, {
@@ -22,7 +22,7 @@ const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) =
22
22
  const hasPricing = pricingInfo && (pricingInfo.modelName || pricingInfo.tokenUsage || pricingInfo.isFreeModel || pricingInfo.isOpencodeFreeModel);
23
23
  const hasOpencodeCost = pricingInfo?.opencodeCost !== null && pricingInfo?.opencodeCost !== undefined;
24
24
  if (!hasPublic && !hasAnthropic && !hasPricing && !hasOpencodeCost) return '';
25
- let costInfo = '\n\nšŸ’° **Cost estimation:**';
25
+ let costInfo = '\n\n### šŸ’° **Cost estimation:**';
26
26
  if (pricingInfo?.modelName) {
27
27
  costInfo += `\n- Model: ${pricingInfo.modelName}`;
28
28
  if (pricingInfo.provider) costInfo += `\n- Provider: ${pricingInfo.provider}`;
@@ -366,21 +366,15 @@ export async function attachLogToGitHub(options) {
366
366
  limitResetTime = null,
367
367
  toolName = 'AI tool',
368
368
  resumeCommand = null,
369
- // Whether auto-resume/auto-restart is enabled (determines if CLI commands should be shown)
370
- // See: https://github.com/link-assistant/hive-mind/issues/1152
371
- isAutoResumeEnabled = false,
369
+ isAutoResumeEnabled = false, // Issue #1152: Whether auto-resume/auto-restart is enabled
372
370
  autoResumeMode = 'resume', // 'resume' or 'restart'
373
- // Session type for differentiating solution draft log comments
374
- // See: https://github.com/link-assistant/hive-mind/issues/1152
375
- sessionType = 'new', // 'new', 'resume', 'auto-resume', 'auto-restart'
376
- // New parameters for agent tool pricing support
377
- publicPricingEstimate = null,
371
+ sessionType = 'new', // Issue #1152: 'new', 'resume', 'auto-resume', 'auto-restart'
372
+ publicPricingEstimate = null, // Agent tool pricing support
378
373
  pricingInfo = null,
379
- // Issue #1088: Track error_during_execution for "Finished with errors" state
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")
374
+ errorDuringExecution = false, // Issue #1088
375
+ requestedModel = null, // Issue #1225: The --model flag value
376
+ tool = null, // The tool used (claude, agent, opencode, codex)
377
+ resultModelUsage = null, // Issue #1454
384
378
  } = options;
385
379
  const targetName = targetType === 'pr' ? 'Pull Request' : 'Issue';
386
380
  const ghCommand = targetType === 'pr' ? 'pr' : 'issue';
@@ -391,15 +385,12 @@ export async function attachLogToGitHub(options) {
391
385
  await log(' āš ļø Log file is empty, skipping upload');
392
386
  return false;
393
387
  }
394
- // Issue #1173: Remove premature size check that blocked large files.
395
- // gh-upload-log can handle files of any size by using repositories for large files.
396
- // For files larger than fileMaxSize, we'll skip inline comment attempt and go directly to gh-upload-log.
388
+ // Issue #1173: gh-upload-log handles large files; skip inline comment for files > fileMaxSize
397
389
  const useLargeFileMode = logStats.size > githubLimits.fileMaxSize;
398
390
  if (useLargeFileMode && verbose) {
399
391
  await log(` šŸ“ Large log file (${Math.round(logStats.size / 1024 / 1024)}MB), will use gh-upload-log`, { verbose: true });
400
392
  }
401
- // Calculate token usage if sessionId and tempDir are provided
402
- // For agent tool, publicPricingEstimate is already provided, so we skip Claude-specific calculation
393
+ // Calculate token usage if sessionId and tempDir are provided (skip for agent tool with pricing)
403
394
  let totalCostUSD = publicPricingEstimate;
404
395
  // Issue #1225: Collect actual model IDs from Claude session JSON output
405
396
  let actualModelIds = null;
@@ -429,6 +420,15 @@ export async function attachLogToGitHub(options) {
429
420
  }
430
421
  }
431
422
  }
423
+ // Issue #1454: Use resultModelUsage from result JSON when it has more models (includes subagent models)
424
+ if (resultModelUsage && typeof resultModelUsage === 'object') {
425
+ const ids = Object.keys(resultModelUsage);
426
+ if (ids.length > 0 && (!actualModelIds || ids.length > actualModelIds.length)) {
427
+ ids.sort((a, b) => (resultModelUsage[b]?.costUSD ?? 0) - (resultModelUsage[a]?.costUSD ?? 0));
428
+ actualModelIds = ids;
429
+ if (verbose) await log(` šŸ¤– Using result JSON modelUsage (${ids.length} models): ${ids.join(', ')}`, { verbose: true });
430
+ }
431
+ }
432
432
  // For agent tool, extract actual model ID from pricingInfo (Issue #1225)
433
433
  if (!actualModelIds && pricingInfo?.modelId) {
434
434
  actualModelIds = [pricingInfo.modelId];
@@ -557,7 +557,7 @@ ${logContent}
557
557
  logComment = `## āš ļø Solution Draft Finished with Errors
558
558
  This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${modelInfoString}
559
559
 
560
- **Note**: The session encountered errors during execution, but some work may have been completed. Please review the changes carefully.
560
+ > **Note**: The session encountered errors during execution, but some work may have been completed. Please review the changes carefully.
561
561
 
562
562
  <details>
563
563
  <summary>Click to expand solution draft log (${Math.round(logStats.size / 1024)}KB)</summary>
@@ -714,8 +714,8 @@ ${resumeCommand}
714
714
 
715
715
  logUploadComment += `${modelInfoString}
716
716
 
717
- šŸ“Ž **Execution log uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
718
- šŸ”— [View complete execution log](${logUrl})
717
+ ### šŸ“Ž **Execution log uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
718
+ - [View complete execution log](${logUrl})
719
719
 
720
720
  ---
721
721
  *This session was interrupted due to usage limits. You can resume once the limit resets.*`;
@@ -726,8 +726,10 @@ The automated solution draft encountered an error:
726
726
  \`\`\`
727
727
  ${errorMessage}
728
728
  \`\`\`${modelInfoString}
729
- šŸ“Ž **Failure log uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
730
- šŸ”— [View complete failure log](${logUrl})
729
+
730
+ ### šŸ“Ž **Failure log uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
731
+ - [View complete failure log](${logUrl})
732
+
731
733
  ---
732
734
  *Now working session is ended, feel free to review and add any feedback on the solution draft.*`;
733
735
  } else if (errorDuringExecution) {
@@ -736,10 +738,11 @@ ${errorMessage}
736
738
  logUploadComment = `## āš ļø Solution Draft Finished with Errors
737
739
  This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${modelInfoString}
738
740
 
739
- **Note**: The session encountered errors during execution, but some work may have been completed. Please review the changes carefully.
741
+ > **Note**: The session encountered errors during execution, but some work may have been completed. Please review the changes carefully.
742
+
743
+ ### šŸ“Ž **Log file uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
744
+ - [View complete solution draft log](${logUrl})
740
745
 
741
- šŸ“Ž **Log file uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
742
- šŸ”— [View complete solution draft log](${logUrl})
743
746
  ---
744
747
  *Now working session is ended, feel free to review and add any feedback on the solution draft.*`;
745
748
  } else {
@@ -761,8 +764,10 @@ This log file contains the complete execution trace of the AI ${targetType === '
761
764
  }
762
765
  logUploadComment = `## ${title}
763
766
  This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.${costInfo}${modelInfoString}
764
- ${sessionNote}šŸ“Ž **Log file uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
765
- šŸ”— [View complete solution draft log](${logUrl})
767
+ ${sessionNote}
768
+ ### šŸ“Ž **Log file uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
769
+ - [View complete solution draft log](${logUrl})
770
+
766
771
  ---
767
772
  *Now working session is ended, feel free to review and add any feedback on the solution draft.*`;
768
773
  }
@@ -61,6 +61,63 @@ async function readCredentials(credentialsPath = DEFAULT_CREDENTIALS_PATH, verbo
61
61
  }
62
62
  }
63
63
 
64
+ /**
65
+ * Format a retry-after value into a user-friendly message.
66
+ * The retry-after header can be either a number of seconds or an HTTP-date.
67
+ * Handles edge cases like 0, missing, or negative values gracefully.
68
+ *
69
+ * @param {string|null} retryAfter - Value of the retry-after header
70
+ * @returns {string} Formatted message part (e.g., " Resets in 2m 30s (Mar 19, 8:00pm UTC)" or " Try again later.")
71
+ * @see https://github.com/link-assistant/hive-mind/issues/1446
72
+ */
73
+ export function formatRetryAfterMessage(retryAfter) {
74
+ if (retryAfter === null || retryAfter === undefined) {
75
+ return ' Try again later.';
76
+ }
77
+
78
+ // Try to parse as number of seconds first
79
+ const seconds = Number(retryAfter);
80
+ if (!Number.isNaN(seconds) && seconds > 0) {
81
+ // Calculate reset time from now + seconds
82
+ const resetAt = dayjs().add(seconds, 'second').utc();
83
+ const resetTimeStr = resetAt.format('MMM D, h:mma');
84
+
85
+ // Format relative time
86
+ const totalMinutes = Math.floor(seconds / 60);
87
+ const remainingSeconds = Math.round(seconds % 60);
88
+ const hours = Math.floor(totalMinutes / 60);
89
+ const minutes = totalMinutes % 60;
90
+
91
+ let relativeStr;
92
+ if (hours > 0) {
93
+ relativeStr = `${hours}h ${minutes}m`;
94
+ } else if (minutes > 0) {
95
+ relativeStr = remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
96
+ } else {
97
+ relativeStr = `${remainingSeconds}s`;
98
+ }
99
+
100
+ return ` Resets in ${relativeStr} (${resetTimeStr} UTC)`;
101
+ }
102
+
103
+ // Try to parse as HTTP-date (e.g., "Wed, 21 Oct 2015 07:28:00 GMT")
104
+ const retryDate = dayjs(retryAfter);
105
+ if (retryDate.isValid()) {
106
+ const diffMs = retryDate.diff(dayjs());
107
+ if (diffMs > 0) {
108
+ const totalMinutes = Math.floor(diffMs / (1000 * 60));
109
+ const hours = Math.floor(totalMinutes / 60);
110
+ const minutes = totalMinutes % 60;
111
+ const relativeStr = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
112
+ const resetTimeStr = retryDate.utc().format('MMM D, h:mma');
113
+ return ` Resets in ${relativeStr} (${resetTimeStr} UTC)`;
114
+ }
115
+ }
116
+
117
+ // Fallback for 0, negative, or unparseable values - don't show misleading info
118
+ return ' Try again later.';
119
+ }
120
+
64
121
  /**
65
122
  * Format an ISO date string to a human-readable reset time using dayjs
66
123
  *
@@ -534,31 +591,47 @@ export async function getClaudeUsageLimits(verbose = false, credentialsPath = DE
534
591
  };
535
592
  }
536
593
 
594
+ const requestHeaders = {
595
+ Accept: 'application/json',
596
+ 'Content-Type': 'application/json',
597
+ 'User-Agent': 'claude-code/2.0.55',
598
+ Authorization: `Bearer ${accessToken}`,
599
+ 'anthropic-beta': 'oauth-2025-04-20',
600
+ };
601
+
537
602
  if (verbose) {
538
603
  console.log('[VERBOSE] /limits fetching usage from API...');
604
+ console.log(`[VERBOSE] /limits API request: GET ${USAGE_API_ENDPOINT}`);
605
+ // Log request headers with sanitized Authorization (show only last 8 chars)
606
+ const sanitizedHeaders = { ...requestHeaders };
607
+ if (sanitizedHeaders.Authorization) {
608
+ const token = sanitizedHeaders.Authorization;
609
+ sanitizedHeaders.Authorization = `Bearer ...${token.slice(-8)}`;
610
+ }
611
+ console.log('[VERBOSE] /limits API request headers:', JSON.stringify(sanitizedHeaders, null, 2));
539
612
  }
540
613
 
541
614
  // Call the Anthropic OAuth usage API
542
615
  const response = await fetch(USAGE_API_ENDPOINT, {
543
616
  method: 'GET',
544
- headers: {
545
- Accept: 'application/json',
546
- 'Content-Type': 'application/json',
547
- 'User-Agent': 'claude-code/2.0.55',
548
- Authorization: `Bearer ${accessToken}`,
549
- 'anthropic-beta': 'oauth-2025-04-20',
550
- },
617
+ headers: requestHeaders,
551
618
  });
552
619
 
553
- // Log HTTP response status for debugging (always, not just on error)
620
+ // Log HTTP response status and headers for debugging (always in verbose mode, not just on error)
554
621
  if (verbose) {
555
622
  console.log(`[VERBOSE] /limits API HTTP status: ${response.status} ${response.statusText}`);
623
+ // Log all response headers for debugging
624
+ const responseHeaders = {};
625
+ response.headers.forEach((value, key) => {
626
+ responseHeaders[key] = value;
627
+ });
628
+ console.log('[VERBOSE] /limits API response headers:', JSON.stringify(responseHeaders, null, 2));
556
629
  }
557
630
 
558
631
  if (!response.ok) {
559
632
  const errorText = await response.text();
560
633
  if (verbose) {
561
- console.error('[VERBOSE] /limits API error:', response.status, errorText);
634
+ console.error('[VERBOSE] /limits API error body:', errorText);
562
635
  }
563
636
 
564
637
  // Check for specific error conditions
@@ -574,7 +647,7 @@ export async function getClaudeUsageLimits(verbose = false, credentialsPath = DE
574
647
  const retryAfter = response.headers.get('retry-after');
575
648
  return {
576
649
  success: false,
577
- error: `Rate limited by Claude Usage API. ${retryAfter ? `Retry after: ${retryAfter}s` : 'Try again later.'}`,
650
+ error: `Claude Usage API access has reached rate limit.${formatRetryAfterMessage(retryAfter)}`,
578
651
  };
579
652
  }
580
653
 
@@ -587,7 +660,7 @@ export async function getClaudeUsageLimits(verbose = false, credentialsPath = DE
587
660
  const data = await response.json();
588
661
 
589
662
  if (verbose) {
590
- console.log('[VERBOSE] /limits API response:', JSON.stringify(data, null, 2));
663
+ console.log('[VERBOSE] /limits API response body:', JSON.stringify(data, null, 2));
591
664
  }
592
665
 
593
666
  // Parse the API response
@@ -971,9 +1044,23 @@ export async function getCachedClaudeLimits(verbose = false) {
971
1044
  if (verbose) console.log('[VERBOSE] /limits-cache: Using cached Claude limits (TTL: ' + Math.round(CACHE_TTL.USAGE_API / 60000) + ' minutes)');
972
1045
  return cached;
973
1046
  }
1047
+ // Also check if we have a cached rate-limit error to avoid hammering a 429'd endpoint
1048
+ const cachedError = cache.get('claude-rate-limited', CACHE_TTL.USAGE_API);
1049
+ if (cachedError) {
1050
+ if (verbose) console.log('[VERBOSE] /limits-cache: Using cached rate-limit error (avoiding repeated 429 requests)');
1051
+ return cachedError;
1052
+ }
974
1053
  if (verbose) console.log('[VERBOSE] /limits-cache: Cache miss for Claude limits, fetching from API...');
975
1054
  const result = await getClaudeUsageLimits(verbose);
976
- if (result.success) cache.set('claude', result, CACHE_TTL.USAGE_API);
1055
+ if (result.success) {
1056
+ cache.set('claude', result, CACHE_TTL.USAGE_API);
1057
+ } else if (result.error && result.error.includes('Rate limited')) {
1058
+ // Cache rate-limit errors to prevent hammering the API
1059
+ // Use the same 20-minute TTL as successful responses
1060
+ // See: https://github.com/link-assistant/hive-mind/issues/1446
1061
+ cache.set('claude-rate-limited', result, CACHE_TTL.USAGE_API);
1062
+ if (verbose) console.log('[VERBOSE] /limits-cache: Cached rate-limit error for ' + Math.round(CACHE_TTL.USAGE_API / 60000) + ' minutes');
1063
+ }
977
1064
  return result;
978
1065
  }
979
1066
 
@@ -1040,6 +1127,7 @@ export default {
1040
1127
  getProgressBar,
1041
1128
  calculateTimePassedPercentage,
1042
1129
  formatUsageMessage,
1130
+ formatRetryAfterMessage,
1043
1131
  // Threshold constants for progress bar visualization
1044
1132
  DISPLAY_THRESHOLDS,
1045
1133
  // Cache management
@@ -179,7 +179,7 @@ export const buildModelInfoString = ({ requestedModel = null, tool = null, prici
179
179
 
180
180
  if (!hasRequested && !hasModelsUsed && !hasModelInfo && !hasPricingModel) return '';
181
181
 
182
- let info = '\n\nšŸ¤– **Models used:**';
182
+ let info = '\n\n### šŸ¤– **Models used:**';
183
183
 
184
184
  // Display tool name
185
185
  if (tool) {
@@ -201,26 +201,26 @@ export const buildModelInfoString = ({ requestedModel = null, tool = null, prici
201
201
 
202
202
  // Build main model line
203
203
  const mainModelName = mainModelMeta?.name || mainModelId;
204
- const mainModelProvider = mainModelMeta?.provider || null;
205
- const mainModelKnowledge = mainModelMeta?.knowledge || null;
204
+
205
+ // Use "Model" label when only one model, "Main model" when multiple
206
+ const modelLabel = supportingEntries.length > 0 ? 'Main model' : 'Model';
206
207
 
207
208
  if (mainMatches) {
208
- info += `\n- **Main model: ${mainModelName}** (ID: \`${mainModelId}\`${mainModelProvider ? `, ${mainModelProvider}` : ''}${mainModelKnowledge ? `, cutoff: ${mainModelKnowledge}` : ''})`;
209
+ info += `\n- **${modelLabel}: ${mainModelName}** (\`${mainModelId}\`)`;
209
210
  } else {
210
211
  // Main model doesn't match requested - show warning
211
- info += `\n- **Main model: ${mainModelName}** (ID: \`${mainModelId}\`${mainModelProvider ? `, ${mainModelProvider}` : ''}${mainModelKnowledge ? `, cutoff: ${mainModelKnowledge}` : ''})`;
212
+ info += `\n- **${modelLabel}: ${mainModelName}** (\`${mainModelId}\`)`;
212
213
  if (hasRequested) {
213
214
  info += `\n- āš ļø **Warning**: Main model \`${mainModelId}\` does not match requested model \`${requestedModel}\``;
214
215
  }
215
216
  }
216
217
 
217
- // Display supporting models
218
+ // Display additional models
218
219
  if (supportingEntries.length > 0) {
219
- info += '\n- Supporting models:';
220
+ info += '\n- **Additional models:**';
220
221
  for (const entry of supportingEntries) {
221
222
  const name = entry.modelInfo?.name || entry.modelId;
222
- const provider = entry.modelInfo?.provider || null;
223
- info += `\n - ${name} (\`${entry.modelId}\`${provider ? `, ${provider}` : ''})`;
223
+ info += `\n * **${name}** (\`${entry.modelId}\`)`;
224
224
  }
225
225
  }
226
226
  } else if (hasModelInfo) {
package/src/solve.mjs CHANGED
@@ -864,6 +864,7 @@ try {
864
864
  let pricingInfo = toolResult.pricingInfo; // Used by agent tool for detailed pricing
865
865
  let errorDuringExecution = toolResult.errorDuringExecution || false; // Issue #1088: Track error_during_execution
866
866
  let resultSummary = toolResult.resultSummary || null; // Issue #1263: Capture result summary for --attach-solution-summary
867
+ let resultModelUsage = toolResult.resultModelUsage || null; // Issue #1454: Capture modelUsage from result JSON
867
868
  limitReached = toolResult.limitReached;
868
869
  cleanupContext.limitReached = limitReached;
869
870
 
@@ -938,6 +939,8 @@ try {
938
939
  sessionId,
939
940
  requestedModel: argv.model,
940
941
  tool: argv.tool || 'claude',
942
+ // Issue #1454: Pass resultModelUsage for accurate multi-model display
943
+ resultModelUsage,
941
944
  });
942
945
 
943
946
  if (logUploadSuccess) {
@@ -1004,6 +1007,8 @@ try {
1004
1007
  autoResumeMode: limitContinueMode,
1005
1008
  requestedModel: argv.model,
1006
1009
  tool: argv.tool || 'claude',
1010
+ // Issue #1454: Pass resultModelUsage for accurate multi-model display
1011
+ resultModelUsage,
1007
1012
  });
1008
1013
 
1009
1014
  if (logUploadSuccess) {
@@ -1100,6 +1105,8 @@ try {
1100
1105
  errorMessage: limitReached ? undefined : `${argv.tool.toUpperCase()} execution failed`,
1101
1106
  requestedModel: argv.model,
1102
1107
  tool: argv.tool || 'claude',
1108
+ // Issue #1454: Pass resultModelUsage for accurate multi-model display
1109
+ resultModelUsage,
1103
1110
  });
1104
1111
 
1105
1112
  if (logUploadSuccess) {
@@ -1183,7 +1190,7 @@ try {
1183
1190
  }
1184
1191
 
1185
1192
  // Search for newly created pull requests and comments
1186
- const verifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType);
1193
+ const verifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage);
1187
1194
  const logsAlreadyUploaded = verifyResult?.logUploadSuccess || false;
1188
1195
 
1189
1196
  // Issue #1162: Auto-restart when PR title/description still has placeholder content
@@ -1230,7 +1237,7 @@ try {
1230
1237
  await cleanupClaudeFile(tempDir, branchName, null, argv);
1231
1238
 
1232
1239
  // Re-verify results after restart (without auto-restart flag to prevent recursion)
1233
- const reVerifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, { ...argv, autoRestartOnNonUpdatedPullRequestDescription: false }, shouldAttachLogs, false, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType);
1240
+ const reVerifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, { ...argv, autoRestartOnNonUpdatedPullRequestDescription: false }, shouldAttachLogs, false, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage);
1234
1241
 
1235
1242
  if (reVerifyResult?.prTitleHasPlaceholder || reVerifyResult?.prBodyHasPlaceholder) {
1236
1243
  await log('āš ļø PR title/description still not updated after restart');
@@ -1353,6 +1360,8 @@ try {
1353
1360
  anthropicTotalCostUSD,
1354
1361
  requestedModel: argv.model,
1355
1362
  tool: argv.tool || 'claude',
1363
+ // Issue #1454: Pass resultModelUsage for accurate multi-model display
1364
+ resultModelUsage,
1356
1365
  });
1357
1366
 
1358
1367
  if (logUploadSuccess) {
@@ -494,7 +494,7 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
494
494
  };
495
495
 
496
496
  // Verify results by searching for new PRs and comments
497
- export const verifyResults = async (owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart = false, sessionId = null, tempDir = null, anthropicTotalCostUSD = null, publicPricingEstimate = null, pricingInfo = null, errorDuringExecution = false, sessionType = 'new') => {
497
+ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart = false, sessionId = null, tempDir = null, anthropicTotalCostUSD = null, publicPricingEstimate = null, pricingInfo = null, errorDuringExecution = false, sessionType = 'new', resultModelUsage = null) => {
498
498
  await log('\nšŸ” Searching for created pull requests or comments...');
499
499
 
500
500
  try {
@@ -711,6 +711,8 @@ Fixes ${issueRef}
711
711
  // Issue #1225: Pass model and tool info for PR comments
712
712
  requestedModel: argv.model,
713
713
  tool: argv.tool || 'claude',
714
+ // Issue #1454: Pass resultModelUsage for accurate multi-model display
715
+ resultModelUsage,
714
716
  });
715
717
  }
716
718
 
@@ -793,6 +795,8 @@ Fixes ${issueRef}
793
795
  // Issue #1225: Pass model and tool info for issue comments
794
796
  requestedModel: argv.model,
795
797
  tool: argv.tool || 'claude',
798
+ // Issue #1454: Pass resultModelUsage for accurate multi-model display
799
+ resultModelUsage,
796
800
  });
797
801
  }
798
802