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.
- package/PUBLISHING.md +6 -6
- package/README.md +16 -12
- package/TESTING.md +20 -7
- package/TEST_PLAN.md +35 -28
- package/package.json +6 -3
- package/src/core/common.ts +389 -16
- package/src/core/registry.ts +958 -0
- package/src/extension.ts +170 -907
- package/src/ui/background-tasks-manager.ts +71 -14
|
@@ -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 =
|
|
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 =
|
|
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 (${
|
|
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 -
|
|
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:")}`);
|