@link-assistant/hive-mind 1.40.1 → 1.41.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 +17 -0
- package/package.json +1 -1
- package/src/claude.budget-stats.lib.mjs +117 -26
- package/src/claude.lib.mjs +11 -5
- package/src/github.lib.mjs +1 -1
- package/src/solve.auto-merge.lib.mjs +17 -0
- package/src/solve.results.lib.mjs +1 -1
- package/src/solve.watch.lib.mjs +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.41.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2c9396d: feat: simplify Dockerfile — bump sandbox 1.5.0→1.6.0, remove Playwright setup, eliminate USER root, remove silent fallbacks (#1505)
|
|
8
|
+
|
|
9
|
+
## 1.40.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 3dbbe9c: fix: improve context, token and cost estimation accuracy for multi-model sessions (#1508)
|
|
14
|
+
- Merge resultModelUsage from Claude Code result JSON into JSONL-based calculations to include sub-agent model tokens (e.g., Haiku) that are missing from JSONL
|
|
15
|
+
- Split token and context usage per model in budget stats PR comments
|
|
16
|
+
- Show per-model cost breakdown in budget stats
|
|
17
|
+
- Fix sub-sessions being duplicated under each model heading in multi-model mode
|
|
18
|
+
- Add verbose diagnostics indicating when token data is sourced from result JSON vs JSONL
|
|
19
|
+
|
|
3
20
|
## 1.40.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -200,6 +200,49 @@ export const displayBudgetStats = async (usage, tokenUsage, log) => {
|
|
|
200
200
|
await log(` Total output tokens: ${formatNumber(usage.outputTokens)}`);
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Merge resultModelUsage from Claude Code result JSON into JSONL-based modelUsage map.
|
|
205
|
+
* Issue #1508: The JSONL file may miss sub-agent model entries (e.g., Haiku used internally),
|
|
206
|
+
* while resultModelUsage from the success result event has the authoritative per-model breakdown.
|
|
207
|
+
* @param {Object} modelUsage - Map of model ID to accumulated usage from JSONL parsing
|
|
208
|
+
* @param {Object} resultModelUsage - Per-model usage from Claude Code result JSON event
|
|
209
|
+
*/
|
|
210
|
+
export const mergeResultModelUsage = (modelUsage, resultModelUsage) => {
|
|
211
|
+
if (!resultModelUsage || typeof resultModelUsage !== 'object') return;
|
|
212
|
+
for (const [modelId, resultUsage] of Object.entries(resultModelUsage)) {
|
|
213
|
+
if (modelId.startsWith('<') && modelId.endsWith('>')) continue;
|
|
214
|
+
if (!modelUsage[modelId]) {
|
|
215
|
+
modelUsage[modelId] = {
|
|
216
|
+
inputTokens: resultUsage.inputTokens || 0,
|
|
217
|
+
cacheCreationTokens: resultUsage.cacheCreationInputTokens || 0,
|
|
218
|
+
cacheCreation5mTokens: 0,
|
|
219
|
+
cacheCreation1hTokens: 0,
|
|
220
|
+
cacheReadTokens: resultUsage.cacheReadInputTokens || 0,
|
|
221
|
+
outputTokens: resultUsage.outputTokens || 0,
|
|
222
|
+
webSearchRequests: resultUsage.webSearchRequests || 0,
|
|
223
|
+
_sourceResultJson: true,
|
|
224
|
+
};
|
|
225
|
+
if (resultUsage.costUSD != null) {
|
|
226
|
+
modelUsage[modelId]._resultCostUSD = resultUsage.costUSD;
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
const jsonlUsage = modelUsage[modelId];
|
|
230
|
+
const jsonlTotal = jsonlUsage.inputTokens + jsonlUsage.cacheCreationTokens + jsonlUsage.cacheReadTokens + jsonlUsage.outputTokens;
|
|
231
|
+
const resultTotal = (resultUsage.inputTokens || 0) + (resultUsage.cacheCreationInputTokens || 0) + (resultUsage.cacheReadInputTokens || 0) + (resultUsage.outputTokens || 0);
|
|
232
|
+
if (resultTotal > jsonlTotal) {
|
|
233
|
+
jsonlUsage.inputTokens = resultUsage.inputTokens || 0;
|
|
234
|
+
jsonlUsage.cacheCreationTokens = resultUsage.cacheCreationInputTokens || 0;
|
|
235
|
+
jsonlUsage.cacheReadTokens = resultUsage.cacheReadInputTokens || 0;
|
|
236
|
+
jsonlUsage.outputTokens = resultUsage.outputTokens || 0;
|
|
237
|
+
jsonlUsage._sourceResultJson = true;
|
|
238
|
+
}
|
|
239
|
+
if (resultUsage.costUSD != null) {
|
|
240
|
+
jsonlUsage._resultCostUSD = resultUsage.costUSD;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
203
246
|
/**
|
|
204
247
|
* Format a token count with K/M suffix for compact display
|
|
205
248
|
* @param {number} tokens - Token count
|
|
@@ -212,9 +255,43 @@ const formatTokensCompact = tokens => {
|
|
|
212
255
|
};
|
|
213
256
|
|
|
214
257
|
/**
|
|
215
|
-
*
|
|
258
|
+
* Format sub-sessions list for budget stats display
|
|
259
|
+
* @param {Array} subSessions - Array of sub-session usage objects
|
|
260
|
+
* @param {number|null} contextLimit - Context window limit for the model
|
|
261
|
+
* @param {number|null} outputLimit - Output token limit for the model
|
|
262
|
+
* @returns {string} Formatted sub-sessions string
|
|
263
|
+
*/
|
|
264
|
+
const formatSubSessionsList = (subSessions, contextLimit, outputLimit) => {
|
|
265
|
+
let result = '\n\nSub sessions (between compact events):';
|
|
266
|
+
for (let i = 0; i < subSessions.length; i++) {
|
|
267
|
+
const sub = subSessions[i];
|
|
268
|
+
const subPeakContext = sub.peakContextUsage || 0;
|
|
269
|
+
const subTotalInput = sub.inputTokens + sub.cacheCreationTokens + sub.cacheReadTokens;
|
|
270
|
+
let line = `\n${i + 1}. `;
|
|
271
|
+
if (contextLimit && subPeakContext > 0) {
|
|
272
|
+
const pct = ((subPeakContext / contextLimit) * 100).toFixed(0);
|
|
273
|
+
line += `${formatTokensCompact(subPeakContext)} / ${formatTokensCompact(contextLimit)} input tokens (${pct}%)`;
|
|
274
|
+
} else {
|
|
275
|
+
line += `${formatTokensCompact(subTotalInput)} input tokens`;
|
|
276
|
+
}
|
|
277
|
+
if (outputLimit) {
|
|
278
|
+
const outPct = ((sub.outputTokens / outputLimit) * 100).toFixed(0);
|
|
279
|
+
line += `; ${formatTokensCompact(sub.outputTokens)} / ${formatTokensCompact(outputLimit)} output tokens (${outPct}%)`;
|
|
280
|
+
} else {
|
|
281
|
+
line += `; ${formatTokensCompact(sub.outputTokens)} output tokens`;
|
|
282
|
+
}
|
|
283
|
+
result += line;
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Build budget stats string for GitHub PR comments (Issue #1491, #1501, #1508)
|
|
216
290
|
* Format requested by user: sub-sessions between compactification events,
|
|
217
291
|
* per-model breakdown, cumulative totals with cached tokens shown separately.
|
|
292
|
+
* Issue #1508: When multiple models are used, token and context usage is now split by model.
|
|
293
|
+
* Sub-sessions are shown as a global section (not duplicated per model) since JSONL
|
|
294
|
+
* sub-session tracking is global across all models.
|
|
218
295
|
* @param {Object} tokenUsage - Token usage data from calculateSessionTokens
|
|
219
296
|
* @param {Object|null} streamTokenUsage - Token usage from stream JSON events (used for comparison, not displayed)
|
|
220
297
|
* @returns {string} Formatted markdown string for PR comment
|
|
@@ -229,6 +306,20 @@ export const buildBudgetStatsString = tokenUsage => {
|
|
|
229
306
|
const modelIds = Object.keys(tokenUsage.modelUsage);
|
|
230
307
|
const isMultiModel = modelIds.length > 1;
|
|
231
308
|
|
|
309
|
+
// Issue #1508: For multi-model sessions, show sub-sessions once (globally), not per-model
|
|
310
|
+
// Sub-sessions track compactification boundaries which are session-wide, not model-specific
|
|
311
|
+
const subSessions = tokenUsage.subSessions || [];
|
|
312
|
+
const hasMultipleSubSessions = subSessions.length > 1;
|
|
313
|
+
|
|
314
|
+
if (isMultiModel && hasMultipleSubSessions) {
|
|
315
|
+
// Issue #1508: For multi-model sessions, show global sub-sessions once (not per-model),
|
|
316
|
+
// since sub-sessions track compactification boundaries which are session-wide.
|
|
317
|
+
// Per-model context/output limits are shown below under each model heading.
|
|
318
|
+
const primaryModelId = modelIds[0];
|
|
319
|
+
const primaryUsage = tokenUsage.modelUsage[primaryModelId];
|
|
320
|
+
stats += formatSubSessionsList(subSessions, primaryUsage.modelInfo?.limit?.context, primaryUsage.modelInfo?.limit?.output);
|
|
321
|
+
}
|
|
322
|
+
|
|
232
323
|
for (const modelId of modelIds) {
|
|
233
324
|
const usage = tokenUsage.modelUsage[modelId];
|
|
234
325
|
const modelName = usage.modelName || modelId;
|
|
@@ -237,34 +328,29 @@ export const buildBudgetStatsString = tokenUsage => {
|
|
|
237
328
|
|
|
238
329
|
if (isMultiModel) stats += `\n\n**${modelName}:**`;
|
|
239
330
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const subTotalInput = sub.inputTokens + sub.cacheCreationTokens + sub.cacheReadTokens;
|
|
251
|
-
let line = `\n${i + 1}. `;
|
|
252
|
-
if (contextLimit && subPeakContext > 0) {
|
|
253
|
-
const pct = ((subPeakContext / contextLimit) * 100).toFixed(0);
|
|
254
|
-
line += `${formatTokensCompact(subPeakContext)} / ${formatTokensCompact(contextLimit)} input tokens (${pct}%)`;
|
|
255
|
-
} else {
|
|
256
|
-
line += `${formatTokensCompact(subTotalInput)} input tokens`;
|
|
257
|
-
}
|
|
258
|
-
if (outputLimit) {
|
|
259
|
-
const outPct = ((sub.outputTokens / outputLimit) * 100).toFixed(0);
|
|
260
|
-
line += `; ${formatTokensCompact(sub.outputTokens)} / ${formatTokensCompact(outputLimit)} output tokens (${outPct}%)`;
|
|
331
|
+
if (!isMultiModel && hasMultipleSubSessions) {
|
|
332
|
+
// Single-model + multiple sub-sessions: show sub-sessions under that model
|
|
333
|
+
stats += formatSubSessionsList(subSessions, contextLimit, outputLimit);
|
|
334
|
+
} else if (!isMultiModel && !hasMultipleSubSessions) {
|
|
335
|
+
// Single-model + single sub-session: simplified format with context/output limits
|
|
336
|
+
const peakContext = usage.peakContextUsage || 0;
|
|
337
|
+
if (contextLimit) {
|
|
338
|
+
if (peakContext > 0) {
|
|
339
|
+
const pct = ((peakContext / contextLimit) * 100).toFixed(0);
|
|
340
|
+
stats += `\n- Max context window: ${formatTokensCompact(peakContext)} / ${formatTokensCompact(contextLimit)} input tokens (${pct}%)`;
|
|
261
341
|
} else {
|
|
262
|
-
|
|
342
|
+
const totalInput = usage.inputTokens + usage.cacheCreationTokens + usage.cacheReadTokens;
|
|
343
|
+
const pct = ((totalInput / contextLimit) * 100).toFixed(0);
|
|
344
|
+
stats += `\n- Context window: ${formatTokensCompact(totalInput)} / ${formatTokensCompact(contextLimit)} tokens (${pct}%)`;
|
|
263
345
|
}
|
|
264
|
-
|
|
346
|
+
}
|
|
347
|
+
if (outputLimit) {
|
|
348
|
+
const outPct = ((usage.outputTokens / outputLimit) * 100).toFixed(0);
|
|
349
|
+
stats += `\n- Max output tokens: ${formatTokensCompact(usage.outputTokens)} / ${formatTokensCompact(outputLimit)} output tokens (${outPct}%)`;
|
|
265
350
|
}
|
|
266
351
|
} else {
|
|
267
|
-
//
|
|
352
|
+
// Multi-model (single or multiple sub-sessions): show per-model context/output limits
|
|
353
|
+
// Issue #1508: Context window and max output tokens should be split by model
|
|
268
354
|
const peakContext = usage.peakContextUsage || 0;
|
|
269
355
|
if (contextLimit) {
|
|
270
356
|
if (peakContext > 0) {
|
|
@@ -282,12 +368,17 @@ export const buildBudgetStatsString = tokenUsage => {
|
|
|
282
368
|
}
|
|
283
369
|
}
|
|
284
370
|
|
|
285
|
-
// Cumulative totals: input tokens + cached shown separately
|
|
371
|
+
// Cumulative totals per model: input tokens + cached shown separately
|
|
286
372
|
const totalInputNonCached = usage.inputTokens + usage.cacheCreationTokens;
|
|
287
373
|
const cachedTokens = usage.cacheReadTokens;
|
|
288
374
|
stats += `\n\nTotal input tokens: ${formatTokensCompact(totalInputNonCached)}`;
|
|
289
375
|
if (cachedTokens > 0) stats += ` + ${formatTokensCompact(cachedTokens)} cached`;
|
|
290
376
|
stats += `\nTotal output tokens: ${formatTokensCompact(usage.outputTokens)} output`;
|
|
377
|
+
|
|
378
|
+
// Issue #1508: Show per-model cost when available
|
|
379
|
+
if (usage.costUSD !== null && usage.costUSD !== undefined) {
|
|
380
|
+
stats += `\nCost: $${usage.costUSD.toFixed(6)}`;
|
|
381
|
+
}
|
|
291
382
|
}
|
|
292
383
|
}
|
|
293
384
|
|
package/src/claude.lib.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToToke
|
|
|
12
12
|
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
13
13
|
import { createInteractiveHandler } from './interactive-mode.lib.mjs';
|
|
14
14
|
import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
|
|
15
|
-
import { displayBudgetStats, createEmptySubSessionUsage, accumulateModelUsage, displayModelUsage, displayCostComparison } from './claude.budget-stats.lib.mjs';
|
|
15
|
+
import { displayBudgetStats, createEmptySubSessionUsage, accumulateModelUsage, displayModelUsage, displayCostComparison, mergeResultModelUsage } from './claude.budget-stats.lib.mjs';
|
|
16
16
|
import { buildClaudeResumeCommand } from './claude.command-builder.lib.mjs';
|
|
17
17
|
import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
|
|
18
18
|
import { CLAUDE_MODELS as availableModels } from './models/index.mjs'; // Issue #1221
|
|
@@ -480,7 +480,7 @@ export const calculateModelCost = (usage, modelInfo, includeBreakdown = false) =
|
|
|
480
480
|
}
|
|
481
481
|
return totalCost;
|
|
482
482
|
};
|
|
483
|
-
export const calculateSessionTokens = async (sessionId, tempDir) => {
|
|
483
|
+
export const calculateSessionTokens = async (sessionId, tempDir, resultModelUsage = null) => {
|
|
484
484
|
const os = (await use('os')).default;
|
|
485
485
|
const homeDir = os.homedir();
|
|
486
486
|
// Construct the path to the session JSONL file
|
|
@@ -576,6 +576,7 @@ export const calculateSessionTokens = async (sessionId, tempDir) => {
|
|
|
576
576
|
if (currentSubSession.messageCount > 0) {
|
|
577
577
|
subSessions.push(currentSubSession);
|
|
578
578
|
}
|
|
579
|
+
mergeResultModelUsage(modelUsage, resultModelUsage);
|
|
579
580
|
// If no usage data was found, return null
|
|
580
581
|
if (Object.keys(modelUsage).length === 0) {
|
|
581
582
|
return null;
|
|
@@ -605,7 +606,7 @@ export const calculateSessionTokens = async (sessionId, tempDir) => {
|
|
|
605
606
|
usage.modelName = modelInfo.name || modelId;
|
|
606
607
|
usage.modelInfo = modelInfo; // Store complete model info
|
|
607
608
|
} else {
|
|
608
|
-
usage.costUSD = null;
|
|
609
|
+
usage.costUSD = usage._resultCostUSD ?? null;
|
|
609
610
|
usage.costBreakdown = null;
|
|
610
611
|
usage.modelName = modelId;
|
|
611
612
|
usage.modelInfo = null;
|
|
@@ -1307,7 +1308,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1307
1308
|
// Calculate and display total token usage from session JSONL file
|
|
1308
1309
|
if (sessionId && tempDir) {
|
|
1309
1310
|
try {
|
|
1310
|
-
const tokenUsage = await calculateSessionTokens(sessionId, tempDir);
|
|
1311
|
+
const tokenUsage = await calculateSessionTokens(sessionId, tempDir, resultModelUsage);
|
|
1311
1312
|
if (tokenUsage) {
|
|
1312
1313
|
// Issue #1501: Log deduplication stats in verbose mode
|
|
1313
1314
|
if (tokenUsage.duplicateEntriesSkipped > 0) {
|
|
@@ -1320,9 +1321,14 @@ export const executeClaudeCommand = async params => {
|
|
|
1320
1321
|
// Display per-model breakdown
|
|
1321
1322
|
if (tokenUsage.modelUsage) {
|
|
1322
1323
|
const modelIds = Object.keys(tokenUsage.modelUsage);
|
|
1324
|
+
const modelsFromResult = modelIds.filter(id => tokenUsage.modelUsage[id]._sourceResultJson);
|
|
1325
|
+
if (modelsFromResult.length > 0) {
|
|
1326
|
+
await log(`📊 Token data supplemented from result JSON for: ${modelsFromResult.join(', ')}`, { verbose: true });
|
|
1327
|
+
}
|
|
1323
1328
|
for (const modelId of modelIds) {
|
|
1324
1329
|
const usage = tokenUsage.modelUsage[modelId];
|
|
1325
|
-
|
|
1330
|
+
const sourceNote = usage._sourceResultJson ? ' (from result JSON)' : '';
|
|
1331
|
+
await log(`\n 📊 ${usage.modelName || modelId}:${sourceNote}`);
|
|
1326
1332
|
await displayModelUsage(usage, log);
|
|
1327
1333
|
// Display budget stats if flag is enabled
|
|
1328
1334
|
if (argv.tokensBudgetStats && usage.modelInfo?.limit) {
|
package/src/github.lib.mjs
CHANGED
|
@@ -399,7 +399,7 @@ export async function attachLogToGitHub(options) {
|
|
|
399
399
|
if (totalCostUSD === null && sessionId && tempDir && !errorMessage) {
|
|
400
400
|
try {
|
|
401
401
|
const { calculateSessionTokens } = await import('./claude.lib.mjs');
|
|
402
|
-
const tokenUsage = await calculateSessionTokens(sessionId, tempDir);
|
|
402
|
+
const tokenUsage = await calculateSessionTokens(sessionId, tempDir, resultModelUsage);
|
|
403
403
|
if (tokenUsage) {
|
|
404
404
|
if (tokenUsage.totalCostUSD !== null && tokenUsage.totalCostUSD !== undefined) {
|
|
405
405
|
totalCostUSD = tokenUsage.totalCostUSD;
|
|
@@ -1120,6 +1120,20 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1120
1120
|
latestAnthropicCost = toolResult.anthropicTotalCostUSD;
|
|
1121
1121
|
}
|
|
1122
1122
|
|
|
1123
|
+
// Issue #1508: Compute budget stats for auto-restart-until-mergeable log comment
|
|
1124
|
+
let autoMergeBudgetStatsData = null;
|
|
1125
|
+
if (argv.tokensBudgetStats && latestSessionId && tempDir) {
|
|
1126
|
+
try {
|
|
1127
|
+
const { calculateSessionTokens } = await import('./claude.lib.mjs');
|
|
1128
|
+
const tokenUsage = await calculateSessionTokens(latestSessionId, tempDir, toolResult.resultModelUsage);
|
|
1129
|
+
if (tokenUsage) {
|
|
1130
|
+
autoMergeBudgetStatsData = { tokenUsage, streamTokenUsage: toolResult.streamTokenUsage || null };
|
|
1131
|
+
}
|
|
1132
|
+
} catch (budgetError) {
|
|
1133
|
+
if (argv.verbose) await log(` ⚠️ Could not calculate budget stats: ${budgetError.message}`, { verbose: true });
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1123
1137
|
// Attach log if enabled
|
|
1124
1138
|
const shouldAttachLogs = argv.attachLogs || argv['attach-logs'];
|
|
1125
1139
|
if (prNumber && shouldAttachLogs) {
|
|
@@ -1149,6 +1163,9 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1149
1163
|
// Issue #1225: Pass model and tool info for PR comments
|
|
1150
1164
|
requestedModel: argv.model,
|
|
1151
1165
|
tool: argv.tool || 'claude',
|
|
1166
|
+
// Issue #1508: Include budget stats (context/token/cost) for auto-restart log
|
|
1167
|
+
resultModelUsage: toolResult.resultModelUsage || null,
|
|
1168
|
+
budgetStatsData: autoMergeBudgetStatsData,
|
|
1152
1169
|
});
|
|
1153
1170
|
await log(formatAligned('', '✅ Session log uploaded to PR', '', 2));
|
|
1154
1171
|
}
|
|
@@ -502,7 +502,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
502
502
|
if (argv.tokensBudgetStats && sessionId && tempDir) {
|
|
503
503
|
try {
|
|
504
504
|
const { calculateSessionTokens } = await import('./claude.lib.mjs');
|
|
505
|
-
const tokenUsage = await calculateSessionTokens(sessionId, tempDir);
|
|
505
|
+
const tokenUsage = await calculateSessionTokens(sessionId, tempDir, resultModelUsage);
|
|
506
506
|
if (tokenUsage) {
|
|
507
507
|
budgetStatsData = { tokenUsage, streamTokenUsage };
|
|
508
508
|
}
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -300,6 +300,8 @@ export const watchForFeedback = async params => {
|
|
|
300
300
|
// Issue #1225: Pass model and tool info for PR comments
|
|
301
301
|
requestedModel: argv.model,
|
|
302
302
|
tool: argv.tool || 'claude',
|
|
303
|
+
// Issue #1508: Pass model usage for failure log (cost info per model)
|
|
304
|
+
resultModelUsage: toolResult.resultModelUsage || null,
|
|
303
305
|
});
|
|
304
306
|
|
|
305
307
|
if (logUploadSuccess) {
|
|
@@ -338,6 +340,20 @@ export const watchForFeedback = async params => {
|
|
|
338
340
|
}
|
|
339
341
|
}
|
|
340
342
|
|
|
343
|
+
// Issue #1508: Compute budget stats for auto-restart log comment
|
|
344
|
+
let autoRestartBudgetStatsData = null;
|
|
345
|
+
if (argv.tokensBudgetStats && latestSessionId && tempDir) {
|
|
346
|
+
try {
|
|
347
|
+
const { calculateSessionTokens } = await import('./claude.lib.mjs');
|
|
348
|
+
const tokenUsage = await calculateSessionTokens(latestSessionId, tempDir, toolResult.resultModelUsage);
|
|
349
|
+
if (tokenUsage) {
|
|
350
|
+
autoRestartBudgetStatsData = { tokenUsage, streamTokenUsage: toolResult.streamTokenUsage || null };
|
|
351
|
+
}
|
|
352
|
+
} catch (budgetError) {
|
|
353
|
+
if (argv.verbose) await log(` ⚠️ Could not calculate budget stats: ${budgetError.message}`, { verbose: true });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
341
357
|
// Issue #1107: Attach log after each auto-restart session with its own cost estimation
|
|
342
358
|
// This ensures each restart has its own log comment instead of one combined log at the end
|
|
343
359
|
const shouldAttachLogs = argv.attachLogs || argv['attach-logs'];
|
|
@@ -369,6 +385,9 @@ export const watchForFeedback = async params => {
|
|
|
369
385
|
// Issue #1225: Pass model and tool info for PR comments
|
|
370
386
|
requestedModel: argv.model,
|
|
371
387
|
tool: argv.tool || 'claude',
|
|
388
|
+
// Issue #1508: Include budget stats (context/token/cost) for auto-restart log
|
|
389
|
+
resultModelUsage: toolResult.resultModelUsage || null,
|
|
390
|
+
budgetStatsData: autoRestartBudgetStatsData,
|
|
372
391
|
});
|
|
373
392
|
|
|
374
393
|
if (logUploadSuccess) {
|