diff --git a/extensions/memory-core/src/dreaming.test.ts b/extensions/memory-core/src/dreaming.test.ts index c731eb873d0..9d9ae36d5bf 100644 --- a/extensions/memory-core/src/dreaming.test.ts +++ b/extensions/memory-core/src/dreaming.test.ts @@ -1535,7 +1535,70 @@ describe("gateway startup reconciliation", () => { } }); - it("does not reschedule startup cron retry from stale enabled config after runtime config disables dreaming", async () => { + it("retries disabled startup cleanup until cron is available", async () => { + vi.useFakeTimers(); + clearInternalHooks(); + const logger = createLogger(); + const managedJob: CronJobLike = { + id: "job-managed", + name: constants.MANAGED_DREAMING_CRON_NAME, + description: `${constants.MANAGED_DREAMING_CRON_TAG} test`, + enabled: true, + schedule: { kind: "cron", expr: "0 3 * * *" }, + sessionTarget: "main", + wakeMode: "now", + payload: { kind: "systemEvent", text: constants.DREAMING_SYSTEM_EVENT_TEXT }, + createdAtMs: 10, + }; + const harness = createCronHarness([managedJob]); + const onMock = vi.fn(); + const api: DreamingPluginApiTestDouble = { + config: { + plugins: { + entries: { + "memory-core": { + config: { + dreaming: { + enabled: false, + frequency: "15 4 * * *", + timezone: "UTC", + }, + }, + }, + }, + }, + }, + pluginConfig: {}, + logger, + runtime: {}, + on: onMock, + }; + + try { + registerShortTermPromotionDreamingForTest(api); + let cronAvailable = false; + await triggerGatewayStart(onMock, { + config: api.config, + getCron: () => (cronAvailable ? harness.cron : undefined), + }); + + await vi.advanceTimersByTimeAsync(constants.STARTUP_CRON_RETRY_DELAY_MS); + expect(harness.removeCalls).toHaveLength(0); + + cronAvailable = true; + await vi.advanceTimersByTimeAsync(constants.STARTUP_CRON_RETRY_DELAY_MS); + + expect(harness.removeCalls).toEqual(["job-managed"]); + expect(harness.jobs).toHaveLength(0); + expect(harness.addCalls).toHaveLength(0); + expectLogContains(logger.info, "removed 1 managed dreaming cron job"); + } finally { + vi.useRealTimers(); + clearInternalHooks(); + } + }); + + it("does not recreate startup cron from stale enabled config after runtime config disables dreaming", async () => { vi.useFakeTimers(); clearInternalHooks(); const logger = createLogger(); diff --git a/extensions/memory-core/src/dreaming.ts b/extensions/memory-core/src/dreaming.ts index 3cb5d92fc6a..7571c86441a 100644 --- a/extensions/memory-core/src/dreaming.ts +++ b/extensions/memory-core/src/dreaming.ts @@ -694,14 +694,6 @@ export function registerShortTermPromotionDreaming(api: OpenClawPluginApi): void const resolveCurrentConfig = (): OpenClawConfig => (api.runtime.config?.current?.() ?? api.config) as OpenClawConfig; - const resolveCurrentDreamingConfig = (): ShortTermPromotionDreamingConfig => { - const cfg = resolveCurrentConfig(); - return resolveShortTermPromotionDreamingConfig({ - pluginConfig: resolveMemoryCorePluginConfig(cfg), - cfg, - }); - }; - const clearStartupCronRetry = (): void => { if (startupCronRetryTimer) { clearTimeout(startupCronRetryTimer); @@ -823,8 +815,8 @@ export function registerShortTermPromotionDreaming(api: OpenClawPluginApi): void return config; }; - const scheduleStartupCronRetry = (config: ShortTermPromotionDreamingConfig): void => { - if (disposed || !config.enabled || hasStartupCron()) { + const scheduleStartupCronRetry = (): void => { + if (disposed || hasStartupCron()) { clearStartupCronRetry(); return; } @@ -838,12 +830,12 @@ export function registerShortTermPromotionDreaming(api: OpenClawPluginApi): void } startupCronRetryAttempts += 1; void reconcileManagedDreamingCron({ reason: "runtime" }) - .then((latestConfig) => { - if (disposed || !latestConfig.enabled || hasStartupCron()) { + .then(() => { + if (disposed || hasStartupCron()) { clearStartupCronRetry(); return; } - scheduleStartupCronRetry(latestConfig); + scheduleStartupCronRetry(); }) .catch((err) => { if (disposed) { @@ -852,13 +844,7 @@ export function registerShortTermPromotionDreaming(api: OpenClawPluginApi): void api.logger.error( `memory-core: deferred dreaming cron retry failed: ${formatErrorMessage(err)}`, ); - try { - scheduleStartupCronRetry(resolveCurrentDreamingConfig()); - } catch (configErr) { - api.logger.error( - `memory-core: deferred dreaming cron retry config refresh failed: ${formatErrorMessage(configErr)}`, - ); - } + scheduleStartupCronRetry(); }); }, STARTUP_CRON_RETRY_DELAY_MS); }; @@ -868,12 +854,12 @@ export function registerShortTermPromotionDreaming(api: OpenClawPluginApi): void // Store the gateway context for runtime cron resolution retries. gatewayContext = ctx as unknown as { getCron?: () => CronServiceLike | null }; try { - const config = await reconcileManagedDreamingCron({ + await reconcileManagedDreamingCron({ reason: "startup", startupConfig: ctx.config, startupCron: () => resolveCronServiceFromGatewayContext(ctx), }); - scheduleStartupCronRetry(config); + scheduleStartupCronRetry(); } catch (err) { api.logger.error( `memory-core: dreaming startup reconciliation failed: ${formatErrorMessage(err)}`,