


























@@ -19,6 +19,10 @@ import {
1919resolveSessionFilePathOptions,
2020resolveStorePath,
2121} from "./paths.js";
22+import {
23+applySessionEntryLifecycleMutation,
24+type SessionEntryLifecycleRemoval,
25+} from "./session-accessor.js";
2226import { cloneSessionStoreRecord } from "./store-cache.js";
2327import { collectSessionMaintenancePreserveKeys } from "./store-maintenance-preserve.js";
2428import { resolveMaintenanceConfig } from "./store-maintenance-runtime.js";
@@ -27,12 +31,7 @@ import {
2731pruneStaleEntries,
2832type ResolvedSessionMaintenanceConfig,
2933} from "./store-maintenance.js";
30-import {
31-archiveRemovedSessionTranscripts,
32-loadSessionStore,
33-updateSessionStore,
34-type SessionMaintenanceApplyReport,
35-} from "./store.js";
34+import { loadSessionStore, updateSessionStore } from "./store.js";
3635import {
3736resolveSessionStoreTargets,
3837type SessionStoreTarget,
@@ -217,15 +216,6 @@ function isMainScopeStaleDirectSessionKey(params: {
217216);
218217}
219218220-function rememberRemovedSessionFile(
221-removedSessionFiles: Map<string, string | undefined>,
222-entry: SessionEntry | undefined,
223-): void {
224-if (entry?.sessionId) {
225-removedSessionFiles.set(entry.sessionId, entry.sessionFile);
226-}
227-}
228-229219function retireMainScopeDirectSessionEntries(params: {
230220cfg: OpenClawConfig;
231221store: Record<string, SessionEntry>;
@@ -270,7 +260,7 @@ export function serializeSessionCleanupResult(params: {
270260function pruneMissingTranscriptEntries(params: {
271261store: Record<string, SessionEntry>;
272262storePath: string;
273-onPruned?: (key: string) => void;
263+onPruned?: (key: string, entry: SessionEntry) => void;
274264}): number {
275265const sessionPathOpts = resolveSessionFilePathOptions({
276266storePath: params.storePath,
@@ -284,7 +274,7 @@ function pruneMissingTranscriptEntries(params: {
284274}
285275delete params.store[key];
286276removed += 1;
287-params.onPruned?.(key);
277+params.onPruned?.(key, entry);
288278continue;
289279}
290280let transcriptPath: string | undefined;
@@ -300,7 +290,7 @@ function pruneMissingTranscriptEntries(params: {
300290) {
301291delete params.store[key];
302292removed += 1;
303-params.onPruned?.(key);
293+params.onPruned?.(key, entry);
304294}
305295}
306296return removed;
@@ -500,64 +490,63 @@ export async function runSessionsCleanup(params: {
500490const appliedSummaries: SessionCleanupSummary[] = [];
501491if (!opts.dryRun) {
502492for (const target of targets) {
503-const appliedReportRef: { current: SessionMaintenanceApplyReport | null } = {
504-current: null,
505-};
506-const dmScopeRemovedSessionFiles = new Map<string, string | undefined>();
507-let missingApplied = 0;
508-let dmScopeRetiredApplied = 0;
509-await updateSessionStore(
510-target.storePath,
511-async (store) => {
512-let removed = 0;
513-if (opts.fixMissing) {
514-missingApplied = pruneMissingTranscriptEntries({
515- store,
516-storePath: target.storePath,
517-});
518-removed += missingApplied;
519-}
520-if (opts.fixDmScope) {
521-// DM-scope retirement removes stale main-scope direct entries during apply.
522-dmScopeRetiredApplied = retireMainScopeDirectSessionEntries({
523- cfg,
524- store,
525-targetAgentId: target.agentId,
526-activeKey: opts.activeKey,
527-onRetired: (_key, entry) => {
528-rememberRemovedSessionFile(dmScopeRemovedSessionFiles, entry);
529-},
493+const applyStore = loadSessionStore(target.storePath, { skipCache: true });
494+const missingRemovals: SessionEntryLifecycleRemoval[] = [];
495+const dmScopeRetiredRemovals: SessionEntryLifecycleRemoval[] = [];
496+if (opts.fixMissing) {
497+pruneMissingTranscriptEntries({
498+store: applyStore,
499+storePath: target.storePath,
500+onPruned: (sessionKey, entry) => {
501+missingRemovals.push({
502+ sessionKey,
503+expectedEntry: cloneSessionStoreRecord({ entry }).entry,
530504});
531-removed += dmScopeRetiredApplied;
532-}
533-return removed;
534-},
535-{
536-activeSessionKey: opts.activeKey,
537-maintenanceOverride: {
538- mode,
539505},
540-onMaintenanceApplied: (report) => {
541-appliedReportRef.current = report;
506+});
507+}
508+if (opts.fixDmScope) {
509+retireMainScopeDirectSessionEntries({
510+ cfg,
511+store: applyStore,
512+targetAgentId: target.agentId,
513+activeKey: opts.activeKey,
514+onRetired: (sessionKey, entry) => {
515+dmScopeRetiredRemovals.push({
516+ sessionKey,
517+expectedEntry: cloneSessionStoreRecord({ entry }).entry,
518+archiveRemovedTranscript: true,
519+});
542520},
543-},
544-);
545-if (dmScopeRemovedSessionFiles.size > 0) {
546-// Archive removed direct-session transcripts unless still referenced by surviving entries.
547-const storeAfterDmScopeRetire = loadSessionStore(target.storePath, { skipCache: true });
548-await archiveRemovedSessionTranscripts({
549-removedSessionFiles: dmScopeRemovedSessionFiles,
550-referencedSessionIds: new Set(
551-Object.values(storeAfterDmScopeRetire)
552-.map((entry) => entry?.sessionId)
553-.filter((id): id is string => Boolean(id)),
554-),
555-storePath: target.storePath,
556-reason: "deleted",
557-restrictToStoreDir: true,
558521});
559522}
560-const afterStore = loadSessionStore(target.storePath, { skipCache: true });
523+const removals: SessionEntryLifecycleRemoval[] = [
524+ ...missingRemovals,
525+ ...dmScopeRetiredRemovals,
526+];
527+const lifecycleResult = await applySessionEntryLifecycleMutation({
528+storePath: target.storePath,
529+ removals,
530+activeSessionKey: opts.activeKey,
531+maintenanceOverride: {
532+ mode,
533+},
534+restrictArchivedTranscriptsToStoreDir: true,
535+pruneUnreferencedArtifacts:
536+mode === "warn"
537+ ? undefined
538+ : {
539+olderThanMs: maintenance.pruneAfterMs,
540+dryRun: false,
541+},
542+});
543+const removedSessionKeys = new Set(lifecycleResult.removedSessionKeys);
544+const missingApplied = missingRemovals.filter(({ sessionKey }) =>
545+removedSessionKeys.has(sessionKey),
546+).length;
547+const dmScopeRetiredApplied = dmScopeRetiredRemovals.filter(({ sessionKey }) =>
548+removedSessionKeys.has(sessionKey),
549+).length;
561550const unreferencedArtifacts =
562551mode === "warn"
563552 ? {
@@ -566,16 +555,16 @@ export async function runSessionsCleanup(params: {
566555freedBytes: 0,
567556olderThanMs: maintenance.pruneAfterMs,
568557}
569- : await pruneUnreferencedSessionArtifacts({
570-store: afterStore,
571-storePath: target.storePath,
558+ : (lifecycleResult.unreferencedArtifacts ?? {
559+scannedFiles: 0,
560+removedFiles: 0,
561+freedBytes: 0,
572562olderThanMs: maintenance.pruneAfterMs,
573-dryRun: false,
574563});
575564const preview = previewResults.find(
576565(result) => result.summary.storePath === target.storePath,
577566);
578-const appliedReport = appliedReportRef.current;
567+const appliedReport = lifecycleResult.maintenanceReport;
579568const summary: SessionCleanupSummary =
580569appliedReport === null
581570 ? {
@@ -599,7 +588,7 @@ export async function runSessionsCleanup(params: {
599588wouldMutate:
600589(preview?.summary.wouldMutate ?? false) || unreferencedArtifacts.removedFiles > 0,
601590applied: true,
602-appliedCount: Object.keys(afterStore).length,
591+appliedCount: lifecycleResult.afterCount,
603592}
604593 : {
605594agentId: target.agentId,
@@ -623,7 +612,7 @@ export async function runSessionsCleanup(params: {
623612(appliedReport.diskBudget?.removedEntries ?? 0) > 0 ||
624613(appliedReport.diskBudget?.removedFiles ?? 0) > 0,
625614applied: true,
626-appliedCount: Object.keys(afterStore).length,
615+appliedCount: lifecycleResult.afterCount,
627616};
628617appliedSummaries.push(summary);
629618}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。