diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ff9def884d..0ad839c869c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai - Agents/model fallback: jump directly to a known later live-session model redirect instead of walking unrelated fallback candidates, while preserving the already-landed live-session/fallback loop guard. Fixes #57471; related loop family already closed via #58496. Thanks @yuxiaoyang2007-prog. - Gateway/Bonjour: keep @homebridge/ciao cancellation handlers registered across advertiser restarts so late probing cancellations cannot crash Linux and other mDNS-churned gateways. Thanks @vincentkoc. - Plugins/startup: load the default `memory-core` slot during Gateway startup when permitted so active-memory recall can call `memory_search` and `memory_get` without requiring an explicit `plugins.slots.memory` entry, while preserving `plugins.slots.memory: "none"`. Thanks @vincentkoc. +- Gateway/plugins: resolve `gateway_start` cron hooks from live Gateway runtime state before the legacy deps fallback, so memory-core dreaming cron reconciliation keeps working on installs where `deps.cron` is not populated during service startup. Fixes #72835. Thanks @RayWoo. - Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet. - Plugins/compat: inventory doctor-side deprecation migrations separately from runtime plugin compatibility so release sweeps preserve needed repairs while enforcing dated removal windows. Thanks @vincentkoc. - Plugins/compat: add missing dated compatibility records for legacy extension-api, memory registration, provider hook/type aliases, runtime aliases, channel SDK helpers, and approval/test utility shims. Thanks @vincentkoc. diff --git a/src/gateway/server-startup-post-attach.test.ts b/src/gateway/server-startup-post-attach.test.ts index 6b15358ac24..d3ebcf51fd9 100644 --- a/src/gateway/server-startup-post-attach.test.ts +++ b/src/gateway/server-startup-post-attach.test.ts @@ -398,6 +398,45 @@ describe("startGatewayPostAttachRuntime", () => { params.deps.cron = reloadedCron as never; expect(getCron()).toBe(reloadedCron); }); + + it("resolves gateway_start cron from the live runtime getter before deps fallback", async () => { + const runGatewayStart = vi.fn< + (event: PluginHookGatewayStartEvent, ctx: PluginHookGatewayContext) => Promise + >(async () => undefined); + const hookRunner = { + hasHooks: vi.fn((hookName: string) => hookName === "gateway_start"), + runGatewayStart, + }; + const depsCron = { list: vi.fn(), add: vi.fn(), update: vi.fn(), remove: vi.fn() }; + const liveCron = { list: vi.fn(), add: vi.fn(), update: vi.fn(), remove: vi.fn() }; + const reloadedCron = { list: vi.fn(), add: vi.fn(), update: vi.fn(), remove: vi.fn() }; + let currentLiveCron = liveCron; + const params = createPostAttachParams({ + deps: { cron: depsCron } as never, + getCronService: () => currentLiveCron, + }); + + await startGatewayPostAttachRuntime( + params, + createPostAttachRuntimeDeps({ + getGlobalHookRunner: vi.fn(async () => hookRunner as never), + }), + ); + + await vi.waitFor(() => { + expect(runGatewayStart).toHaveBeenCalledTimes(1); + }); + + const ctx = runGatewayStart.mock.calls[0]?.[1]; + if (!ctx?.getCron) { + throw new Error("gateway_start context did not expose getCron"); + } + expect(ctx.getCron()).toBe(liveCron); + + params.deps.cron = depsCron as never; + currentLiveCron = reloadedCron; + expect(ctx.getCron()).toBe(reloadedCron); + }); }); function createPostAttachRuntimeDeps( diff --git a/src/gateway/server-startup-post-attach.ts b/src/gateway/server-startup-post-attach.ts index 9e9069227b2..2da02d6565d 100644 --- a/src/gateway/server-startup-post-attach.ts +++ b/src/gateway/server-startup-post-attach.ts @@ -470,6 +470,7 @@ export async function startGatewayPostAttachRuntime( }; logChannels: { info: (msg: string) => void; error: (msg: string) => void }; unavailableGatewayMethods: Set; + getCronService?: () => PluginHookGatewayCronService | null | undefined; onPluginServices?: (pluginServices: PluginServicesHandle | null) => void; onSidecarsReady?: () => void; startupTrace?: GatewayStartupTrace; @@ -570,7 +571,9 @@ export async function startGatewayPostAttachRuntime( port: params.port, config: params.gatewayPluginConfigAtStart, workspaceDir: params.defaultWorkspaceDir, - getCron: () => params.deps.cron as PluginHookGatewayCronService | undefined, + getCron: () => + params.getCronService?.() ?? + (params.deps.cron as PluginHookGatewayCronService | undefined), }, ) .catch((err) => { diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index d724c6a6ed9..0458fdb82e8 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -36,6 +36,7 @@ import { setCurrentPluginMetadataSnapshot, } from "../plugins/current-plugin-metadata-snapshot.js"; import { runGlobalGatewayStopSafely } from "../plugins/hook-runner-global.js"; +import type { PluginHookGatewayCronService } from "../plugins/hook-types.js"; import type { PluginRuntime } from "../plugins/runtime/types.js"; import { getTotalQueueSize } from "../process/command-queue.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -938,6 +939,8 @@ export async function startGatewayServer( logHooks, logChannels, unavailableGatewayMethods, + getCronService: () => + runtimeState?.cronState.cron as PluginHookGatewayCronService | undefined, onPluginServices: (pluginServices) => { runtimeState.pluginServices = pluginServices; },