Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions apps/memos-local-plugin/core/pipeline/memory-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,14 @@ export function createMemoryCore(
}
}

function scheduleStartupRecovery(label: string, task: () => Promise<void>): void {
void task().catch((err) => {
log.debug(`${label}.failed`, {
err: err instanceof Error ? err.message : String(err),
});
});
}

// ─── Lifecycle ──
async function init(): Promise<void> {
if (shutDown) {
Expand Down Expand Up @@ -617,14 +625,18 @@ export function createMemoryCore(
});
}
if (stale.length > 0) {
await recoverOpenEpisodesAsSessionEnd(stale);
scheduleStartupRecovery("startup.open_recovery", async () => {
await recoverOpenEpisodesAsSessionEnd(stale);
});
}
}
const dirtyClosed = handle.repos.episodes
.list({ status: "closed", limit: 500 })
.filter((ep) => episodeRewardIsDirty(ep));
if (dirtyClosed.length > 0) {
await recoverDirtyClosedEpisodes(dirtyClosed);
scheduleStartupRecovery("startup.dirty_closed_recovery", async () => {
await recoverDirtyClosedEpisodes(dirtyClosed);
});
}
} catch (err) {
log.debug("init.orphan_scan.failed", {
Expand Down
34 changes: 34 additions & 0 deletions apps/memos-local-plugin/tests/unit/startup-recovery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";

const source = readFileSync(
join(__dirname, "../../core/pipeline/memory-core.ts"),
"utf8",
);

function initBody(): string {
const start = source.indexOf(" async function init(): Promise<void> {");
expect(start, "init() function should be present").toBeGreaterThanOrEqual(0);
const end = source.indexOf("\n function", start + 1);
expect(end, "init() should be followed by another function").toBeGreaterThan(start);
return source.slice(start, end);
}

function stripScheduledRecoveryCallbacks(body: string): string {
return body.replace(
/scheduleStartupRecovery\([\s\S]*?\n \}\);/g,
"scheduleStartupRecovery(<background task>);",
);
}

describe("memory-core startup recovery", () => {
it("does not block init on stale/dirty episode recovery", () => {
const synchronousInitBody = stripScheduledRecoveryCallbacks(initBody());

expect(synchronousInitBody).not.toContain("await recoverOpenEpisodesAsSessionEnd(stale)");
expect(synchronousInitBody).not.toContain("await recoverDirtyClosedEpisodes(dirtyClosed)");
expect(initBody()).toContain("scheduleStartupRecovery(\"startup.open_recovery\"");
expect(initBody()).toContain("scheduleStartupRecovery(\"startup.dirty_closed_recovery\"");
});
});