import { lookupContextTokens } from "../agents/context.js"; import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER, } from "../agents/defaults.js"; import { resolveConfiguredModelRef } from "../agents/model-selection.js"; import { loadConfig } from "../config/config.js"; import { loadSessionStore, resolveStorePath, type SessionEntry, } from "../config/sessions.js"; import { info } from "../globals.js"; import type { RuntimeEnv } from "../runtime.js"; import { isRich, theme } from "../terminal/theme.js"; type SessionRow = { key: string; kind: "direct" | "group" | "global" | "unknown"; 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; model?: string; contextTokens?: number; }; const KIND_PAD = 6; const KEY_PAD = 26; const AGE_PAD = 9; const MODEL_PAD = 14; const TOKENS_PAD = 20; const formatKTokens = (value: number) => `${(value / 1000).toFixed(value >= 10_000 ? 0 : 1)}k`; const truncateKey = (key: string) => { if (key.length <= KEY_PAD) return key; const head = Math.max(4, KEY_PAD - 10); return `${key.slice(0, head)}...${key.slice(-6)}`; }; const colorByPct = (label: string, pct: number | null, rich: boolean) => { if (!rich || pct === null) return label; if (pct >= 95) return theme.error(label); if (pct >= 80) return theme.warn(label); if (pct >= 60) return theme.success(label); return theme.muted(label); }; const formatTokensCell = ( total: number, contextTokens: number | null, rich: boolean, ) => { if (!total) return "-".padEnd(TOKENS_PAD); const totalLabel = formatKTokens(total); const ctxLabel = contextTokens ? formatKTokens(contextTokens) : "?"; const pct = contextTokens ? Math.min(999, Math.round((total / contextTokens) * 100)) : null; const label = `${totalLabel}/${ctxLabel} (${pct ?? "?"}%)`; const padded = label.padEnd(TOKENS_PAD); return colorByPct(padded, pct, rich); }; const formatKindCell = (kind: SessionRow["kind"], rich: boolean) => { const label = kind.padEnd(KIND_PAD); if (!rich) return label; if (kind === "group") return theme.accentBright(label); if (kind === "global") return theme.warn(label); if (kind === "direct") return theme.accent(label); return theme.muted(label); }; const formatAgeCell = (updatedAt: number | null | undefined, rich: boolean) => { const ageLabel = updatedAt ? formatAge(Date.now() - updatedAt) : "unknown"; const padded = ageLabel.padEnd(AGE_PAD); return rich ? theme.muted(padded) : padded; }; const formatModelCell = (model: string | null | undefined, rich: boolean) => { const label = (model ?? "unknown").padEnd(MODEL_PAD); return rich ? theme.info(label) : label; }; const formatFlagsCell = (row: SessionRow, rich: boolean) => { 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; }; const formatAge = (ms: number | null | undefined) => { if (!ms || ms < 0) return "unknown"; const minutes = Math.round(ms / 60_000); if (minutes < 1) return "just now"; if (minutes < 60) return `${minutes}m ago`; const hours = Math.round(minutes / 60); if (hours < 48) return `${hours}h ago`; const days = Math.round(hours / 24); return `${days}d ago`; }; function classifyKey(key: string, entry?: SessionEntry): SessionRow["kind"] { if (key === "global") return "global"; if (key === "unknown") return "unknown"; if (entry?.chatType === "group" || entry?.chatType === "room") return "group"; if ( key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:") ) { return "group"; } return "direct"; } function toRows(store: Record): SessionRow[] { return Object.entries(store) .map(([key, entry]) => { const updatedAt = entry?.updatedAt ?? null; return { key, kind: classifyKey(key, entry), 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, model: entry?.model, contextTokens: entry?.contextTokens, } satisfies SessionRow; }) .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)); } export async function sessionsCommand( opts: { json?: boolean; store?: string; active?: string }, runtime: RuntimeEnv, ) { const cfg = loadConfig(); const resolved = resolveConfiguredModelRef({ cfg, defaultProvider: DEFAULT_PROVIDER, defaultModel: DEFAULT_MODEL, }); const configContextTokens = cfg.agents?.defaults?.contextTokens ?? lookupContextTokens(resolved.model) ?? DEFAULT_CONTEXT_TOKENS; const configModel = resolved.model ?? DEFAULT_MODEL; const storePath = resolveStorePath(opts.store ?? cfg.session?.store); const store = loadSessionStore(storePath); let activeMinutes: number | undefined; if (opts.active !== undefined) { const parsed = Number.parseInt(String(opts.active), 10); if (Number.isNaN(parsed) || parsed <= 0) { runtime.error("--active must be a positive integer (minutes)"); runtime.exit(1); return; } activeMinutes = parsed; } const rows = toRows(store).filter((row) => { if (activeMinutes === undefined) return true; if (!row.updatedAt) return false; return Date.now() - row.updatedAt <= activeMinutes * 60_000; }); if (opts.json) { runtime.log( JSON.stringify( { path: storePath, count: rows.length, activeMinutes: activeMinutes ?? null, sessions: rows.map((r) => ({ ...r, contextTokens: r.contextTokens ?? lookupContextTokens(r.model) ?? configContextTokens ?? null, model: r.model ?? configModel ?? null, })), }, null, 2, ), ); return; } runtime.log(info(`Session store: ${storePath}`)); runtime.log(info(`Sessions listed: ${rows.length}`)); if (activeMinutes) { runtime.log(info(`Filtered to last ${activeMinutes} minute(s)`)); } if (rows.length === 0) { runtime.log("No sessions found."); return; } const rich = isRich(); const header = [ "Kind".padEnd(KIND_PAD), "Key".padEnd(KEY_PAD), "Age".padEnd(AGE_PAD), "Model".padEnd(MODEL_PAD), "Tokens (ctx %)".padEnd(TOKENS_PAD), "Flags", ].join(" "); runtime.log(rich ? theme.heading(header) : header); for (const row of rows) { const model = row.model ?? configModel; const contextTokens = row.contextTokens ?? lookupContextTokens(model) ?? configContextTokens; const input = row.inputTokens ?? 0; const output = row.outputTokens ?? 0; const total = row.totalTokens ?? input + output; const keyLabel = truncateKey(row.key).padEnd(KEY_PAD); const keyCell = rich ? theme.accent(keyLabel) : keyLabel; const line = [ formatKindCell(row.kind, rich), keyCell, formatAgeCell(row.updatedAt, rich), formatModelCell(model, rich), formatTokensCell(total, contextTokens ?? null, rich), formatFlagsCell(row, rich), ].join(" "); runtime.log(line.trimEnd()); } }