fix(cron): make delivery previews dry-run safe

This commit is contained in:
Ayaan Zaidi
2026-04-21 10:50:53 +05:30
parent 4f2d24f463
commit 9e160d5c0f
4 changed files with 66 additions and 12 deletions

View File

@@ -145,6 +145,7 @@ describe("printCronList", () => {
expect(logs[0]).toContain("Delivery");
expect(logs[1]).toContain("announce -> telegram:-100");
expect(logs[1]).toContain("resolved from last");
});
it("shows dash in Model column for systemEvent jobs", () => {

View File

@@ -152,7 +152,7 @@ const CRON_NEXT_PAD = 10;
const CRON_LAST_PAD = 10;
const CRON_STATUS_PAD = 9;
const CRON_TARGET_PAD = 9;
const CRON_DELIVERY_PAD = 42;
const CRON_DELIVERY_PAD = 64;
const CRON_AGENT_PAD = 10;
const CRON_MODEL_PAD = 20;
@@ -278,13 +278,18 @@ export async function resolveCronDeliveryPreview(job: CronJob): Promise<CronDeli
]);
const cfg = loadConfig();
const agentId = job.agentId?.trim() || resolveDefaultAgentId(cfg);
const resolved = await resolveDeliveryTarget(cfg, agentId, {
channel: requestedChannel,
to: plan.to,
threadId: plan.threadId,
accountId: plan.accountId,
sessionKey: job.sessionKey,
});
const resolved = await resolveDeliveryTarget(
cfg,
agentId,
{
channel: requestedChannel,
to: plan.to,
threadId: plan.threadId,
accountId: plan.accountId,
sessionKey: job.sessionKey,
},
{ dryRun: true },
);
if (!resolved.ok) {
return {
label: `${plan.mode} -> ${formatTarget(requestedChannel, plan.to ?? null)}`,
@@ -358,10 +363,10 @@ export function printCronList(
const statusLabel = pad(statusRaw, CRON_STATUS_PAD);
const targetLabel = pad(job.sessionTarget ?? "-", CRON_TARGET_PAD);
const deliveryPreview = opts?.deliveryPreviews?.get(job.id);
const deliveryLabel = pad(
truncate(deliveryPreview?.label ?? "-", CRON_DELIVERY_PAD),
CRON_DELIVERY_PAD,
);
const deliveryText = deliveryPreview
? `${deliveryPreview.label} (${deliveryPreview.detail})`
: "-";
const deliveryLabel = pad(truncate(deliveryText, CRON_DELIVERY_PAD), CRON_DELIVERY_PAD);
const agentLabel = pad(truncate(job.agentId ?? "-", CRON_AGENT_PAD), CRON_AGENT_PAD);
const modelLabel = pad(
truncate(

View File

@@ -335,6 +335,25 @@ describe("resolveDeliveryTarget", () => {
);
});
it("skips id-like target normalization for dry-run delivery previews", async () => {
setMainSessionEntry(undefined);
vi.mocked(maybeResolveIdLikeTarget).mockClear();
const result = await resolveDeliveryTarget(
makeCfg({ bindings: [] }),
AGENT_ID,
{
channel: "forum",
to: "123456789",
},
{ dryRun: true },
);
expect(result.ok).toBe(true);
expect(result.to).toBe("123456789");
expect(maybeResolveIdLikeTarget).not.toHaveBeenCalled();
});
it("falls back to the runtime target resolver when the channel plugin is not already loaded", async () => {
setMainSessionEntry(undefined);
setActivePluginRegistry(

View File

@@ -76,6 +76,7 @@ export async function resolveDeliveryTarget(
accountId?: string;
sessionKey?: string;
},
options?: { dryRun?: boolean },
): Promise<DeliveryTargetResolution> {
const requestedChannel = typeof jobPayload.channel === "string" ? jobPayload.channel : "last";
const explicitTo = typeof jobPayload.to === "string" ? jobPayload.to : undefined;
@@ -177,6 +178,34 @@ export async function resolveDeliveryTarget(
};
}
if (options?.dryRun) {
const { getLoadedChannelPluginForRead } = await loadDeliveryTargetRuntime();
const defaultTo = getLoadedChannelPluginForRead(channel)?.config.resolveDefaultTo?.({
cfg,
accountId,
});
const previewTo = toCandidate ?? defaultTo;
if (!previewTo) {
return {
ok: false,
channel,
to: undefined,
accountId,
threadId,
mode,
error: new Error("Target is required for delivery preview."),
};
}
return {
ok: true,
channel,
to: previewTo,
accountId,
threadId,
mode,
};
}
let effectiveAllowFrom: string[] | undefined;
if (mode === "implicit") {
const {