






























@@ -1,8 +1,9 @@
11import fs from "node:fs/promises";
2+import path from "node:path";
23import { afterEach, describe, expect, it, vi } from "vitest";
34import { setupCronServiceSuite, writeCronStoreSnapshot } from "../../cron/service.test-harness.js";
45import { createCronServiceState } from "../../cron/service/state.js";
5-import { onTimer } from "../../cron/service/timer.js";
6+import { executeJobCore, onTimer } from "../../cron/service/timer.js";
67import { loadCronStore, saveCronStore } from "../../cron/store.js";
78import type { CronJob } from "../../cron/types.js";
89import * as detachedTaskRuntime from "../../tasks/detached-task-runtime.js";
@@ -49,6 +50,62 @@ afterEach(() => {
4950});
50515152describe("cron service timer seam coverage", () => {
53+it("routes main cron jobs onto a cron run lane derived from the target agent", async () => {
54+const { storePath } = await makeStorePath();
55+const now = Date.parse("2026-03-23T12:00:00.000Z");
56+const enqueueSystemEvent = vi.fn();
57+const requestHeartbeat = vi.fn();
58+const runHeartbeatOnce = vi.fn(async () => ({ status: "ran" as const, durationMs: 1 }));
59+const job = {
60+ ...createDueMainJob({ now, wakeMode: "now" }),
61+sessionKey: "agent:main-pr-router:main",
62+state: { runningAtMs: now },
63+};
64+const cronRunSessionKey = `agent:main-pr-router:cron:main-heartbeat-job:run:${now}`;
65+const sessionStorePath = path.join(path.dirname(path.dirname(storePath)), "sessions.json");
66+await fs.writeFile(
67+sessionStorePath,
68+JSON.stringify({
69+"agent:main-pr-router:main": {
70+lastChannel: "discord",
71+lastTo: "channel-1",
72+lastAccountId: "default",
73+},
74+}),
75+"utf8",
76+);
77+78+const state = createCronServiceState({
79+ storePath,
80+cronEnabled: true,
81+log: logger,
82+nowMs: () => now,
83+resolveSessionStorePath: () => sessionStorePath,
84+ enqueueSystemEvent,
85+ requestHeartbeat,
86+ runHeartbeatOnce,
87+runIsolatedAgentJob: vi.fn(async () => ({ status: "ok" as const })),
88+});
89+90+const result = await executeJobCore(state, job);
91+92+expect(result).toMatchObject({ status: "ok", sessionKey: cronRunSessionKey });
93+expect(enqueueSystemEvent).toHaveBeenCalledWith("heartbeat seam tick", {
94+agentId: undefined,
95+sessionKey: cronRunSessionKey,
96+contextKey: "cron:main-heartbeat-job",
97+deliveryContext: { channel: "discord", to: "channel-1", accountId: "default" },
98+});
99+expect(runHeartbeatOnce).toHaveBeenCalledWith({
100+source: "cron",
101+intent: "immediate",
102+reason: "cron:main-heartbeat-job",
103+agentId: undefined,
104+sessionKey: cronRunSessionKey,
105+heartbeat: { target: "last" },
106+});
107+});
108+52109it("persists the next schedule and hands off next-heartbeat main jobs", async () => {
53110const { storePath } = await makeStorePath();
54111const now = Date.parse("2026-03-23T12:00:00.000Z");
@@ -73,17 +130,18 @@ describe("cron service timer seam coverage", () => {
7313074131await onTimer(state);
75132133+const cronRunSessionKey = `agent:main:cron:main-heartbeat-job:run:${now}`;
76134expect(enqueueSystemEvent).toHaveBeenCalledWith("heartbeat seam tick", {
77135agentId: undefined,
78-sessionKey: "agent:main:main",
136+sessionKey: cronRunSessionKey,
79137contextKey: "cron:main-heartbeat-job",
80138});
81139expect(requestHeartbeat).toHaveBeenCalledWith({
82140source: "cron",
83141intent: "event",
84142reason: "cron:main-heartbeat-job",
85143agentId: undefined,
86-sessionKey: "agent:main:main",
144+sessionKey: cronRunSessionKey,
87145heartbeat: { target: "last" },
88146});
89147@@ -103,7 +161,7 @@ describe("cron service timer seam coverage", () => {
103161expect(task.sourceId).toBe("main-heartbeat-job");
104162expect(task.ownerKey).toBe("");
105163expect(task.scopeKind).toBe("system");
106-expect(task.childSessionKey).toBe("agent:main:main");
164+expect(task.childSessionKey).toBe(cronRunSessionKey);
107165expect(task.runId).toBe(`cron:main-heartbeat-job:${now}`);
108166expect(task.label).toBe("main heartbeat job");
109167expect(task.task).toBe("main heartbeat job");
@@ -201,9 +259,10 @@ describe("cron service timer seam coverage", () => {
201259{ jobId: "main-heartbeat-job", error: ledgerError },
202260"cron: failed to create task ledger record",
203261);
262+const cronRunSessionKey = `agent:main:cron:main-heartbeat-job:run:${now}`;
204263expect(enqueueSystemEvent).toHaveBeenCalledWith("heartbeat seam tick", {
205264agentId: undefined,
206-sessionKey: "agent:main:main",
265+sessionKey: cronRunSessionKey,
207266contextKey: "cron:main-heartbeat-job",
208267});
209268此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。