(null);
@@ -129,6 +169,43 @@ export function ConversationWorkspace({
runtime.summary.state === "streaming" || runtime.progress.phase === "working";
const isWorking =
isSubmitting || runtimeIsWorking || runtime.progress.phase === "recovering";
+ const presentedStatus = status ?? {
+ label: runtimeStatusLabel(isWorking, runtime.summary.state),
+ detail: isSubmitting ? "Sending prompt to Pi…" : runtime.progress.detail,
+ phase: isWorking ? ("working" as const) : runtime.progress.phase,
+ bridge: "unknown",
+ extensions: Object.values(runtime.extensionStatuses),
+ };
+ const visibleStatus = {
+ label: plainStatusText(presentedStatus.label) || "Pi status",
+ detail: plainStatusText(presentedStatus.detail) || "No status details",
+ bridge: plainStatusText(presentedStatus.bridge) || "unknown",
+ extensions: presentedStatus.extensions.map(plainStatusText).filter(Boolean),
+ };
+ const activeTool = runtime.progress.tool
+ ? plainStatusText(runtime.progress.tool)
+ : undefined;
+ const inputTokens = runtime.stats.tokens?.input;
+ const outputTokens = runtime.stats.tokens?.output;
+ const cacheReadTokens = runtime.stats.tokens?.cacheRead;
+ const undividedTokens =
+ inputTokens === undefined && outputTokens === undefined
+ ? runtime.stats.tokens?.total
+ : undefined;
+ const context = runtime.stats.contextUsage;
+ const contextPercent =
+ context?.percent ??
+ (context?.tokens != null && context.contextWindow
+ ? (context.tokens / context.contextWindow) * 100
+ : undefined);
+ let contextDetail: string | undefined;
+ if (context?.tokens != null) {
+ contextDetail = compactCount(context.tokens);
+ if (context.contextWindow !== undefined)
+ contextDetail += ` / ${compactCount(context.contextWindow)}`;
+ if (contextPercent !== undefined)
+ contextDetail += ` · ${Math.round(contextPercent)}%`;
+ }
useEffect(() => {
if (runtime.scroll.atBottom && ref.current)
ref.current.scrollTop = ref.current.scrollHeight;
@@ -269,15 +346,69 @@ export function ConversationWorkspace({
-
-
- {isSubmitting ? "Sending prompt to Pi…" : runtime.progress.detail}
+
+
+ {visibleStatus.label}
+ {visibleStatus.detail}
+
+
+ {inputTokens !== undefined && (
+
+ In
+ {compactCount(inputTokens)}
+
+ )}
+ {outputTokens !== undefined && (
+
+ Out
+ {compactCount(outputTokens)}
+
+ )}
+ {cacheReadTokens !== undefined && (
+
+ Cache read
+ {compactCount(cacheReadTokens)}
+
+ )}
+ {undividedTokens !== undefined && (
+
+ Tokens
+ {compactCount(undividedTokens)}
+
+ )}
+ {contextDetail && (
+
+ Context
+ {contextDetail}
+
+ )}
+ {runtime.summary.queueCount > 0 && (
+
+ Queue
+ {runtime.summary.queueCount}
+
+ )}
+
+ Bridge
+ {visibleStatus.bridge}
+
+ {activeTool && (
+
+ Tool
+ {activeTool}
+
+ )}
+ {visibleStatus.extensions.map((entry, index) => (
+
+ {entry}
+
+ ))}
- {runtime.progress.tool && {runtime.progress.tool}}
{
await user.click(screen.getByText("Tool result · read"));
expect(details).toHaveAttribute("open");
});
+ it("packs session status between model controls and the composer", () => {
+ const runtime = runtimeView(summary("status", "streaming"));
+ runtime.summary.queueCount = 2;
+ runtime.stats = {
+ tokens: { input: 10_000, output: 2_450, cacheRead: 8_000, total: 12_450 },
+ contextUsage: { tokens: 32_000, contextWindow: 200_000, percent: 16 },
+ };
+ const { container } = render(
+ ,
+ );
+ const modelControls = screen.getByRole("region", {
+ name: "Model and thinking controls",
+ });
+ const status = screen.getByRole("status", { name: "Session status" });
+ const composer = container.querySelector(".composer");
+
+ for (const text of [
+ "Working",
+ "Thinking and preparing a response",
+ "Bridge",
+ "ready",
+ "In",
+ "10k",
+ "Out",
+ "2.5k",
+ "Cache read",
+ "8k",
+ "Context",
+ "32k / 200k · 16%",
+ "Queue",
+ "2",
+ "LSP active",
+ ])
+ expect(status).toHaveTextContent(text);
+ expect(status.textContent).not.toMatch(/[\u001B\u0007\uE0B0\uFFFD]/u);
+ expect(modelControls.nextElementSibling).toBe(status);
+ expect(status.nextElementSibling).toBe(composer);
+ });
it("shows a jump control only while scrolled up and moves to the latest message", async () => {
const user = userEvent.setup();
const onScroll = vi.fn();