@link-assistant/hive-mind 1.47.0 → 1.47.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.47.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 3bbd66e: Improve Context and tokens usage output format: move percentage before unit label, parenthesize cached tokens in Total line, use consistent X / Y (Z%) format for output tokens when limit is known, and show sub-sessions under model heading instead of globally
8
+
3
9
  ## 1.47.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.47.0",
3
+ "version": "1.47.1",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -165,16 +165,15 @@ export const displayBudgetStats = async (usage, tokenUsage, log) => {
165
165
  const sub = subSessions[i];
166
166
  const subPeak = sub.peakContextUsage || 0;
167
167
  // Issue #1539: Only use peak per-request context for context window display.
168
- // Cumulative totals across all requests can exceed the context limit and produce
169
- // impossible percentages (e.g. 250%). When peak is unknown, skip context display.
168
+ // Issue #1547: Percentage before unit label: X / Y (Z%) input tokens
170
169
  const parts = [];
171
170
  if (contextLimit && subPeak > 0) {
172
171
  const pct = ((subPeak / contextLimit) * 100).toFixed(0);
173
- parts.push(`${formatNumber(subPeak)} / ${formatNumber(contextLimit)} input tokens (${pct}%)`);
172
+ parts.push(`${formatNumber(subPeak)} / ${formatNumber(contextLimit)} (${pct}%) input tokens`);
174
173
  }
175
174
  if (outputLimit) {
176
175
  const outPct = ((sub.outputTokens / outputLimit) * 100).toFixed(0);
177
- parts.push(`${formatNumber(sub.outputTokens)} / ${formatNumber(outputLimit)} output tokens (${outPct}%)`);
176
+ parts.push(`${formatNumber(sub.outputTokens)} / ${formatNumber(outputLimit)} (${outPct}%) output tokens`);
178
177
  }
179
178
  if (parts.length > 0) {
180
179
  await log(` ${i + 1}. Context window: ${parts.join(', ')}`);
@@ -182,14 +181,15 @@ export const displayBudgetStats = async (usage, tokenUsage, log) => {
182
181
  }
183
182
  } else if (peakContext > 0) {
184
183
  // Single sub-session with known peak: single-line format
184
+ // Issue #1547: Percentage before unit label
185
185
  const parts = [];
186
186
  if (contextLimit) {
187
187
  const pct = ((peakContext / contextLimit) * 100).toFixed(0);
188
- parts.push(`${formatNumber(peakContext)} / ${formatNumber(contextLimit)} input tokens (${pct}%)`);
188
+ parts.push(`${formatNumber(peakContext)} / ${formatNumber(contextLimit)} (${pct}%) input tokens`);
189
189
  }
190
190
  if (outputLimit) {
191
191
  const outPct = ((usage.outputTokens / outputLimit) * 100).toFixed(0);
192
- parts.push(`${formatNumber(usage.outputTokens)} / ${formatNumber(outputLimit)} output tokens (${outPct}%)`);
192
+ parts.push(`${formatNumber(usage.outputTokens)} / ${formatNumber(outputLimit)} (${outPct}%) output tokens`);
193
193
  }
194
194
  if (parts.length > 0) {
195
195
  await log(` Context window: ${parts.join(', ')}`);
@@ -199,15 +199,20 @@ export const displayBudgetStats = async (usage, tokenUsage, log) => {
199
199
  // Cumulative totals are shown on the Total line below — no duplication needed.
200
200
 
201
201
  // Cumulative totals — single line
202
+ // Issue #1547: Parenthesized cached format and consistent output format
202
203
  const totalInputNonCached = usage.inputTokens + usage.cacheCreationTokens;
203
204
  const cachedTokens = usage.cacheReadTokens;
204
- let totalLine = `${formatNumber(totalInputNonCached)}`;
205
- if (cachedTokens > 0) totalLine += ` + ${formatNumber(cachedTokens)} cached`;
206
- totalLine += ` input tokens, ${formatNumber(usage.outputTokens)} output tokens`;
207
- // Issue #1539: When peakContextUsage is unknown, embed output percentage in Total line
205
+ let totalLine;
206
+ if (cachedTokens > 0) {
207
+ totalLine = `(${formatNumber(totalInputNonCached)} + ${formatNumber(cachedTokens)} cached) input tokens`;
208
+ } else {
209
+ totalLine = `${formatNumber(totalInputNonCached)} input tokens`;
210
+ }
208
211
  if (peakContext === 0 && outputLimit) {
209
212
  const outPct = ((usage.outputTokens / outputLimit) * 100).toFixed(0);
210
- totalLine += ` (${outPct}% of ${formatNumber(outputLimit)} output limit)`;
213
+ totalLine += `, ${formatNumber(usage.outputTokens)} / ${formatNumber(outputLimit)} (${outPct}%) output tokens`;
214
+ } else {
215
+ totalLine += `, ${formatNumber(usage.outputTokens)} output tokens`;
211
216
  }
212
217
  await log(` Total: ${totalLine}`);
213
218
  };
@@ -325,12 +330,12 @@ const formatContextOutputLine = (peakContext, contextLimit, outputTokens, output
325
330
  // context window metrics and produce impossible percentages (e.g. 250%).
326
331
  if (peakContext > 0) {
327
332
  const pct = ((peakContext / contextLimit) * 100).toFixed(0);
328
- parts.push(`${formatTokensCompact(peakContext)} / ${formatTokensCompact(contextLimit)} input tokens (${pct}%)`);
333
+ parts.push(`${formatTokensCompact(peakContext)} / ${formatTokensCompact(contextLimit)} (${pct}%) input tokens`);
329
334
  }
330
335
  }
331
336
  if (outputLimit) {
332
337
  const outPct = ((outputTokens / outputLimit) * 100).toFixed(0);
333
- parts.push(`${formatTokensCompact(outputTokens)} / ${formatTokensCompact(outputLimit)} output tokens (${outPct}%)`);
338
+ parts.push(`${formatTokensCompact(outputTokens)} / ${formatTokensCompact(outputLimit)} (${outPct}%) output tokens`);
334
339
  }
335
340
  if (parts.length === 0) return '';
336
341
  return `\n${prefix}Context window: ${parts.join(', ')}`;
@@ -364,15 +369,6 @@ export const buildBudgetStatsString = tokenUsage => {
364
369
  const subSessions = tokenUsage.subSessions || [];
365
370
  const hasMultipleSubSessions = subSessions.length > 1;
366
371
 
367
- if (isMultiModel && hasMultipleSubSessions) {
368
- // Issue #1508: For multi-model sessions, show global sub-sessions once (not per-model),
369
- // since sub-sessions track compactification boundaries which are session-wide.
370
- // Per-model context/output limits are shown below under each model heading.
371
- const primaryModelId = modelIds[0];
372
- const primaryUsage = tokenUsage.modelUsage[primaryModelId];
373
- stats += formatSubSessionsList(subSessions, primaryUsage.modelInfo?.limit?.context, primaryUsage.modelInfo?.limit?.output);
374
- }
375
-
376
372
  for (const modelId of modelIds) {
377
373
  const usage = tokenUsage.modelUsage[modelId];
378
374
  const modelName = usage.modelName || modelId;
@@ -383,8 +379,9 @@ export const buildBudgetStatsString = tokenUsage => {
383
379
 
384
380
  const peakContext = usage.peakContextUsage || 0;
385
381
 
386
- if (!isMultiModel && hasMultipleSubSessions) {
387
- // Single-model + multiple sub-sessions: show numbered sub-sessions under that model
382
+ if (hasMultipleSubSessions && (!isMultiModel || modelId === modelIds[0])) {
383
+ // Issue #1547: Show sub-sessions under the primary model heading (not globally).
384
+ // For single-model sessions, show under that model. For multi-model, under the first model.
388
385
  stats += formatSubSessionsList(subSessions, contextLimit, outputLimit);
389
386
  } else if (peakContext > 0) {
390
387
  // Issue #1526: Single line format for context window + output tokens
@@ -394,18 +391,22 @@ export const buildBudgetStatsString = tokenUsage => {
394
391
  // Cumulative totals are shown on the Total line below — no duplication needed.
395
392
 
396
393
  // Cumulative totals per model: input tokens + cached shown separately
397
- // Issue #1526: Shorter format single "Total:" line
394
+ // Issue #1547: Parenthesized cached format: (X + Y cached) input tokens
398
395
  const totalInputNonCached = usage.inputTokens + usage.cacheCreationTokens;
399
396
  const cachedTokens = usage.cacheReadTokens;
400
- let totalLine = `${formatTokensCompact(totalInputNonCached)}`;
401
- if (cachedTokens > 0) totalLine += ` + ${formatTokensCompact(cachedTokens)} cached`;
402
- totalLine += ` input tokens, ${formatTokensCompact(usage.outputTokens)} output tokens`;
397
+ let totalLine;
398
+ if (cachedTokens > 0) {
399
+ totalLine = `(${formatTokensCompact(totalInputNonCached)} + ${formatTokensCompact(cachedTokens)} cached) input tokens`;
400
+ } else {
401
+ totalLine = `${formatTokensCompact(totalInputNonCached)} input tokens`;
402
+ }
403
403
 
404
- // Issue #1539: When peakContextUsage is unknown (no per-request data), embed
405
- // output token percentage in the Total line so no data is lost.
404
+ // Issue #1547: Consistent output format use X / Y (Z%) output tokens when limit known
406
405
  if (peakContext === 0 && outputLimit) {
407
406
  const outPct = ((usage.outputTokens / outputLimit) * 100).toFixed(0);
408
- totalLine += ` (${outPct}% of ${formatTokensCompact(outputLimit)} output limit)`;
407
+ totalLine += `, ${formatTokensCompact(usage.outputTokens)} / ${formatTokensCompact(outputLimit)} (${outPct}%) output tokens`;
408
+ } else {
409
+ totalLine += `, ${formatTokensCompact(usage.outputTokens)} output tokens`;
409
410
  }
410
411
 
411
412
  // Issue #1508: Show per-model cost when available