Files
openclaw/src/commands/sessions-table.ts
Gustavo Madeira Santana eff3c5c707 Session/Cron maintenance hardening and cleanup UX (#24753)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 7533b85156
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-02-23 22:39:48 +00:00

149 lines
4.9 KiB
TypeScript

import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
import type { SessionEntry } from "../config/sessions.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveSessionModelRef } from "../gateway/session-utils.js";
import { formatTimeAgo } from "../infra/format-time/format-relative.ts";
import { parseAgentSessionKey } from "../routing/session-key.js";
import { theme } from "../terminal/theme.js";
export type SessionDisplayRow = {
key: string;
updatedAt: number | null;
ageMs: number | null;
sessionId?: string;
systemSent?: boolean;
abortedLastRun?: boolean;
thinkingLevel?: string;
verboseLevel?: string;
reasoningLevel?: string;
elevatedLevel?: string;
responseUsage?: string;
groupActivation?: string;
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
totalTokensFresh?: boolean;
model?: string;
modelProvider?: string;
providerOverride?: string;
modelOverride?: string;
contextTokens?: number;
};
export type SessionDisplayDefaults = {
model: string;
};
export const SESSION_KEY_PAD = 26;
export const SESSION_AGE_PAD = 9;
export const SESSION_MODEL_PAD = 14;
export function toSessionDisplayRows(store: Record<string, SessionEntry>): SessionDisplayRow[] {
return Object.entries(store)
.map(([key, entry]) => {
const updatedAt = entry?.updatedAt ?? null;
return {
key,
updatedAt,
ageMs: updatedAt ? Date.now() - updatedAt : null,
sessionId: entry?.sessionId,
systemSent: entry?.systemSent,
abortedLastRun: entry?.abortedLastRun,
thinkingLevel: entry?.thinkingLevel,
verboseLevel: entry?.verboseLevel,
reasoningLevel: entry?.reasoningLevel,
elevatedLevel: entry?.elevatedLevel,
responseUsage: entry?.responseUsage,
groupActivation: entry?.groupActivation,
inputTokens: entry?.inputTokens,
outputTokens: entry?.outputTokens,
totalTokens: entry?.totalTokens,
totalTokensFresh: entry?.totalTokensFresh,
model: entry?.model,
modelProvider: entry?.modelProvider,
providerOverride: entry?.providerOverride,
modelOverride: entry?.modelOverride,
contextTokens: entry?.contextTokens,
} satisfies SessionDisplayRow;
})
.toSorted((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
}
export function resolveSessionDisplayDefaults(cfg: OpenClawConfig): SessionDisplayDefaults {
const resolved = resolveConfiguredModelRef({
cfg,
defaultProvider: DEFAULT_PROVIDER,
defaultModel: DEFAULT_MODEL,
});
return {
model: resolved.model ?? DEFAULT_MODEL,
};
}
export function resolveSessionDisplayModel(
cfg: OpenClawConfig,
row: Pick<
SessionDisplayRow,
"key" | "model" | "modelProvider" | "modelOverride" | "providerOverride"
>,
defaults: SessionDisplayDefaults,
): string {
const resolved = resolveSessionModelRef(cfg, row, parseAgentSessionKey(row.key)?.agentId);
return resolved.model ?? defaults.model;
}
function truncateSessionKey(key: string): string {
if (key.length <= SESSION_KEY_PAD) {
return key;
}
const head = Math.max(4, SESSION_KEY_PAD - 10);
return `${key.slice(0, head)}...${key.slice(-6)}`;
}
export function formatSessionKeyCell(key: string, rich: boolean): string {
const label = truncateSessionKey(key).padEnd(SESSION_KEY_PAD);
return rich ? theme.accent(label) : label;
}
export function formatSessionAgeCell(updatedAt: number | null | undefined, rich: boolean): string {
const ageLabel = updatedAt ? formatTimeAgo(Date.now() - updatedAt) : "unknown";
const padded = ageLabel.padEnd(SESSION_AGE_PAD);
return rich ? theme.muted(padded) : padded;
}
export function formatSessionModelCell(model: string | null | undefined, rich: boolean): string {
const label = (model ?? "unknown").padEnd(SESSION_MODEL_PAD);
return rich ? theme.info(label) : label;
}
export function formatSessionFlagsCell(
row: Pick<
SessionDisplayRow,
| "thinkingLevel"
| "verboseLevel"
| "reasoningLevel"
| "elevatedLevel"
| "responseUsage"
| "groupActivation"
| "systemSent"
| "abortedLastRun"
| "sessionId"
>,
rich: boolean,
): string {
const flags = [
row.thinkingLevel ? `think:${row.thinkingLevel}` : null,
row.verboseLevel ? `verbose:${row.verboseLevel}` : null,
row.reasoningLevel ? `reasoning:${row.reasoningLevel}` : null,
row.elevatedLevel ? `elev:${row.elevatedLevel}` : null,
row.responseUsage ? `usage:${row.responseUsage}` : null,
row.groupActivation ? `activation:${row.groupActivation}` : null,
row.systemSent ? "system" : null,
row.abortedLastRun ? "aborted" : null,
row.sessionId ? `id:${row.sessionId}` : null,
].filter(Boolean);
const label = flags.join(" ");
return label.length === 0 ? "" : rich ? theme.muted(label) : label;
}