





















@@ -222,6 +222,56 @@ describe("applyFileBackedSessionStoreMaintenance", () => {
222222]);
223223expect(trajectoryCleanupReferencedIds).toEqual(new Set(["shared-session", "active-session"]));
224224});
225+226+it("forced cleanup prunes stale model-run probes before the cap evicts real sessions", async () => {
227+const now = Date.now();
228+const staleProbe = "agent:main:explicit:model-run-123e4567-e89b-12d3-a456-426614174099";
229+const store: Record<string, SessionEntry> = {
230+[staleProbe]: makeEntry(now - 2 * DAY_MS),
231+};
232+for (let i = 0; i < 50; i++) {
233+store[`agent:main:explicit:real-${i}`] = makeEntry(now - 3 * DAY_MS);
234+}
235+let report: { modelRunPruned: number; pruned: number; capped: number } | undefined;
236+237+const result = await applyFileBackedSessionStoreMaintenance({
238+storePath: "/tmp/openclaw-sessions/sessions.json",
239+ store,
240+maintenanceConfig: {
241+mode: "enforce",
242+pruneAfterMs: 7 * DAY_MS,
243+maxEntries: 50,
244+modelRunPruneAfterMs: DAY_MS,
245+modelRunPruneAfterConfigured: false,
246+resetArchiveRetentionMs: null,
247+maxDiskBytes: null,
248+highWaterBytes: null,
249+},
250+maintenanceOverride: { mode: "enforce" },
251+onMaintenanceApplied: (applied) => {
252+report = {
253+modelRunPruned: applied.modelRunPruned,
254+pruned: applied.pruned,
255+capped: applied.capped,
256+};
257+},
258+log: { warn: () => {}, info: () => {} },
259+artifacts: {
260+archiveRemovedSessionTranscripts: async () => new Set(),
261+removeRemovedSessionTrajectoryArtifacts: async () => {},
262+cleanupArchivedSessionTranscripts: async () => {},
263+},
264+});
265+266+expect(result.changedStore).toBe(true);
267+expect(report?.modelRunPruned).toBe(1);
268+expect(report?.capped).toBe(0);
269+expect(store[staleProbe]).toBeUndefined();
270+expect(Object.keys(store)).toHaveLength(50);
271+for (let i = 0; i < 50; i++) {
272+expect(store).toHaveProperty(`agent:main:explicit:real-${i}`);
273+}
274+});
225275});
226276227277describe("pruneStaleModelRunEntries", () => {
@@ -520,6 +570,61 @@ describe("resolveMaintenanceConfigFromInput", () => {
520570expect(maintenance.mode).toBe("enforce");
521571});
522572573+it("defaults gateway model-run probes to 24h retention with override and disable support", () => {
574+expect(resolveMaintenanceConfigFromInput().modelRunPruneAfterMs).toBe(DAY_MS);
575+expect(
576+resolveMaintenanceConfigFromInput({ modelRunPruneAfter: "48h" }).modelRunPruneAfterMs,
577+).toBe(2 * DAY_MS);
578+expect(
579+resolveMaintenanceConfigFromInput({ modelRunPruneAfter: false }).modelRunPruneAfterMs,
580+).toBe(null);
581+expect(resolveMaintenanceConfigFromInput().modelRunPruneAfterConfigured).toBe(false);
582+expect(
583+resolveMaintenanceConfigFromInput({ modelRunPruneAfter: "48h" }).modelRunPruneAfterConfigured,
584+).toBe(true);
585+expect(
586+resolveMaintenanceConfigFromInput({ modelRunPruneAfter: false }).modelRunPruneAfterConfigured,
587+).toBe(true);
588+expect(
589+resolveMaintenanceConfigFromInput({ modelRunPruneAfter: "bad" }).modelRunPruneAfterMs,
590+).toBe(null);
591+expect(
592+resolveMaintenanceConfigFromInput({ modelRunPruneAfter: "bad" }).modelRunPruneAfterConfigured,
593+).toBe(true);
594+});
595+596+it("force-gates the unset model-run prune default to the cap-eviction threshold", () => {
597+const defaultMaintenance = resolveMaintenanceConfigFromInput({ maxEntries: 50 });
598+expect(resolveSessionEntryMaintenanceHighWater(50)).toBe(75);
599+expect(shouldRunModelRunPrune({ maintenance: defaultMaintenance, entryCount: 60 })).toBe(false);
600+expect(
601+shouldRunModelRunPrune({ maintenance: defaultMaintenance, entryCount: 60, force: true }),
602+).toBe(true);
603+expect(
604+shouldRunModelRunPrune({ maintenance: defaultMaintenance, entryCount: 50, force: true }),
605+).toBe(false);
606+expect(
607+shouldRunModelRunPrune({
608+maintenance: resolveMaintenanceConfigFromInput({
609+maxEntries: 50,
610+modelRunPruneAfter: "24h",
611+}),
612+entryCount: 1,
613+force: true,
614+}),
615+).toBe(true);
616+expect(
617+shouldRunModelRunPrune({
618+maintenance: resolveMaintenanceConfigFromInput({
619+maxEntries: 50,
620+modelRunPruneAfter: false,
621+}),
622+entryCount: 60,
623+force: true,
624+}),
625+).toBe(false);
626+});
627+523628it("batches normal entry-count maintenance for production-sized caps", () => {
524629expect(resolveSessionEntryMaintenanceHighWater(2)).toBe(3);
525630expect(resolveSessionEntryMaintenanceHighWater(50)).toBe(75);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。