























@@ -0,0 +1,171 @@
1+import fs from "node:fs/promises";
2+import os from "node:os";
3+import path from "node:path";
4+import type { DatabaseSync } from "node:sqlite";
5+import type {
6+OpenClawConfig,
7+ResolvedMemorySearchConfig,
8+} from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
9+import type {
10+MemorySource,
11+MemorySyncProgressUpdate,
12+} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
13+import { afterEach, beforeEach, describe, expect, it } from "vitest";
14+import { MemoryManagerSyncOps } from "./manager-sync-ops.js";
15+16+type MemoryIndexEntry = {
17+path: string;
18+absPath: string;
19+mtimeMs: number;
20+size: number;
21+hash: string;
22+content?: string;
23+};
24+25+type SyncParams = {
26+reason?: string;
27+force?: boolean;
28+forceSessions?: boolean;
29+sessionFile?: string;
30+progress?: (update: MemorySyncProgressUpdate) => void;
31+};
32+33+class SessionDeltaHarness extends MemoryManagerSyncOps {
34+protected readonly cfg = {} as OpenClawConfig;
35+protected readonly agentId = "main";
36+protected readonly workspaceDir = "/tmp/openclaw-test-workspace";
37+protected readonly settings = {
38+sync: {
39+sessions: {
40+deltaBytes: 100_000,
41+deltaMessages: 50,
42+postCompactionForce: true,
43+},
44+},
45+} as ResolvedMemorySearchConfig;
46+protected readonly batch = {
47+enabled: false,
48+wait: false,
49+concurrency: 1,
50+pollIntervalMs: 0,
51+timeoutMs: 0,
52+};
53+protected readonly vector = { enabled: false, available: false };
54+protected readonly cache = { enabled: false };
55+protected db = null as unknown as DatabaseSync;
56+57+readonly syncCalls: SyncParams[] = [];
58+59+addPendingSessionFile(sessionFile: string) {
60+this.sessionPendingFiles.add(sessionFile);
61+}
62+63+getDirtySessionFiles(): string[] {
64+return Array.from(this.sessionsDirtyFiles);
65+}
66+67+isSessionsDirty(): boolean {
68+return this.sessionsDirty;
69+}
70+71+async processPendingSessionDeltas(): Promise<void> {
72+await (
73+this as unknown as {
74+processSessionDeltaBatch: () => Promise<void>;
75+}
76+).processSessionDeltaBatch();
77+}
78+79+protected computeProviderKey(): string {
80+return "test";
81+}
82+83+protected async sync(params?: SyncParams): Promise<void> {
84+this.syncCalls.push(params ?? {});
85+}
86+87+protected async withTimeout<T>(
88+promise: Promise<T>,
89+_timeoutMs: number,
90+_message: string,
91+): Promise<T> {
92+return await promise;
93+}
94+95+protected getIndexConcurrency(): number {
96+return 1;
97+}
98+99+protected pruneEmbeddingCacheIfNeeded(): void {}
100+101+protected async indexFile(
102+_entry: MemoryIndexEntry,
103+_options: { source: MemorySource; content?: string },
104+): Promise<void> {}
105+}
106+107+describe("session archive delta bypass", () => {
108+let tmpDir = "";
109+110+beforeEach(async () => {
111+tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-archive-delta-"));
112+});
113+114+afterEach(async () => {
115+await fs.rm(tmpDir, { recursive: true, force: true });
116+});
117+118+async function writeSessionFile(name: string): Promise<string> {
119+const filePath = path.join(tmpDir, name);
120+await fs.writeFile(
121+filePath,
122+JSON.stringify({
123+type: "message",
124+message: { role: "user", content: "short archived session" },
125+}) + "\n",
126+"utf-8",
127+);
128+return filePath;
129+}
130+131+it.each(["reset", "deleted"] as const)(
132+"marks below-threshold %s archives dirty immediately",
133+async (reason) => {
134+const archivePath = await writeSessionFile(
135+`session-a.jsonl.${reason}.2026-05-03T05-38-59.000Z`,
136+);
137+const harness = new SessionDeltaHarness();
138+harness.addPendingSessionFile(archivePath);
139+140+await harness.processPendingSessionDeltas();
141+142+expect(harness.getDirtySessionFiles()).toEqual([archivePath]);
143+expect(harness.isSessionsDirty()).toBe(true);
144+expect(harness.syncCalls).toEqual([{ reason: "session-delta" }]);
145+},
146+);
147+148+it("keeps .jsonl.bak archives on the normal below-threshold delta path", async () => {
149+const bakPath = await writeSessionFile("session-a.jsonl.bak.2026-05-03T05-38-59.000Z");
150+const harness = new SessionDeltaHarness();
151+harness.addPendingSessionFile(bakPath);
152+153+await harness.processPendingSessionDeltas();
154+155+expect(harness.getDirtySessionFiles()).toEqual([]);
156+expect(harness.isSessionsDirty()).toBe(false);
157+expect(harness.syncCalls).toEqual([]);
158+});
159+160+it("keeps live transcripts below the configured thresholds", async () => {
161+const livePath = await writeSessionFile("session-a.jsonl");
162+const harness = new SessionDeltaHarness();
163+harness.addPendingSessionFile(livePath);
164+165+await harness.processPendingSessionDeltas();
166+167+expect(harness.getDirtySessionFiles()).toEqual([]);
168+expect(harness.isSessionsDirty()).toBe(false);
169+expect(harness.syncCalls).toEqual([]);
170+});
171+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。