pi-background-tasks 0.2.0 → 0.3.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.
@@ -5,6 +5,7 @@ import { matchesKey, truncateToWidth, visibleWidth, type Component, type TUI } f
5
5
  import {
6
6
  boundedRead,
7
7
  compactWhitespace,
8
+ formatCompactNumber,
8
9
  formatDuration,
9
10
  taskDisplayName,
10
11
  truncateChars,
@@ -57,18 +58,10 @@ function taskAge(task: BgTask, now = Date.now()): string {
57
58
  return formatDuration((task.endTime ?? now) - task.startTime);
58
59
  }
59
60
 
60
- function formatTokens(count: number): string {
61
- if (count < 1000) return count.toString();
62
- if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
63
- if (count < 1000000) return `${Math.round(count / 1000)}k`;
64
- if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
65
- return `${Math.round(count / 1000000)}M`;
66
- }
67
-
68
61
  function formatContextUsage(task: BgTask): string {
69
62
  const usage = task.contextUsage;
70
63
  if (!usage || !usage.contextWindow) return "—";
71
- const window = formatTokens(usage.contextWindow);
64
+ const window = formatCompactNumber(usage.contextWindow);
72
65
  if (usage.percent === null || usage.tokens === null) return `?/${window}`;
73
66
  return `${usage.percent.toFixed(1)}%/${window}`;
74
67
  }
@@ -76,9 +69,62 @@ function formatContextUsage(task: BgTask): string {
76
69
  function formatContextDetail(task: BgTask): string {
77
70
  const usage = task.contextUsage;
78
71
  if (!usage || !usage.contextWindow) return "not reported by this background task";
79
- const window = formatTokens(usage.contextWindow);
72
+ const window = formatCompactNumber(usage.contextWindow);
80
73
  if (usage.percent === null || usage.tokens === null) return `unknown tokens / ${window} window`;
81
- return `${usage.percent.toFixed(1)}% of ${window} window (${formatTokens(usage.tokens)} tokens)`;
74
+ return `${usage.percent.toFixed(1)}% of ${window} window (${formatCompactNumber(usage.tokens)} tokens)`;
75
+ }
76
+
77
+ function formatTokenUsage(task: BgTask): string {
78
+ const usage = task.tokenUsage;
79
+ if (!usage || usage.totalTokens <= 0) return "";
80
+ return `tok ${formatCompactNumber(usage.totalTokens)}`;
81
+ }
82
+
83
+ function formatTokenDetail(task: BgTask): string {
84
+ const usage = task.tokenUsage;
85
+ if (!usage || usage.totalTokens <= 0) return "not reported by this background task";
86
+ const parts = [
87
+ `input ${formatCompactNumber(usage.input)}`,
88
+ `output ${formatCompactNumber(usage.output)}`,
89
+ `cache read ${formatCompactNumber(usage.cacheRead)}`,
90
+ `cache write ${formatCompactNumber(usage.cacheWrite)}`,
91
+ `total ${formatCompactNumber(usage.totalTokens)}`,
92
+ ];
93
+ return parts.join(" · ");
94
+ }
95
+
96
+ function formatToolUsage(task: BgTask): string {
97
+ const usage = task.toolUsage;
98
+ if (!usage || (usage.total <= 0 && usage.failed <= 0)) return "";
99
+ return usage.failed > 0 ? `tools ${usage.total}/${usage.failed} failed` : `tools ${usage.total}`;
100
+ }
101
+
102
+ function formatToolDetail(task: BgTask): string {
103
+ const usage = task.toolUsage;
104
+ if (!usage || (usage.total <= 0 && usage.failed <= 0)) return "not reported by this background task";
105
+ const byName = Object.entries(usage.byName ?? {})
106
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
107
+ .slice(0, 6)
108
+ .map(([name, count]) => `${name} ${count}`);
109
+ const parts = [`${usage.total} total`];
110
+ if (usage.failed > 0) parts.push(`${usage.failed} failed`);
111
+ parts.push(...byName);
112
+ return parts.join(" · ");
113
+ }
114
+
115
+ function shortModelName(model: string): string {
116
+ const slash = model.lastIndexOf("/");
117
+ return slash >= 0 ? model.slice(slash + 1) : model;
118
+ }
119
+
120
+ function formatModel(task: BgTask): string {
121
+ if (!task.model) return "";
122
+ return `model ${shortModelName(task.model)}`;
123
+ }
124
+
125
+ function formatModelDetail(task: BgTask): string {
126
+ if (!task.model) return "not reported by this background task";
127
+ return task.model;
82
128
  }
83
129
 
84
130
  function contextColor(theme: Theme, task: BgTask, text: string): string {
@@ -465,10 +511,11 @@ export class BackgroundTasksManager implements Component {
465
511
  : " No running background tasks. Press h to show recent history.";
466
512
  body.push(this.theme.fg("dim", message));
467
513
  } else {
468
- const maxNameWidth = Math.max(12, width - 62);
514
+ const maxNameWidth = Math.max(12, width - 78);
469
515
  const visible = tasks.slice(this.listScroll, this.listScroll + LIST_VISIBLE_ROWS);
470
516
  for (let i = 0; i < visible.length; i++) {
471
- const task = visible[i]!;
517
+ const task = visible[i];
518
+ if (!task) continue;
472
519
  const index = this.listScroll + i;
473
520
  const selected = index === this.selectedIndex;
474
521
  const pointer = selected ? "›" : " ";
@@ -487,10 +534,16 @@ export class BackgroundTasksManager implements Component {
487
534
  const size = formatSize(task.bytesWritten);
488
535
  const context = formatContextUsage(task);
489
536
  const contextText = ` ${contextColor(this.theme, task, `ctx ${context}`)}`;
537
+ const model = formatModel(task);
538
+ const modelText = model ? ` ${this.theme.fg("dim", model)}` : "";
539
+ const tokenUsage = formatTokenUsage(task);
540
+ const tokenText = tokenUsage ? ` ${this.theme.fg("dim", tokenUsage)}` : "";
541
+ const toolUsage = formatToolUsage(task);
542
+ const toolText = toolUsage ? ` ${this.theme.fg("dim", toolUsage)}` : "";
490
543
  const activity = this.activityLabel(task);
491
544
  const activityText = activity ? ` ${this.theme.fg("warning", activity)}` : "";
492
545
  const exit = task.exitCode !== undefined && task.status !== "running" ? this.theme.fg("dim", ` exit=${task.exitCode}`) : "";
493
- let row = ` ${pointer} ${unreadMark} ${name} ${this.theme.fg("dim", task.id)} ${this.theme.fg("dim", "·")} ${status}${exit} ${this.theme.fg("dim", `${runtime} ${size}`)}${contextText}${activityText}`;
546
+ let row = ` ${pointer} ${unreadMark} ${name} ${this.theme.fg("dim", task.id)} ${this.theme.fg("dim", "·")} ${status}${exit} ${this.theme.fg("dim", `${runtime} ${size}`)}${contextText}${modelText}${tokenText}${toolText}${activityText}`;
494
547
  if (selected) row = lightBlue(padAnsi(truncateToWidth(row, width - 4), width - 4));
495
548
  body.push(row);
496
549
  }
@@ -528,8 +581,12 @@ export class BackgroundTasksManager implements Component {
528
581
  if (task.description && compactWhitespace(task.description) !== compactWhitespace(name)) {
529
582
  body.push(` ${this.theme.fg("toolTitle", "Description:")} ${truncateToWidth(task.description, width - 16)}`);
530
583
  }
584
+ const modelDetail = formatModelDetail(task);
585
+ body.push(` ${this.theme.fg("toolTitle", "Model:")} ${task.model ? this.theme.fg("accent", modelDetail) : this.theme.fg("dim", modelDetail)}`);
531
586
  const context = formatContextDetail(task);
532
587
  body.push(` ${this.theme.fg("toolTitle", "Context:")} ${contextColor(this.theme, task, context)}`);
588
+ body.push(` ${this.theme.fg("toolTitle", "Tokens:")} ${this.theme.fg("dim", formatTokenDetail(task))}`);
589
+ body.push(` ${this.theme.fg("toolTitle", "Tools:")} ${this.theme.fg("dim", formatToolDetail(task))}`);
533
590
  body.push(` ${this.theme.fg("toolTitle", "Command:")} ${truncateToWidth(task.command, width - 13)}`);
534
591
  if (task.error) body.push(` ${this.theme.fg("error", `Error: ${task.error}`)}`);
535
592
  body.push("", ` ${this.theme.fg("toolTitle", "Output tail:")}`);