mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 08:11:26 +00:00
feat: show transcript file size in session status
Add transcript size monitoring to /status and session_status tool. Displays file size and message count (e.g. '📄 Transcript: 1.2 MB, 627 messages'). Shows ⚠️ warning when transcript exceeds 1 MB, which helps catch sessions approaching the compaction death spiral described in #13624. - getTranscriptInfo() reads JSONL file stat + line count - Wired into both /status command and session_status tool - 8 new tests covering file reading, formatting, and edge cases
This commit is contained in:
committed by
Peter Steinberger
parent
fc6d53c895
commit
15dd2cda20
@@ -55,6 +55,15 @@ type QueueStatus = {
|
||||
showDetails?: boolean;
|
||||
};
|
||||
|
||||
export type TranscriptInfo = {
|
||||
/** File size in bytes. */
|
||||
sizeBytes: number;
|
||||
/** Number of non-empty lines (messages) in the transcript. */
|
||||
messageCount: number;
|
||||
/** Absolute path to the transcript file. */
|
||||
filePath: string;
|
||||
};
|
||||
|
||||
type StatusArgs = {
|
||||
config?: OpenClawConfig;
|
||||
agent: AgentConfig;
|
||||
@@ -75,6 +84,7 @@ type StatusArgs = {
|
||||
mediaDecisions?: MediaUnderstandingDecision[];
|
||||
subagentsLine?: string;
|
||||
includeTranscriptUsage?: boolean;
|
||||
transcriptInfo?: TranscriptInfo;
|
||||
now?: number;
|
||||
};
|
||||
|
||||
@@ -324,6 +334,74 @@ const formatVoiceModeLine = (
|
||||
return `🔊 Voice: ${autoMode} · provider=${provider} · limit=${maxLength} · summary=${summarize}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read transcript file metadata (size + line count) for a session.
|
||||
* Returns `undefined` when the file does not exist or cannot be read.
|
||||
*/
|
||||
export function getTranscriptInfo(params: {
|
||||
sessionId?: string;
|
||||
sessionEntry?: SessionEntry;
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
storePath?: string;
|
||||
}): TranscriptInfo | undefined {
|
||||
if (!params.sessionId) {
|
||||
return undefined;
|
||||
}
|
||||
let logPath: string;
|
||||
try {
|
||||
const resolvedAgentId =
|
||||
params.agentId ??
|
||||
(params.sessionKey ? resolveAgentIdFromSessionKey(params.sessionKey) : undefined);
|
||||
logPath = resolveSessionFilePath(
|
||||
params.sessionId,
|
||||
params.sessionEntry,
|
||||
resolveSessionFilePathOptions({ agentId: resolvedAgentId, storePath: params.storePath }),
|
||||
);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const stat = fs.statSync(logPath);
|
||||
if (!stat.isFile()) {
|
||||
return undefined;
|
||||
}
|
||||
// Count non-empty lines for message count.
|
||||
const content = fs.readFileSync(logPath, "utf-8");
|
||||
let messageCount = 0;
|
||||
for (const line of content.split("\n")) {
|
||||
if (line.trim()) {
|
||||
messageCount += 1;
|
||||
}
|
||||
}
|
||||
return { sizeBytes: stat.size, messageCount, filePath: logPath };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} B`;
|
||||
}
|
||||
if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
}
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
/** Size threshold (bytes) above which a warning emoji is shown. Default: 1 MB. */
|
||||
const TRANSCRIPT_SIZE_WARNING_BYTES = 1024 * 1024;
|
||||
|
||||
function formatTranscriptLine(info: TranscriptInfo | undefined): string | null {
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
const sizeLabel = formatFileSize(info.sizeBytes);
|
||||
const warning = info.sizeBytes >= TRANSCRIPT_SIZE_WARNING_BYTES ? " ⚠️" : "";
|
||||
return `📄 Transcript: ${sizeLabel}, ${info.messageCount} message${info.messageCount === 1 ? "" : "s"}${warning}`;
|
||||
}
|
||||
|
||||
export function buildStatusMessage(args: StatusArgs): string {
|
||||
const now = args.now ?? Date.now();
|
||||
const entry = args.sessionEntry;
|
||||
@@ -472,6 +550,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
usagePair && costLine ? `${usagePair} · ${costLine}` : (usagePair ?? costLine);
|
||||
const mediaLine = formatMediaUnderstandingLine(args.mediaDecisions);
|
||||
const voiceLine = formatVoiceModeLine(args.config, args.sessionEntry);
|
||||
const transcriptLine = formatTranscriptLine(args.transcriptInfo);
|
||||
|
||||
return [
|
||||
versionLine,
|
||||
@@ -479,6 +558,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
modelLine,
|
||||
usageCostLine,
|
||||
`📚 ${contextLine}`,
|
||||
transcriptLine,
|
||||
mediaLine,
|
||||
args.usageLine,
|
||||
`🧵 ${sessionLine}`,
|
||||
|
||||
Reference in New Issue
Block a user