fix(gateway): bind startup cron hook to live state

This commit is contained in:
Peter Steinberger
2026-04-27 20:25:37 +01:00
parent 7829c438a6
commit dc76963e36
4 changed files with 47 additions and 1 deletions

View File

@@ -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.

View File

@@ -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<void>
>(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(

View File

@@ -470,6 +470,7 @@ export async function startGatewayPostAttachRuntime(
};
logChannels: { info: (msg: string) => void; error: (msg: string) => void };
unavailableGatewayMethods: Set<string>;
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) => {

View File

@@ -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;
},