
























@@ -1620,6 +1620,12 @@ describe("cron service timer regressions", () => {
16201620id: "isolated-context-assembled-81368",
16211621name: "context assembled regression",
16221622},
1623+{
1624+phase: "before_agent_reply",
1625+phaseText: "before-agent-reply",
1626+id: "isolated-before-agent-reply-82811",
1627+name: "before agent reply regression",
1628+},
16231629] satisfies Array<{
16241630phase: CronAgentExecutionPhase;
16251631phaseText: string;
@@ -1710,6 +1716,86 @@ describe("cron service timer regressions", () => {
17101716},
17111717);
171217181719+it("re-arms the pre-execution watchdog when before_agent_reply does not claim (#82811)", async () => {
1720+vi.useFakeTimers();
1721+try {
1722+const store = timerRegressionFixtures.makeStorePath();
1723+const scheduledAt = Date.parse("2026-05-17T03:00:00.000Z");
1724+const cronJob = createIsolatedRegressionJob({
1725+id: "isolated-before-agent-reply-unhandled-82811",
1726+name: "before agent reply unhandled regression",
1727+ scheduledAt,
1728+schedule: { kind: "at", at: new Date(scheduledAt).toISOString() },
1729+payload: { kind: "agentTurn", message: "work", timeoutSeconds: 1_200 },
1730+state: { nextRunAtMs: scheduledAt },
1731+});
1732+await writeCronJobs(store.storePath, [cronJob]);
1733+1734+vi.setSystemTime(scheduledAt);
1735+let now = scheduledAt;
1736+const started = createDeferred<void>();
1737+let abortObserved = false;
1738+const cleanupTimedOutAgentRun = vi.fn(async () => {});
1739+const state = createCronServiceState({
1740+cronEnabled: true,
1741+storePath: store.storePath,
1742+log: noopLogger,
1743+nowMs: () => now,
1744+enqueueSystemEvent: vi.fn(),
1745+requestHeartbeat: vi.fn(),
1746+ cleanupTimedOutAgentRun,
1747+runIsolatedAgentJob: vi.fn(
1748+async ({
1749+ abortSignal,
1750+ onExecutionStarted,
1751+ onExecutionPhase,
1752+}: {
1753+abortSignal?: AbortSignal;
1754+onExecutionStarted?: (info?: CronAgentExecutionStarted) => void;
1755+onExecutionPhase?: (info: CronAgentExecutionPhaseUpdate) => void;
1756+}) => {
1757+onExecutionStarted?.({
1758+jobId: "isolated-before-agent-reply-unhandled-82811",
1759+phase: "runner_entered",
1760+});
1761+onExecutionPhase?.({
1762+jobId: "isolated-before-agent-reply-unhandled-82811",
1763+phase: "before_agent_reply",
1764+});
1765+onExecutionPhase?.({
1766+jobId: "isolated-before-agent-reply-unhandled-82811",
1767+phase: "runtime_plugins",
1768+});
1769+started.resolve();
1770+abortSignal?.addEventListener(
1771+"abort",
1772+() => {
1773+abortObserved = true;
1774+},
1775+{ once: true },
1776+);
1777+return await new Promise<never>(() => {});
1778+},
1779+),
1780+});
1781+1782+const timerPromise = onTimer(state);
1783+await started.promise;
1784+await vi.advanceTimersByTimeAsync(60_100);
1785+now += 60_100;
1786+await timerPromise;
1787+1788+const job = requireJob(state, "isolated-before-agent-reply-unhandled-82811");
1789+expect(abortObserved).toBe(true);
1790+expect(job.state.lastStatus).toBe("error");
1791+expect(job.state.lastError).toContain("stalled before execution start");
1792+expect(job.state.lastError).toContain("runtime-plugins");
1793+expect(cleanupTimedOutAgentRun).toHaveBeenCalledTimes(1);
1794+} finally {
1795+vi.useRealTimers();
1796+}
1797+});
1798+17131799it("keeps state updates when cron next-run computation throws after a successful run (#30905)", () => {
17141800const startedAt = Date.parse("2026-03-02T12:00:00.000Z");
17151801const endedAt = startedAt + 50;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。