taskchef 8.2.1 → 8.2.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/.codex-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/src/dashboard/dist/app.js +1 -1
- package/src/dashboard/dist/app.js.map +1 -1
- package/src/dashboard/dist/styles.css +1 -1
- package/src/dashboard/react/components/ActivityTimeline.tsx +4 -3
- package/src/dashboard/react/components/RelativeTime.tsx +3 -1
- package/src/dashboard/react/components/TaskCardStats.test.tsx +2 -2
- package/src/dashboard/react/components/TaskCardStats.tsx +4 -3
- package/src/dashboard/react/components/UsagePanel.tsx +14 -23
- package/src/dashboard/react/presentation.test.tsx +40 -9
- package/src/dashboard/react/styles.css +1 -1
|
@@ -59,6 +59,7 @@ export function ActivityTimeline({ highlightTurnRef, task }: { highlightTurnRef:
|
|
|
59
59
|
<RelativeTime
|
|
60
60
|
icon={<IconHistory aria-hidden size={14} />}
|
|
61
61
|
label="Turn update time"
|
|
62
|
+
tooltipLabel="Updated at"
|
|
62
63
|
value={presentation.updatedAt}
|
|
63
64
|
/>
|
|
64
65
|
</StatCell>
|
|
@@ -70,7 +71,7 @@ export function ActivityTimeline({ highlightTurnRef, task }: { highlightTurnRef:
|
|
|
70
71
|
accessibleLabel={usage.tokens.accessibleLabel}
|
|
71
72
|
animated={usage.animated}
|
|
72
73
|
icon={<IconStack2 aria-hidden size={14} />}
|
|
73
|
-
title=
|
|
74
|
+
title="Tokens"
|
|
74
75
|
value={usage.tokens.value}
|
|
75
76
|
/>
|
|
76
77
|
</StatCell>
|
|
@@ -79,7 +80,7 @@ export function ActivityTimeline({ highlightTurnRef, task }: { highlightTurnRef:
|
|
|
79
80
|
accessibleLabel={usage.cost.accessibleLabel}
|
|
80
81
|
animated={usage.animated}
|
|
81
82
|
icon={<IconCurrencyDollar aria-hidden size={14} />}
|
|
82
|
-
title=
|
|
83
|
+
title="Estimated cost"
|
|
83
84
|
value={usage.cost.value}
|
|
84
85
|
/>
|
|
85
86
|
</StatCell>
|
|
@@ -100,7 +101,7 @@ function TurnReportedWork({ turn }: { turn: TaskTurn }) {
|
|
|
100
101
|
<StaticStat
|
|
101
102
|
accessibleLabel={elapsed.accessibleLabel}
|
|
102
103
|
icon={<IconHourglass aria-hidden size={14} />}
|
|
103
|
-
title=
|
|
104
|
+
title="Duration"
|
|
104
105
|
value={elapsed.value}
|
|
105
106
|
/>
|
|
106
107
|
);
|
|
@@ -12,10 +12,12 @@ export function RelativeTimeProvider({ children, now }: { children: ReactNode; n
|
|
|
12
12
|
export function RelativeTime({
|
|
13
13
|
icon,
|
|
14
14
|
label,
|
|
15
|
+
tooltipLabel,
|
|
15
16
|
value,
|
|
16
17
|
}: {
|
|
17
18
|
icon?: ReactNode;
|
|
18
19
|
label: string;
|
|
20
|
+
tooltipLabel?: string;
|
|
19
21
|
value: string | null | undefined;
|
|
20
22
|
}) {
|
|
21
23
|
const [exact, setExact] = useState(false);
|
|
@@ -29,7 +31,7 @@ export function RelativeTime({
|
|
|
29
31
|
? "Show relative time"
|
|
30
32
|
: `Exact time: ${exactText}. Show exact time`;
|
|
31
33
|
return (
|
|
32
|
-
<Tooltip label={tooltip}>
|
|
34
|
+
<Tooltip events={{ focus: true, hover: true, touch: false }} label={tooltipLabel ?? tooltip}>
|
|
33
35
|
<UnstyledButton
|
|
34
36
|
aria-label={unavailable
|
|
35
37
|
? `${label}: unavailable.`
|
|
@@ -48,7 +48,7 @@ describe("task card stats", () => {
|
|
|
48
48
|
expect(screen.getByLabelText(/estimated cost unavailable/i)).toHaveTextContent("n/a");
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test("shows
|
|
51
|
+
test("shows the requested estimated-cost label to keyboard users", () => {
|
|
52
52
|
vi.useFakeTimers();
|
|
53
53
|
renderStats(fixtureTask({
|
|
54
54
|
status: "completed",
|
|
@@ -57,7 +57,7 @@ describe("task card stats", () => {
|
|
|
57
57
|
const cost = screen.getByLabelText(/estimated cost \$12\.34/i);
|
|
58
58
|
fireEvent.focus(cost);
|
|
59
59
|
vi.advanceTimersByTime(150);
|
|
60
|
-
expect(screen.getByRole("tooltip")).toHaveTextContent("
|
|
60
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent("Estimated cost");
|
|
61
61
|
vi.useRealTimers();
|
|
62
62
|
});
|
|
63
63
|
|
|
@@ -19,6 +19,7 @@ export function TaskCardStats({ task }: { task: Task }) {
|
|
|
19
19
|
<RelativeTime
|
|
20
20
|
icon={<IconHistory aria-hidden size={14} />}
|
|
21
21
|
label={`Updated time for ${task.title}`}
|
|
22
|
+
tooltipLabel="Updated at"
|
|
22
23
|
value={task.meaningfulUpdatedAt ?? task.updatedAt}
|
|
23
24
|
/>
|
|
24
25
|
</StatCell>
|
|
@@ -26,7 +27,7 @@ export function TaskCardStats({ task }: { task: Task }) {
|
|
|
26
27
|
<StaticStat
|
|
27
28
|
accessibleLabel={reportedWork.accessibleLabel}
|
|
28
29
|
icon={<IconHourglass aria-hidden size={14} />}
|
|
29
|
-
title=
|
|
30
|
+
title="Duration"
|
|
30
31
|
value={reportedWork.value}
|
|
31
32
|
/>
|
|
32
33
|
</StatCell>
|
|
@@ -35,7 +36,7 @@ export function TaskCardStats({ task }: { task: Task }) {
|
|
|
35
36
|
accessibleLabel={usage.tokens.accessibleLabel}
|
|
36
37
|
animated={usage.tokens.animated}
|
|
37
38
|
icon={<IconStack2 aria-hidden size={14} />}
|
|
38
|
-
title=
|
|
39
|
+
title="Tokens"
|
|
39
40
|
value={usage.tokens.value}
|
|
40
41
|
/>
|
|
41
42
|
<Text className="taskchef-visually-hidden" component="span" role="status">
|
|
@@ -47,7 +48,7 @@ export function TaskCardStats({ task }: { task: Task }) {
|
|
|
47
48
|
accessibleLabel={usage.cost.accessibleLabel}
|
|
48
49
|
animated={usage.cost.animated}
|
|
49
50
|
icon={<IconCurrencyDollar aria-hidden size={14} />}
|
|
50
|
-
title=
|
|
51
|
+
title="Estimated cost"
|
|
51
52
|
value={usage.cost.value}
|
|
52
53
|
/>
|
|
53
54
|
</StatCell>
|
|
@@ -13,12 +13,9 @@ export function UsagePanel({ task }: { task: Task }) {
|
|
|
13
13
|
const usage = usageView(task);
|
|
14
14
|
const reportedWork = taskReportedWorkView(task);
|
|
15
15
|
const ready = usage.kind === "ready" ? usage : null;
|
|
16
|
-
const cached = ready?.usage.cachedInputTokens ?? 0;
|
|
17
|
-
const input = ready?.usage.inputTokens ?? 0;
|
|
18
|
-
const cacheRatio = ready && input + cached > 0
|
|
19
|
-
? `${Math.round((cached / (input + cached)) * 100)}%`
|
|
20
|
-
: "n/a";
|
|
21
16
|
const models = Object.keys(ready?.usage.models ?? {}).join(", ");
|
|
17
|
+
const estimatedCost = ready?.usage.estimatedCostUsd;
|
|
18
|
+
const hasEstimatedCost = typeof estimatedCost === "number" && Number.isFinite(estimatedCost);
|
|
22
19
|
const stateValue = usage.kind === "pending"
|
|
23
20
|
? "Pending"
|
|
24
21
|
: usage.kind === "calculating"
|
|
@@ -36,8 +33,8 @@ export function UsagePanel({ task }: { task: Task }) {
|
|
|
36
33
|
accessibleValue: ready
|
|
37
34
|
? `${formatFullTokens(ready.usage.totalTokens)} tokens`
|
|
38
35
|
: usage.label,
|
|
39
|
-
animated: usage.kind === "pending" || usage.kind === "calculating",
|
|
40
|
-
label:
|
|
36
|
+
animated: usage.kind === "pending" || usage.kind === "calculating" || ready?.knownSoFar,
|
|
37
|
+
label: "Tokens",
|
|
41
38
|
title: ready ? `${formatFullTokens(ready.usage.totalTokens)} tokens` : usage.label,
|
|
42
39
|
value: ready ? formatCompactTokens(ready.usage.totalTokens) : stateValue,
|
|
43
40
|
},
|
|
@@ -49,7 +46,9 @@ export function UsagePanel({ task }: { task: Task }) {
|
|
|
49
46
|
: `Estimated cost ${accessibleState}${
|
|
50
47
|
usage.kind === "unavailable" ? `: ${usage.label}` : ""
|
|
51
48
|
}`,
|
|
52
|
-
animated: usage.kind === "pending"
|
|
49
|
+
animated: usage.kind === "pending"
|
|
50
|
+
|| usage.kind === "calculating"
|
|
51
|
+
|| (ready?.knownSoFar && hasEstimatedCost),
|
|
53
52
|
label: "Estimated cost",
|
|
54
53
|
title: ready?.usage.estimatedCostUsd == null
|
|
55
54
|
? "Estimated cost unavailable"
|
|
@@ -60,24 +59,16 @@ export function UsagePanel({ task }: { task: Task }) {
|
|
|
60
59
|
},
|
|
61
60
|
{
|
|
62
61
|
accessibleValue: reportedWork.accessibleLabel,
|
|
63
|
-
label: "
|
|
62
|
+
label: "Duration",
|
|
64
63
|
title: reportedWork.title,
|
|
65
64
|
value: reportedWork.value,
|
|
66
65
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
accessibleValue: cacheRatio === "n/a" ? "Cache ratio unavailable" : `Cache ratio ${cacheRatio}`,
|
|
76
|
-
label: "Cache ratio",
|
|
77
|
-
title: cacheRatio === "n/a" ? "Cache ratio unavailable because no input-token total is available." : undefined,
|
|
78
|
-
value: cacheRatio,
|
|
79
|
-
},
|
|
80
|
-
] : []),
|
|
66
|
+
{
|
|
67
|
+
accessibleValue: models || "Models unavailable",
|
|
68
|
+
label: "Models",
|
|
69
|
+
title: models || "Models unavailable",
|
|
70
|
+
value: models || "n/a",
|
|
71
|
+
},
|
|
81
72
|
];
|
|
82
73
|
return (
|
|
83
74
|
<Box aria-live="polite">
|
|
@@ -154,10 +154,11 @@ describe("token and working presentation", () => {
|
|
|
154
154
|
|
|
155
155
|
expect(screen.getByText("Tokens").nextSibling).toHaveTextContent("330");
|
|
156
156
|
expect(screen.getByText("Estimated cost").nextSibling).toHaveTextContent("$0.12");
|
|
157
|
-
expect(screen.getByText("
|
|
157
|
+
expect(screen.getByText("Duration").nextSibling).toHaveTextContent("18m 32s");
|
|
158
158
|
expect(screen.getByLabelText(/reported work 18m 32s.*wall-clock/i)).toBeVisible();
|
|
159
|
-
expect(screen.getByText("
|
|
160
|
-
expect(screen.
|
|
159
|
+
expect(screen.getByText("Models").nextSibling).toHaveTextContent("gpt-5.6-sol, gpt-5.6-luna");
|
|
160
|
+
expect(screen.queryByText("Cache ratio")).not.toBeInTheDocument();
|
|
161
|
+
expect(screen.getByText("Tokens").closest(".taskchef-metrics")?.children).toHaveLength(4);
|
|
161
162
|
expect(screen.queryByText(/API-equivalent/i)).not.toBeInTheDocument();
|
|
162
163
|
});
|
|
163
164
|
|
|
@@ -176,10 +177,38 @@ describe("token and working presentation", () => {
|
|
|
176
177
|
|
|
177
178
|
expect(screen.getByText("Estimated cost").nextSibling).toHaveTextContent("n/a");
|
|
178
179
|
expect(screen.getByLabelText("Estimated cost unavailable")).toBeVisible();
|
|
179
|
-
expect(screen.getByText("
|
|
180
|
-
expect(screen.getByLabelText("
|
|
181
|
-
expect(screen.
|
|
182
|
-
|
|
180
|
+
expect(screen.getByText("Models").nextSibling).toHaveTextContent("n/a");
|
|
181
|
+
expect(screen.getByLabelText("Models unavailable")).toBeVisible();
|
|
182
|
+
expect(screen.queryByText("Cache ratio")).not.toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("shimmers known usage values during a running turn without changing card titles", () => {
|
|
186
|
+
const { container, rerender } = render(
|
|
187
|
+
<MantineProvider>
|
|
188
|
+
<UsagePanel task={fixtureTask({
|
|
189
|
+
usage: {
|
|
190
|
+
status: "available",
|
|
191
|
+
task: { totalTokens: 330, estimatedCostUsd: 0.12 },
|
|
192
|
+
},
|
|
193
|
+
})} />
|
|
194
|
+
</MantineProvider>,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(screen.getByText("Tokens")).toBeVisible();
|
|
198
|
+
expect(screen.queryByText(/known so far/i)).not.toBeInTheDocument();
|
|
199
|
+
expect(container.querySelectorAll(".taskchef-shimmer")).toHaveLength(2);
|
|
200
|
+
|
|
201
|
+
rerender(
|
|
202
|
+
<MantineProvider>
|
|
203
|
+
<UsagePanel task={fixtureTask({
|
|
204
|
+
usage: {
|
|
205
|
+
status: "available",
|
|
206
|
+
task: { totalTokens: 330, estimatedCostUsd: null },
|
|
207
|
+
},
|
|
208
|
+
})} />
|
|
209
|
+
</MantineProvider>,
|
|
210
|
+
);
|
|
211
|
+
expect(container.querySelectorAll(".taskchef-shimmer")).toHaveLength(1);
|
|
183
212
|
});
|
|
184
213
|
|
|
185
214
|
test("keeps reported work visible across pending, calculating, and unavailable usage", () => {
|
|
@@ -200,7 +229,9 @@ describe("token and working presentation", () => {
|
|
|
200
229
|
expect(screen.getByText("Tokens").nextSibling).toHaveTextContent(expected);
|
|
201
230
|
expect(screen.getByText("Estimated cost").nextSibling).toHaveTextContent(expected);
|
|
202
231
|
expect(screen.getByLabelText(accessibleCost)).toBeVisible();
|
|
203
|
-
expect(screen.getByText("
|
|
232
|
+
expect(screen.getByText("Duration").nextSibling).toHaveTextContent("n/a");
|
|
233
|
+
expect(screen.getByText("Models").nextSibling).toHaveTextContent("n/a");
|
|
234
|
+
expect(screen.getByText("Tokens").closest(".taskchef-metrics")?.children).toHaveLength(4);
|
|
204
235
|
cleanup();
|
|
205
236
|
}
|
|
206
237
|
});
|
|
@@ -458,7 +489,7 @@ describe("reported wall-clock work presentation", () => {
|
|
|
458
489
|
reportedWork: { terminalTurns: 1, validTurns: 1, totalMilliseconds: 1_112_000 },
|
|
459
490
|
})).toMatchObject({ kind: "available", value: "18m 32s" });
|
|
460
491
|
render(<MantineProvider><UsagePanel task={compactTerminal} /></MantineProvider>);
|
|
461
|
-
expect(screen.getByText("
|
|
492
|
+
expect(screen.getByText("Duration").nextSibling).toHaveTextContent("n/a");
|
|
462
493
|
cleanup();
|
|
463
494
|
}
|
|
464
495
|
|
|
@@ -125,7 +125,7 @@ body {
|
|
|
125
125
|
.taskchef-toolbar .mantine-SegmentedControl-root { overflow-x: auto; }
|
|
126
126
|
.taskchef-notifications { right: 16px; bottom: 16px; width: min(330px, calc(100vw - 32px)); }
|
|
127
127
|
.taskchef-metadata { grid-template-columns: 1fr; }
|
|
128
|
-
.taskchef-metrics { grid-template-columns: repeat(
|
|
128
|
+
.taskchef-metrics { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
129
129
|
.taskchef-metadata dt { padding-bottom: 0; border-bottom: 0; }
|
|
130
130
|
}
|
|
131
131
|
|