mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-25 20:09:34 +00:00
* fix(compaction): preserve fresh usage after compaction Co-authored-by: HollyChou <128659251+Hollychou924@users.noreply.github.com> Co-authored-by: 吴杨帆 <39647285+leno23@users.noreply.github.com> * fix(compaction): satisfy stale usage timestamp narrowing * fix(clownfish): address review for ghcrawl-156678-autonomous-smoke (1) Co-authored-by: HollyChou <128659251+Hollychou924@users.noreply.github.com> Co-authored-by: de1ty <7804799+de1tydev@users.noreply.github.com> Co-authored-by: 吴杨帆 <39647285+leno23@users.noreply.github.com> Co-authored-by: Zhao Shiqi <109639815+425072024@users.noreply.github.com> --------- Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com> Co-authored-by: 吴杨帆 <39647285+leno23@users.noreply.github.com> Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com> Co-authored-by: Zhao Shiqi <109639815+425072024@users.noreply.github.com>
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
/**
|
|
* Shared helpers for clearing assistant usage snapshots invalidated by
|
|
* transcript compaction.
|
|
*/
|
|
import type { AgentMessage } from "./runtime/index.js";
|
|
import { makeZeroUsageSnapshot } from "./usage.js";
|
|
|
|
function parseCompactionUsageTimestamp(value: unknown): number | null {
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string") {
|
|
const parsed = Date.parse(value);
|
|
if (Number.isFinite(parsed)) {
|
|
return parsed;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function stripStaleAssistantUsageBeforeLatestCompaction<TMessage extends AgentMessage>(
|
|
messages: TMessage[],
|
|
options: {
|
|
mutate?: boolean;
|
|
whenMissingCompactionSummary?: "preserve" | "zeroAssistantUsage";
|
|
} = {},
|
|
): TMessage[] {
|
|
let latestCompactionSummaryIndex = -1;
|
|
let latestCompactionTimestamp: number | null = null;
|
|
for (let i = 0; i < messages.length; i += 1) {
|
|
const entry = messages[i];
|
|
if (entry?.role !== "compactionSummary") {
|
|
continue;
|
|
}
|
|
latestCompactionSummaryIndex = i;
|
|
latestCompactionTimestamp = parseCompactionUsageTimestamp(
|
|
(entry as { timestamp?: unknown }).timestamp ?? null,
|
|
);
|
|
}
|
|
const hasCompactionSummary = latestCompactionSummaryIndex !== -1;
|
|
if (!hasCompactionSummary && options.whenMissingCompactionSummary !== "zeroAssistantUsage") {
|
|
return messages;
|
|
}
|
|
|
|
const out = options.mutate ? messages : [...messages];
|
|
let touched = false;
|
|
for (let i = 0; i < out.length; i += 1) {
|
|
const candidate = out[i] as
|
|
| (AgentMessage & { usage?: unknown; timestamp?: unknown })
|
|
| undefined;
|
|
if (!candidate || candidate.role !== "assistant") {
|
|
continue;
|
|
}
|
|
if (!candidate.usage || typeof candidate.usage !== "object") {
|
|
continue;
|
|
}
|
|
|
|
const messageTimestamp = parseCompactionUsageTimestamp(candidate.timestamp);
|
|
const compactionTimestamp = latestCompactionTimestamp;
|
|
const hasTimestampBoundary =
|
|
hasCompactionSummary && compactionTimestamp !== null && messageTimestamp !== null;
|
|
const staleByMissingSummary = !hasCompactionSummary;
|
|
const staleByTimestamp = hasTimestampBoundary && messageTimestamp <= compactionTimestamp;
|
|
const staleByLegacyOrdering =
|
|
hasCompactionSummary && !hasTimestampBoundary && i < latestCompactionSummaryIndex;
|
|
if (!staleByMissingSummary && !staleByTimestamp && !staleByLegacyOrdering) {
|
|
continue;
|
|
}
|
|
|
|
// Session runtime expects assistant usage to stay structurally valid during
|
|
// accounting. Keep stale snapshots present, but zeroed after compaction.
|
|
const candidateRecord = candidate as unknown as Record<string, unknown>;
|
|
out[i] = {
|
|
...candidateRecord,
|
|
usage: makeZeroUsageSnapshot(),
|
|
} as unknown as TMessage;
|
|
touched = true;
|
|
}
|
|
return touched ? out : messages;
|
|
}
|