fix(cron): consolidate announce delivery, fire-and-forget trigger, and minimal prompt mode (#40204)

* fix(cron): consolidate announce delivery and detach manual runs

* fix: queue detached cron runs (#40204)
This commit is contained in:
Tyler Yust
2026-03-08 14:46:33 -07:00
committed by GitHub
parent 7dfd77abeb
commit 38543d8196
16 changed files with 534 additions and 88 deletions

View File

@@ -135,9 +135,15 @@ describe("resolvePromptModeForSession", () => {
expect(resolvePromptModeForSession("agent:main:subagent:child")).toBe("minimal");
});
it("uses full mode for cron sessions", () => {
expect(resolvePromptModeForSession("agent:main:cron:job-1")).toBe("full");
expect(resolvePromptModeForSession("agent:main:cron:job-1:run:run-abc")).toBe("full");
it("uses minimal mode for cron sessions", () => {
expect(resolvePromptModeForSession("agent:main:cron:job-1")).toBe("minimal");
expect(resolvePromptModeForSession("agent:main:cron:job-1:run:run-abc")).toBe("minimal");
});
it("uses full mode for regular and undefined sessions", () => {
expect(resolvePromptModeForSession(undefined)).toBe("full");
expect(resolvePromptModeForSession("agent:main")).toBe("full");
expect(resolvePromptModeForSession("agent:main:thread:abc")).toBe("full");
});
});

View File

@@ -19,7 +19,7 @@ import type {
PluginHookBeforeAgentStartResult,
PluginHookBeforePromptBuildResult,
} from "../../../plugins/types.js";
import { isSubagentSessionKey } from "../../../routing/session-key.js";
import { isCronSessionKey, isSubagentSessionKey } from "../../../routing/session-key.js";
import { joinPresentTextSegments } from "../../../shared/text/join-segments.js";
import { resolveSignalReactionLevel } from "../../../signal/reaction-level.js";
import { resolveTelegramInlineButtonsScope } from "../../../telegram/inline-buttons.js";
@@ -613,7 +613,7 @@ export function resolvePromptModeForSession(sessionKey?: string): "minimal" | "f
if (!sessionKey) {
return "full";
}
return isSubagentSessionKey(sessionKey) ? "minimal" : "full";
return isSubagentSessionKey(sessionKey) || isCronSessionKey(sessionKey) ? "minimal" : "full";
}
export function resolveAttemptFsWorkspaceOnly(params: {

View File

@@ -197,6 +197,25 @@ describe("subagent announce timeout config", () => {
expect(internalEvents[0]?.announceType).toBe("cron job");
});
it("regression, keeps child announce internal when requester is a cron run session", async () => {
const cronSessionKey = "agent:main:cron:daily-check:run:run-123";
await runAnnounceFlowForTest("run-cron-internal", {
requesterSessionKey: cronSessionKey,
requesterDisplayKey: cronSessionKey,
requesterOrigin: { channel: "discord", to: "channel:cron-results", accountId: "acct-1" },
});
const directAgentCall = findGatewayCall(
(call) => call.method === "agent" && call.expectFinal === true,
);
expect(directAgentCall?.params?.sessionKey).toBe(cronSessionKey);
expect(directAgentCall?.params?.deliver).toBe(false);
expect(directAgentCall?.params?.channel).toBeUndefined();
expect(directAgentCall?.params?.to).toBeUndefined();
expect(directAgentCall?.params?.accountId).toBeUndefined();
});
it("regression, routes child announce to parent session instead of grandparent when parent session still exists", async () => {
const parentSessionKey = "agent:main:subagent:parent";
requesterDepthResolver = (sessionKey?: string) =>

View File

@@ -14,6 +14,7 @@ import type { ConversationRef } from "../infra/outbound/session-binding-service.
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
import { normalizeAccountId, normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js";
import { isCronSessionKey } from "../sessions/session-key-utils.js";
import { extractTextFromChatContent } from "../shared/chat-content.js";
import {
type DeliveryContext,
@@ -78,6 +79,10 @@ function resolveSubagentAnnounceTimeoutMs(cfg: ReturnType<typeof loadConfig>): n
return Math.min(Math.max(1, Math.floor(configured)), MAX_TIMER_SAFE_TIMEOUT_MS);
}
function isInternalAnnounceRequesterSession(sessionKey: string | undefined): boolean {
return getSubagentDepthFromSessionStore(sessionKey) >= 1 || isCronSessionKey(sessionKey);
}
function summarizeDeliveryError(error: unknown): string {
if (error instanceof Error) {
return error.message || "error";
@@ -580,8 +585,7 @@ async function resolveSubagentCompletionOrigin(params: {
async function sendAnnounce(item: AnnounceQueueItem) {
const cfg = loadConfig();
const announceTimeoutMs = resolveSubagentAnnounceTimeoutMs(cfg);
const requesterDepth = getSubagentDepthFromSessionStore(item.sessionKey);
const requesterIsSubagent = requesterDepth >= 1;
const requesterIsSubagent = isInternalAnnounceRequesterSession(item.sessionKey);
const origin = item.origin;
const threadId =
origin?.threadId != null && origin.threadId !== "" ? String(origin.threadId) : undefined;
@@ -1216,6 +1220,8 @@ export async function runSubagentAnnounceFlow(params: {
}
let requesterDepth = getSubagentDepthFromSessionStore(targetRequesterSessionKey);
const requesterIsInternalSession = () =>
requesterDepth >= 1 || isCronSessionKey(targetRequesterSessionKey);
let childCompletionFindings: string | undefined;
let subagentRegistryRuntime:
@@ -1339,7 +1345,7 @@ export async function runSubagentAnnounceFlow(params: {
const announceSessionId = childSessionId || "unknown";
const findings = childCompletionFindings || reply || "(no output)";
let requesterIsSubagent = requesterDepth >= 1;
let requesterIsSubagent = requesterIsInternalSession();
if (requesterIsSubagent) {
const {
isSubagentSessionRunActive,
@@ -1363,7 +1369,7 @@ export async function runSubagentAnnounceFlow(params: {
targetRequesterOrigin =
normalizeDeliveryContext(fallback.requesterOrigin) ?? targetRequesterOrigin;
requesterDepth = getSubagentDepthFromSessionStore(targetRequesterSessionKey);
requesterIsSubagent = requesterDepth >= 1;
requesterIsSubagent = requesterIsInternalSession();
}
}
}