fix(cron): resolve #35185 bot review findings

This commit is contained in:
Tak Hoffman
2026-03-04 21:40:57 -06:00
parent 3bf6ed181e
commit 2da3a1399c
4 changed files with 81 additions and 14 deletions

View File

@@ -516,6 +516,40 @@ describe("subagent announce formatting", () => {
expect(msg).not.toContain("There are still 1 active subagent run for this session.");
});
it("keeps cron child session when descendants are still pending", async () => {
sessionStore = {
"agent:main:subagent:test": {
sessionId: "child-session-cron-pending-descendants",
},
"agent:main:main": {
sessionId: "requester-session-cron-pending-descendants",
},
};
readLatestAssistantReplyMock.mockResolvedValue("");
chatHistoryMock.mockResolvedValueOnce({
messages: [{ role: "assistant", content: [{ type: "text", text: "final answer: cron" }] }],
});
subagentRegistryMock.countPendingDescendantRuns.mockImplementation((sessionKey: string) =>
sessionKey === "agent:main:subagent:test" ? 1 : 0,
);
const didAnnounce = await runSubagentAnnounceFlow({
childSessionKey: "agent:main:subagent:test",
childRunId: "run-direct-cron-pending-descendants",
requesterSessionKey: "agent:main:main",
requesterDisplayKey: "main",
requesterOrigin: { channel: "discord", to: "channel:12345", accountId: "acct-1" },
announceType: "cron job",
...defaultOutcomeAnnounce,
cleanup: "delete",
expectsCompletionMessage: true,
});
expect(didAnnounce).toBe(true);
expect(sendSpy).toHaveBeenCalledTimes(1);
expect(sessionsDeleteSpy).not.toHaveBeenCalled();
});
it("suppresses completion delivery when subagent reply is ANNOUNCE_SKIP", async () => {
const didAnnounce = await runSubagentAnnounceFlow({
childSessionKey: "agent:main:subagent:test",

View File

@@ -1238,12 +1238,14 @@ export async function runSubagentAnnounceFlow(params: {
// Best-effort only; fall back to direct announce behavior when unavailable.
}
const isCronAnnounce = params.announceType === "cron job";
if (pendingChildDescendantRuns > 0 && !isCronAnnounce) {
// The finished run still has pending descendant subagents (either active,
// or ended but still finishing their own announce and cleanup flow). Defer
// announcing this run until descendants fully settle.
if (pendingChildDescendantRuns > 0) {
// Descendants are still pending cleanup/announce work. Keep the child
// session alive so descendant routing and retry paths are not orphaned.
shouldDeleteChildSession = false;
return false;
if (!isCronAnnounce) {
// Non-cron announce flows still defer while descendants settle.
return false;
}
}
if (requesterDepth >= 1 && reply?.trim()) {

View File

@@ -421,6 +421,38 @@ describe("runCronIsolatedAgentTurn", () => {
});
});
it("keeps cron run status ok when announce and direct fallback both fail", async () => {
await withTelegramAnnounceFixture(
async ({ home, storePath, deps }) => {
mockAgentPayloads([{ text: "hello from cron" }]);
vi.mocked(runSubagentAnnounceFlow).mockResolvedValueOnce(false);
const res = await runTelegramAnnounceTurn({
home,
storePath,
deps,
delivery: {
mode: "announce",
channel: "telegram",
to: "123",
bestEffort: false,
},
});
expect(res.status).toBe("ok");
expect(res.delivered).toBe(false);
expect(res.deliveryAttempted).toBe(true);
expect(res.error).toContain("cron announce delivery failed");
expect(deps.sendMessageTelegram).toHaveBeenCalledTimes(1);
},
{
deps: {
sendMessageTelegram: vi.fn().mockRejectedValue(new Error("direct fallback failed")),
},
},
);
});
it("marks attempted when announce delivery reports false and best-effort is enabled", async () => {
const { res, deps } = await runAnnounceFlowResult(true);
expect(res.status).toBe("ok");

View File

@@ -475,15 +475,14 @@ export async function dispatchCronDelivery(
if (announceDeliveryWasAttempted && !delivered && !params.isAborted()) {
const directFallback = await deliverViaDirect(params.resolvedDelivery);
if (directFallback) {
return {
result: directFallback,
delivered,
deliveryAttempted,
summary,
outputText,
synthesizedText,
deliveryPayloads,
};
// Preserve announce-mode semantics: announce failure should not
// downgrade an otherwise successful cron execution to hard error.
// Keep announceResult and only use direct fallback on success.
logWarn(
`[cron:${params.job.id}] direct fallback after announce failure also failed: ${
directFallback.error ?? "unknown error"
}`,
);
}
// If direct delivery succeeded (returned null without error),
// `delivered` has been set to true by deliverViaDirect.