From 9e160d5c0fbab7ef5a7c0d344c8190caa83f45b0 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 21 Apr 2026 10:50:53 +0530 Subject: [PATCH] fix(cron): make delivery previews dry-run safe --- src/cli/cron-cli/shared.test.ts | 1 + src/cli/cron-cli/shared.ts | 29 +++++++++++-------- .../isolated-agent/delivery-target.test.ts | 19 ++++++++++++ src/cron/isolated-agent/delivery-target.ts | 29 +++++++++++++++++++ 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/cli/cron-cli/shared.test.ts b/src/cli/cron-cli/shared.test.ts index 7af3b94a067..a756e16b956 100644 --- a/src/cli/cron-cli/shared.test.ts +++ b/src/cli/cron-cli/shared.test.ts @@ -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", () => { diff --git a/src/cli/cron-cli/shared.ts b/src/cli/cron-cli/shared.ts index f07cf5bb611..2ed0ce65981 100644 --- a/src/cli/cron-cli/shared.ts +++ b/src/cli/cron-cli/shared.ts @@ -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 ${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( diff --git a/src/cron/isolated-agent/delivery-target.test.ts b/src/cron/isolated-agent/delivery-target.test.ts index 1c9deee084f..8d94cca7955 100644 --- a/src/cron/isolated-agent/delivery-target.test.ts +++ b/src/cron/isolated-agent/delivery-target.test.ts @@ -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( diff --git a/src/cron/isolated-agent/delivery-target.ts b/src/cron/isolated-agent/delivery-target.ts index 7946747ee4c..f4fafe0ce5c 100644 --- a/src/cron/isolated-agent/delivery-target.ts +++ b/src/cron/isolated-agent/delivery-target.ts @@ -76,6 +76,7 @@ export async function resolveDeliveryTarget( accountId?: string; sessionKey?: string; }, + options?: { dryRun?: boolean }, ): Promise { 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 {