Skip to content

Commit f007c10

Browse files
committed
fix(memory): preserve pending dirty state on refresh
Keep real pending memory and session dirty work when an external index replacement clears stale identity state, while still cleaning identity-only dirty flags.
1 parent 01b5981 commit f007c10

2 files changed

Lines changed: 46 additions & 5 deletions

File tree

extensions/memory-core/src/memory/manager.self-heal-missing-identity.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,48 @@ describe("memory manager self-heal missing identity with FTS-only chunks", () =>
212212
expect(statusAfter.custom?.indexIdentity).toEqual({ status: "valid" });
213213
expect(statusAfter.dirty).toBe(false);
214214
});
215+
216+
it("preserves pending memory and session dirty work when replacement refresh clears missing identity", async () => {
217+
await seedChunksWithNoMeta();
218+
const memoryManager = await createManager({ provider: "none", vectorEnabled: false });
219+
const dirtyState = memoryManager as unknown as {
220+
dirty: boolean;
221+
memoryFullRetryDirty: boolean;
222+
sessionsDirty: boolean;
223+
sessionsDirtyFiles: Set<string>;
224+
sessionsFullRetryDirty: boolean;
225+
};
226+
227+
expect(indexIdentityStatus(memoryManager)).toBe("missing");
228+
dirtyState.dirty = true;
229+
dirtyState.memoryFullRetryDirty = true;
230+
dirtyState.sessionsDirty = true;
231+
dirtyState.sessionsDirtyFiles.add("session-a.jsonl");
232+
dirtyState.sessionsFullRetryDirty = true;
233+
234+
const cliResult = await getMemorySearchManager({
235+
cfg: createCfg({ provider: "none", vectorEnabled: false }),
236+
agentId: "main",
237+
purpose: "cli",
238+
});
239+
if (!cliResult.manager) {
240+
throw new Error(cliResult.error ?? "cli manager missing");
241+
}
242+
const cliManager = cliResult.manager as unknown as MemoryIndexManager;
243+
try {
244+
await cliManager.sync({ reason: "cli", force: true });
245+
} finally {
246+
await cliManager.close?.();
247+
}
248+
249+
const statusAfter = memoryManager.status();
250+
251+
expect(statusAfter.custom?.indexIdentity).toEqual({ status: "valid" });
252+
expect(statusAfter.dirty).toBe(true);
253+
expect(dirtyState.dirty).toBe(true);
254+
expect(dirtyState.memoryFullRetryDirty).toBe(true);
255+
expect(dirtyState.sessionsDirty).toBe(true);
256+
expect(dirtyState.sessionsDirtyFiles.has("session-a.jsonl")).toBe(true);
257+
expect(dirtyState.sessionsFullRetryDirty).toBe(true);
258+
});
215259
});

extensions/memory-core/src/memory/manager.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -636,11 +636,8 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
636636
state.status === "mismatched" ||
637637
(state.status === "missing" && (this.sources.has("memory") || this.hasIndexedChunks()));
638638
if (refreshedDatabase && hadIndexIdentityDirty && state.status === "valid") {
639-
this.dirty = false;
640-
this.sessionsDirty = false;
641-
this.sessionsDirtyFiles.clear();
642-
this.memoryFullRetryDirty = false;
643-
this.sessionsFullRetryDirty = false;
639+
this.dirty = this.memoryFullRetryDirty || this.pendingWatchPaths.size > 0;
640+
this.sessionsDirty = this.sessionsFullRetryDirty || this.sessionsDirtyFiles.size > 0;
644641
}
645642
if (refreshedDatabase) {
646643
this.indexFileRefreshPendingDirtyCleanup = false;

0 commit comments

Comments
 (0)