mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(cron): pass agentId to runHeartbeatOnce for main-session jobs (#14140)
* fix(cron): pass agentId to runHeartbeatOnce for main-session jobs Main-session cron jobs with agentId always ran the heartbeat under the default agent, ignoring the job's agent binding. enqueueSystemEvent correctly routed the system event to the bound agent's session, but runHeartbeatOnce was called without agentId, so the heartbeat ran under the default agent and never picked up the event. Thread agentId from job.agentId through the CronServiceDeps type, timer execution, and the gateway wrapper so heartbeat-runner uses the correct agent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * cron: add heartbeat agentId propagation regression test (#14140) (thanks @ishikawa-pro) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -200,6 +200,49 @@ describe("CronService", () => {
|
||||
await store.cleanup();
|
||||
});
|
||||
|
||||
it("passes agentId to runHeartbeatOnce for main-session wakeMode now jobs", async () => {
|
||||
const store = await makeStorePath();
|
||||
const enqueueSystemEvent = vi.fn();
|
||||
const requestHeartbeatNow = vi.fn();
|
||||
const runHeartbeatOnce = vi.fn(async () => ({ status: "ran" as const, durationMs: 1 }));
|
||||
|
||||
const cron = new CronService({
|
||||
storePath: store.storePath,
|
||||
cronEnabled: true,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow,
|
||||
runHeartbeatOnce,
|
||||
runIsolatedAgentJob: vi.fn(async () => ({ status: "ok" })),
|
||||
});
|
||||
|
||||
await cron.start();
|
||||
const job = await cron.add({
|
||||
name: "wakeMode now with agent",
|
||||
agentId: "ops",
|
||||
enabled: true,
|
||||
schedule: { kind: "at", at: new Date(1).toISOString() },
|
||||
sessionTarget: "main",
|
||||
wakeMode: "now",
|
||||
payload: { kind: "systemEvent", text: "hello" },
|
||||
});
|
||||
|
||||
await cron.run(job.id, "force");
|
||||
|
||||
expect(runHeartbeatOnce).toHaveBeenCalledTimes(1);
|
||||
expect(runHeartbeatOnce).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: `cron:${job.id}`,
|
||||
agentId: "ops",
|
||||
}),
|
||||
);
|
||||
expect(requestHeartbeatNow).not.toHaveBeenCalled();
|
||||
expect(enqueueSystemEvent).toHaveBeenCalledWith("hello", { agentId: "ops" });
|
||||
|
||||
cron.stop();
|
||||
await store.cleanup();
|
||||
});
|
||||
|
||||
it("wakeMode now falls back to queued heartbeat when main lane stays busy", async () => {
|
||||
const store = await makeStorePath();
|
||||
const enqueueSystemEvent = vi.fn();
|
||||
|
||||
@@ -37,7 +37,7 @@ export type CronServiceDeps = {
|
||||
sessionStorePath?: string;
|
||||
enqueueSystemEvent: (text: string, opts?: { agentId?: string }) => void;
|
||||
requestHeartbeatNow: (opts?: { reason?: string }) => void;
|
||||
runHeartbeatOnce?: (opts?: { reason?: string }) => Promise<HeartbeatRunResult>;
|
||||
runHeartbeatOnce?: (opts?: { reason?: string; agentId?: string }) => Promise<HeartbeatRunResult>;
|
||||
runIsolatedAgentJob: (params: { job: CronJob; message: string }) => Promise<{
|
||||
status: "ok" | "error" | "skipped";
|
||||
summary?: string;
|
||||
|
||||
@@ -440,7 +440,7 @@ async function executeJobCore(
|
||||
|
||||
let heartbeatResult: HeartbeatRunResult;
|
||||
for (;;) {
|
||||
heartbeatResult = await state.deps.runHeartbeatOnce({ reason });
|
||||
heartbeatResult = await state.deps.runHeartbeatOnce({ reason, agentId: job.agentId });
|
||||
if (
|
||||
heartbeatResult.status !== "skipped" ||
|
||||
heartbeatResult.reason !== "requests-in-flight"
|
||||
|
||||
@@ -69,9 +69,11 @@ export function buildGatewayCronService(params: {
|
||||
requestHeartbeatNow,
|
||||
runHeartbeatOnce: async (opts) => {
|
||||
const runtimeConfig = loadConfig();
|
||||
const agentId = opts?.agentId ? resolveCronAgent(opts.agentId).agentId : undefined;
|
||||
return await runHeartbeatOnce({
|
||||
cfg: runtimeConfig,
|
||||
reason: opts?.reason,
|
||||
agentId,
|
||||
deps: { ...params.deps, runtime: defaultRuntime },
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user