


























@@ -14,6 +14,10 @@ import type {
1414MemorySyncParams,
1515MemorySyncProgressUpdate,
1616} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
17+import {
18+clearConfigCache,
19+clearRuntimeConfigSnapshot,
20+} from "openclaw/plugin-sdk/runtime-config-snapshot";
1721import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
1822import { MemoryManagerSyncOps } from "./manager-sync-ops.js";
1923@@ -102,6 +106,7 @@ class SessionStartupCatchupHarness extends MemoryManagerSyncOps {
102106103107readonly syncCalls: SyncParams[] = [];
104108readonly indexedPaths: string[] = [];
109+readonly indexedContents: string[] = [];
105110106111constructor(sourceRows: SourceStateRow[]) {
107112super();
@@ -139,6 +144,35 @@ class SessionStartupCatchupHarness extends MemoryManagerSyncOps {
139144return Array.from(this.sessionPendingFiles);
140145}
141146147+addPendingSessionTarget(target: NonNullable<MemorySyncParams["sessions"]>[number]): void {
148+this.sessionPendingTargets.set(
149+[target.agentId ?? "", target.sessionId, target.sessionKey ?? ""].join("\0"),
150+target,
151+);
152+}
153+154+async processPendingSessionDeltas(): Promise<void> {
155+await (
156+this as unknown as {
157+processSessionDeltaBatch: () => Promise<void>;
158+}
159+).processSessionDeltaBatch();
160+}
161+162+async combineTargetSessionFilesForTest(params: {
163+sessions?: MemorySyncParams["sessions"];
164+sessionFiles?: string[];
165+}): Promise<Set<string> | null> {
166+return await (
167+this as unknown as {
168+combineTargetSessionFiles: (params: {
169+sessions?: MemorySyncParams["sessions"];
170+sessionFiles?: string[];
171+}) => Promise<Set<string> | null>;
172+}
173+).combineTargetSessionFiles(params);
174+}
175+142176isSessionsDirty(): boolean {
143177return this.sessionsDirty;
144178}
@@ -184,9 +218,10 @@ class SessionStartupCatchupHarness extends MemoryManagerSyncOps {
184218185219protected async indexFile(
186220entry: MemoryIndexEntry,
187-_options: { source: MemorySource; content?: string },
221+options: { source: MemorySource; content?: string },
188222): Promise<void> {
189223this.indexedPaths.push(entry.path);
224+this.indexedContents.push(options.content ?? "");
190225}
191226}
192227@@ -202,6 +237,8 @@ describe("session startup catch-up", () => {
202237vi.clearAllTimers();
203238vi.useRealTimers();
204239vi.unstubAllEnvs();
240+clearRuntimeConfigSnapshot();
241+clearConfigCache();
205242await fs.rm(stateDir, { recursive: true, force: true });
206243});
207244@@ -396,6 +433,147 @@ describe("session startup catch-up", () => {
396433expect(harness.indexedPaths).toEqual([]);
397434});
398435436+it("resolves identity-targeted delta sync through a custom session store", async () => {
437+const storeDir = path.join(stateDir, "custom-sessions");
438+const sessionFile = path.join(storeDir, "custom-thread.jsonl");
439+const storePath = path.join(storeDir, "sessions.json");
440+const configPath = path.join(stateDir, "openclaw.json");
441+await fs.mkdir(storeDir, { recursive: true });
442+await fs.writeFile(
443+sessionFile,
444+JSON.stringify({
445+type: "message",
446+message: { role: "user", content: "custom store target" },
447+}) + "\n",
448+"utf-8",
449+);
450+await fs.writeFile(
451+storePath,
452+JSON.stringify({
453+"agent:main:chat:custom": {
454+sessionFile: "custom-thread.jsonl",
455+sessionId: "custom-thread",
456+},
457+}),
458+"utf-8",
459+);
460+await fs.writeFile(configPath, JSON.stringify({ session: { store: storePath } }), "utf-8");
461+vi.stubEnv("OPENCLAW_CONFIG_PATH", configPath);
462+clearRuntimeConfigSnapshot();
463+clearConfigCache();
464+const harness = new SessionStartupCatchupHarness([]);
465+(harness as unknown as { settings: ResolvedMemorySearchConfig }).settings.sync.sessions = {
466+deltaBytes: 1,
467+deltaMessages: 1,
468+postCompactionForce: true,
469+};
470+harness.addPendingSessionTarget({
471+agentId: "main",
472+sessionId: "custom-thread",
473+sessionKey: "agent:main:chat:custom",
474+});
475+476+await harness.processPendingSessionDeltas();
477+await Promise.resolve();
478+479+expect(harness.getDirtySessionFiles()).toEqual([sessionFile]);
480+expect(harness.syncCalls).toEqual([{ reason: "session-delta" }]);
481+});
482+483+it("keeps explicit custom-store session file targets at the sync gate", async () => {
484+const storeDir = path.join(stateDir, "custom-sessions");
485+const sessionFile = path.join(storeDir, "explicit-target.jsonl");
486+const storePath = path.join(storeDir, "sessions.json");
487+const configPath = path.join(stateDir, "openclaw.json");
488+await fs.mkdir(storeDir, { recursive: true });
489+await fs.writeFile(
490+sessionFile,
491+JSON.stringify({
492+type: "message",
493+message: { role: "user", content: "explicit target" },
494+}) + "\n",
495+"utf-8",
496+);
497+await fs.writeFile(
498+storePath,
499+JSON.stringify({
500+"agent:main:chat:explicit-target": {
501+sessionFile: "explicit-target.jsonl",
502+sessionId: "explicit-target",
503+},
504+}),
505+"utf-8",
506+);
507+await fs.writeFile(configPath, JSON.stringify({ session: { store: storePath } }), "utf-8");
508+vi.stubEnv("OPENCLAW_CONFIG_PATH", configPath);
509+clearRuntimeConfigSnapshot();
510+clearConfigCache();
511+const harness = new SessionStartupCatchupHarness([]);
512+513+await expect(
514+harness.combineTargetSessionFilesForTest({ sessionFiles: [sessionFile] }),
515+).resolves.toEqual(new Set([sessionFile]));
516+});
517+518+it("preserves generated-session classification during targeted custom-store indexing", async () => {
519+const storeDir = path.join(stateDir, "custom-sessions");
520+const sessionFile = path.join(storeDir, "cron-thread.jsonl");
521+const otherSessionFile = path.join(storeDir, "other-thread.jsonl");
522+const storePath = path.join(storeDir, "sessions.json");
523+const configPath = path.join(stateDir, "openclaw.json");
524+await fs.mkdir(storeDir, { recursive: true });
525+await fs.writeFile(
526+sessionFile,
527+JSON.stringify({
528+type: "message",
529+message: { role: "assistant", content: "Internal cron output that must stay out." },
530+}) + "\n",
531+"utf-8",
532+);
533+await fs.writeFile(
534+otherSessionFile,
535+JSON.stringify({
536+type: "message",
537+message: { role: "user", content: "Other custom-store content" },
538+}) + "\n",
539+"utf-8",
540+);
541+await fs.writeFile(
542+storePath,
543+JSON.stringify({
544+"agent:main:cron:job-1:run:run-1": {
545+sessionFile: "cron-thread.jsonl",
546+sessionId: "cron-thread",
547+},
548+"agent:main:chat:other": {
549+sessionFile: "other-thread.jsonl",
550+sessionId: "other-thread",
551+},
552+}),
553+"utf-8",
554+);
555+await fs.writeFile(configPath, JSON.stringify({ session: { store: storePath } }), "utf-8");
556+vi.stubEnv("OPENCLAW_CONFIG_PATH", configPath);
557+clearRuntimeConfigSnapshot();
558+clearConfigCache();
559+const harness = new SessionStartupCatchupHarness([]);
560+561+await (
562+harness as unknown as {
563+syncSessionFiles: (params: {
564+needsFullReindex: boolean;
565+targetSessionFiles: string[];
566+}) => Promise<void>;
567+}
568+).syncSessionFiles({
569+needsFullReindex: false,
570+targetSessionFiles: [sessionFile],
571+});
572+573+expect(harness.indexedPaths).toEqual(["sessions/cron-thread.jsonl"]);
574+expect(harness.indexedContents).toEqual([""]);
575+});
576+399577it("queues transcript update identity without requiring a session file", async () => {
400578vi.useFakeTimers();
401579const harness = new SessionStartupCatchupHarness([]);
@@ -456,6 +634,49 @@ describe("session startup catch-up", () => {
456634harness.stopTranscriptListener();
457635});
458636637+it("queues file-only transcript updates from a custom session store", async () => {
638+vi.useFakeTimers();
639+const storeDir = path.join(stateDir, "custom-sessions");
640+const sessionFile = path.join(storeDir, "custom-update.jsonl");
641+const storePath = path.join(storeDir, "sessions.json");
642+const configPath = path.join(stateDir, "openclaw.json");
643+await fs.mkdir(storeDir, { recursive: true });
644+await fs.writeFile(
645+sessionFile,
646+JSON.stringify({
647+type: "message",
648+message: { role: "user", content: "custom update" },
649+}) + "\n",
650+"utf-8",
651+);
652+await fs.writeFile(
653+storePath,
654+JSON.stringify({
655+"agent:main:chat:custom-update": {
656+sessionFile: "custom-update.jsonl",
657+sessionId: "custom-update",
658+},
659+}),
660+"utf-8",
661+);
662+await fs.writeFile(configPath, JSON.stringify({ session: { store: storePath } }), "utf-8");
663+vi.stubEnv("OPENCLAW_CONFIG_PATH", configPath);
664+clearRuntimeConfigSnapshot();
665+clearConfigCache();
666+const harness = new SessionStartupCatchupHarness([]);
667+harness.startTranscriptListener();
668+669+emitSessionTranscriptUpdate({
670+ sessionFile,
671+sessionKey: "agent:main:chat:custom-update",
672+});
673+await Promise.resolve();
674+675+expect(harness.getPendingSessionFiles()).toEqual([sessionFile]);
676+expect(harness.getPendingSessionTargets()).toEqual([]);
677+harness.stopTranscriptListener();
678+});
679+459680it("prefers transcript update path compatibility before identity", async () => {
460681vi.useFakeTimers();
461682const session = await writeSessionFile("thread.jsonl");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。