



















@@ -1,10 +1,16 @@
11import fs from "node:fs";
2+import path from "node:path";
23import { resolveDefaultAgentId } from "../../agents/agent-scope.js";
34import { resolveStoredSessionOwnerAgentId } from "../../gateway/session-store-key.js";
45import { getLogger } from "../../logging/logger.js";
56import { normalizeAgentId } from "../../routing/session-key.js";
67import type { OpenClawConfig } from "../types.openclaw.js";
7-import { enforceSessionDiskBudget } from "./disk-budget.js";
8+import {
9+enforceSessionDiskBudget,
10+pruneUnreferencedSessionArtifacts,
11+resolveSessionArtifactCanonicalPathsForEntry,
12+type SessionUnreferencedArtifactSweepResult,
13+} from "./disk-budget.js";
814import {
915resolveSessionFilePath,
1016resolveSessionFilePathOptions,
@@ -54,6 +60,7 @@ export type SessionCleanupSummary = {
5460missing: number;
5561pruned: number;
5662capped: number;
63+unreferencedArtifacts: SessionUnreferencedArtifactSweepResult;
5764diskBudget: Awaited<ReturnType<typeof enforceSessionDiskBudget>>;
5865wouldMutate: boolean;
5966applied?: true;
@@ -143,14 +150,35 @@ function pruneMissingTranscriptEntries(params: {
143150return removed;
144151}
145152153+function addEntryArtifactPathsToSet(params: {
154+paths: Set<string>;
155+store: Record<string, SessionEntry>;
156+storePath: string;
157+keys: ReadonlySet<string>;
158+}): void {
159+const sessionsDir = path.dirname(params.storePath);
160+for (const key of params.keys) {
161+const entry = params.store[key];
162+if (!entry) {
163+continue;
164+}
165+for (const artifactPath of resolveSessionArtifactCanonicalPathsForEntry({
166+ sessionsDir,
167+ entry,
168+})) {
169+params.paths.add(artifactPath);
170+}
171+}
172+}
173+146174async function previewStoreCleanup(params: {
147175target: SessionStoreTarget;
176+maintenance: ResolvedSessionMaintenanceConfig;
148177mode: ResolvedSessionMaintenanceConfig["mode"];
149178dryRun: boolean;
150179activeKey?: string;
151180fixMissing?: boolean;
152181}) {
153-const maintenance = resolveMaintenanceConfig();
154182const beforeStore = loadSessionStore(params.target.storePath, { skipCache: true });
155183const previewStore = cloneSessionStoreRecord(beforeStore);
156184const staleKeys = new Set<string>();
@@ -166,26 +194,50 @@ async function previewStoreCleanup(params: {
166194},
167195})
168196 : 0;
169-const pruned = pruneStaleEntries(previewStore, maintenance.pruneAfterMs, {
197+const pruned = pruneStaleEntries(previewStore, params.maintenance.pruneAfterMs, {
170198log: false,
171199onPruned: ({ key }) => {
172200staleKeys.add(key);
173201},
174202});
175-const capped = capEntryCount(previewStore, maintenance.maxEntries, {
203+const capped = capEntryCount(previewStore, params.maintenance.maxEntries, {
176204log: false,
177205onCapped: ({ key }) => {
178206cappedKeys.add(key);
179207},
180208});
209+const entryCleanupArtifactPaths = new Set<string>();
210+addEntryArtifactPathsToSet({
211+paths: entryCleanupArtifactPaths,
212+store: beforeStore,
213+storePath: params.target.storePath,
214+keys: staleKeys,
215+});
216+addEntryArtifactPathsToSet({
217+paths: entryCleanupArtifactPaths,
218+store: beforeStore,
219+storePath: params.target.storePath,
220+keys: cappedKeys,
221+});
181222const beforeBudgetStore = cloneSessionStoreRecord(previewStore);
223+const budgetRemovedFilePaths = new Set<string>();
182224const diskBudget = await enforceSessionDiskBudget({
183225store: previewStore,
184226storePath: params.target.storePath,
185227activeSessionKey: params.activeKey,
186- maintenance,
228+maintenance: params.maintenance,
187229warnOnly: false,
188230dryRun: true,
231+onRemoveFile: (canonicalPath) => {
232+budgetRemovedFilePaths.add(canonicalPath);
233+},
234+});
235+const unreferencedArtifacts = await pruneUnreferencedSessionArtifacts({
236+store: previewStore,
237+storePath: params.target.storePath,
238+olderThanMs: params.maintenance.pruneAfterMs,
239+dryRun: true,
240+excludeCanonicalPaths: new Set([...budgetRemovedFilePaths, ...entryCleanupArtifactPaths]),
189241});
190242const budgetEvictedKeys = new Set<string>();
191243for (const key of Object.keys(beforeBudgetStore)) {
@@ -199,6 +251,7 @@ async function previewStoreCleanup(params: {
199251missing > 0 ||
200252pruned > 0 ||
201253capped > 0 ||
254+unreferencedArtifacts.removedFiles > 0 ||
202255(diskBudget?.removedEntries ?? 0) > 0 ||
203256(diskBudget?.removedFiles ?? 0) > 0;
204257@@ -212,6 +265,7 @@ async function previewStoreCleanup(params: {
212265 missing,
213266 pruned,
214267 capped,
268+ unreferencedArtifacts,
215269 diskBudget,
216270 wouldMutate,
217271};
@@ -232,7 +286,8 @@ export async function runSessionsCleanup(params: {
232286targets?: SessionStoreTarget[];
233287}): Promise<SessionsCleanupRunResult> {
234288const { cfg, opts } = params;
235-const mode = opts.enforce ? "enforce" : resolveMaintenanceConfig().mode;
289+const maintenance = resolveMaintenanceConfig();
290+const mode = opts.enforce ? "enforce" : maintenance.mode;
236291const targets =
237292params.targets ??
238293resolveSessionStoreTargets(cfg, {
@@ -245,6 +300,7 @@ export async function runSessionsCleanup(params: {
245300for (const target of targets) {
246301const result = await previewStoreCleanup({
247302 target,
303+ maintenance,
248304 mode,
249305dryRun: Boolean(opts.dryRun),
250306activeKey: opts.activeKey,
@@ -281,6 +337,20 @@ export async function runSessionsCleanup(params: {
281337},
282338);
283339const afterStore = loadSessionStore(target.storePath, { skipCache: true });
340+const unreferencedArtifacts =
341+mode === "warn"
342+ ? {
343+scannedFiles: 0,
344+removedFiles: 0,
345+freedBytes: 0,
346+olderThanMs: maintenance.pruneAfterMs,
347+}
348+ : await pruneUnreferencedSessionArtifacts({
349+store: afterStore,
350+storePath: target.storePath,
351+olderThanMs: maintenance.pruneAfterMs,
352+dryRun: false,
353+});
284354const preview = previewResults.find(
285355(result) => result.summary.storePath === target.storePath,
286356);
@@ -298,10 +368,14 @@ export async function runSessionsCleanup(params: {
298368missing: 0,
299369pruned: 0,
300370capped: 0,
371+ unreferencedArtifacts,
301372diskBudget: null,
302373wouldMutate: false,
303374}),
304375dryRun: false,
376+ unreferencedArtifacts,
377+wouldMutate:
378+(preview?.summary.wouldMutate ?? false) || unreferencedArtifacts.removedFiles > 0,
305379applied: true,
306380appliedCount: Object.keys(afterStore).length,
307381}
@@ -315,11 +389,13 @@ export async function runSessionsCleanup(params: {
315389missing: missingApplied,
316390pruned: appliedReport.pruned,
317391capped: appliedReport.capped,
392+ unreferencedArtifacts,
318393diskBudget: appliedReport.diskBudget,
319394wouldMutate:
320395missingApplied > 0 ||
321396appliedReport.pruned > 0 ||
322397appliedReport.capped > 0 ||
398+unreferencedArtifacts.removedFiles > 0 ||
323399(appliedReport.diskBudget?.removedEntries ?? 0) > 0 ||
324400(appliedReport.diskBudget?.removedFiles ?? 0) > 0,
325401applied: true,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。