- Created wow-statusline package with configurable editor footer showing CWD, git branch, model/provider info, thinking level, token usage, and cost.
- Registered `/wow:statusline:show` command to inspect the module's enabled state.
- Added `statusline?` config option to WowConfig, supporting boolean or `{ enabled?: boolean }` shape.
- Wired statusline into wow-pi extension entry point as a workspace dependency and side-effect import.
105 lines
2.8 KiB
TypeScript
105 lines
2.8 KiB
TypeScript
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
import type {
|
|
FooterDataSnapshot,
|
|
SessionSnapshot,
|
|
UsageSummary,
|
|
} from "./types";
|
|
|
|
export interface StatuslineState {
|
|
currentCtx: ExtensionContext | null;
|
|
thinkingLevel: string;
|
|
statusEntries: Array<[string, string]>;
|
|
branch: string;
|
|
requestRender: (() => void) | null;
|
|
footerData: FooterDataSnapshot | null;
|
|
originalSetFooter: ((...args: unknown[]) => unknown) | null;
|
|
footerHijacked: boolean;
|
|
lastRenderSignature: string;
|
|
}
|
|
|
|
export function createState(): StatuslineState {
|
|
return {
|
|
currentCtx: null,
|
|
thinkingLevel: "off",
|
|
statusEntries: [],
|
|
branch: "",
|
|
requestRender: null,
|
|
footerData: null,
|
|
originalSetFooter: null,
|
|
footerHijacked: false,
|
|
lastRenderSignature: "",
|
|
};
|
|
}
|
|
|
|
export function snapshotFromContext(
|
|
state: StatuslineState,
|
|
ctx: ExtensionContext,
|
|
): SessionSnapshot {
|
|
return {
|
|
cwd: ctx.cwd,
|
|
branch: state.branch,
|
|
modelName: ctx.model?.name || ctx.model?.id || "no-model",
|
|
provider: ctx.model?.provider,
|
|
thinkingLevel: state.thinkingLevel,
|
|
usage: collectUsage(ctx),
|
|
statuses: [...state.statusEntries],
|
|
};
|
|
}
|
|
|
|
export function syncFooterSnapshot(state: StatuslineState): void {
|
|
if (!state.footerData) return;
|
|
state.branch = state.footerData.getGitBranch() || "";
|
|
const statuses = state.footerData.getExtensionStatuses();
|
|
if (statuses.size === 0) return;
|
|
state.statusEntries = Array.from(statuses.entries()).sort(([left], [right]) =>
|
|
left.localeCompare(right),
|
|
);
|
|
}
|
|
|
|
function collectUsage(ctx: ExtensionContext): UsageSummary {
|
|
let input = 0;
|
|
let output = 0;
|
|
let cost = 0;
|
|
|
|
try {
|
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
continue;
|
|
const usage = readUsage(entry.message);
|
|
if (!usage) continue;
|
|
input += usage.input;
|
|
output += usage.output;
|
|
cost += usage.cost;
|
|
}
|
|
} catch {
|
|
// Session access can fail during teardown or special modes.
|
|
}
|
|
|
|
return { input, output, cost };
|
|
}
|
|
|
|
function readUsage(message: unknown): UsageSummary | null {
|
|
if (!message || typeof message !== "object") return null;
|
|
const usage = (message as { usage?: unknown }).usage;
|
|
if (!usage || typeof usage !== "object") return null;
|
|
|
|
const input = readNumber((usage as { input?: unknown }).input);
|
|
const output = readNumber((usage as { output?: unknown }).output);
|
|
const costValue =
|
|
usage && typeof usage === "object"
|
|
? readNumber(
|
|
(
|
|
(usage as { cost?: unknown }).cost as
|
|
| { total?: unknown }
|
|
| undefined
|
|
)?.total,
|
|
)
|
|
: 0;
|
|
|
|
return { input, output, cost: costValue };
|
|
}
|
|
|
|
function readNumber(value: unknown): number {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
}
|