From 9ced682a9d7e53ec31fc528db5b3ec1c3d71390b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 07:50:45 +0100 Subject: [PATCH] fix(cron): omit disabled delivery trace errors --- CHANGELOG.md | 1 + .../run.message-tool-policy.test.ts | 23 +++++++- src/cron/isolated-agent/run.ts | 57 +++++++++++-------- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c764d3fcd95..9d5c8cbdf93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai - Discord: preserve explicit `user:` and `channel:` delivery targets through plugin routing so cron announcements and failure alerts keep their intended recipient kind. Refs #62777; carries forward #62798. Thanks @neeravmakwana. - Cron: add `failureAlert.includeSkipped` and `openclaw cron edit --failure-alert-include-skipped` so persistently skipped jobs can alert without counting skips as execution errors or affecting retry backoff. Fixes #60846. Thanks @slideshow-dingo. - Cron: invalidate stale pending runtime slots after live or offline `jobs.json` schedule edits, while preserving due slots for formatting-only rewrites. Fixes #27996 and #71607; carries forward #71651. Thanks @xialonglee and @fagnersouza666. +- Cron: omit synthetic `delivery.resolved` errors from `--no-deliver` run records while preserving explicit no-deliver target traces for agent-initiated messages. Fixes #72210; carries forward #72219. Thanks @hatemclawbot-collab and @xydigit-sj. - Cron: classify isolated runs as errors from structured embedded-run execution-denial metadata, with final-output marker fallback for `SYSTEM_RUN_DENIED`, `INVALID_REQUEST`, and approval-binding refusals, so blocked commands no longer appear green in cron history. Fixes #67172; carries forward #67186. Thanks @oc-gh-dr, @hclsys, and @1yihui. - Onboarding/GitHub Copilot: add manifest-owned `--github-copilot-token` support for non-interactive setup, including env fallback, tokenRef storage in ref mode, saved-profile reuse, and current Copilot default-model wiring. Refs #50002 and supersedes #50003. Thanks @scottgl9. - Gateway/install: add a validated `--wrapper`/`OPENCLAW_WRAPPER` service install path that persists executable LaunchAgent/systemd wrappers across forced reinstalls, updates, and doctor repairs instead of falling back to raw node/bun `ProgramArguments`. Fixes #69400. (#72445) Thanks @willtmc. diff --git a/src/cron/isolated-agent/run.message-tool-policy.test.ts b/src/cron/isolated-agent/run.message-tool-policy.test.ts index 5e1d68760b6..d9ec02b18f7 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -354,7 +354,7 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { error: undefined, }); - await runCronIsolatedAgentTurn({ + const result = await runCronIsolatedAgentTurn({ ...makeParams(), job: { id: "message-tool-policy", @@ -374,6 +374,18 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { messageThreadId: 42, currentChannelId: "room#42", }); + expect(result.delivery).toEqual( + expect.objectContaining({ + intended: { channel: "topicchat", to: "room#42", threadId: 42, source: "explicit" }, + resolved: { + ok: true, + channel: "topicchat", + to: "room#42", + threadId: 42, + source: "explicit", + }, + }), + ); }); it('does not resolve implicit "last" context for bare delivery.mode none', async () => { @@ -731,6 +743,15 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { ); expect(result.delivered).toBe(false); expect(result.deliveryAttempted).toBe(false); + expect(result.delivery).toEqual( + expect.objectContaining({ + intended: { channel: "last", to: null, source: "last" }, + messageToolSentTo: [{ channel: "messagechat", to: "123" }], + fallbackUsed: false, + delivered: false, + }), + ); + expect(result.delivery).not.toHaveProperty("resolved"); }); }); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index b1a3bdd80d9..f304b61cf5b 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -200,6 +200,34 @@ function normalizeMessagingToolTarget( }; } +function buildResolvedCronTraceTarget( + resolvedDelivery: ResolvedCronDeliveryTarget, +): CronDeliveryTrace["resolved"] { + if (resolvedDelivery.ok) { + return { + ok: true, + ...normalizeCronTraceTarget({ + channel: resolvedDelivery.channel, + to: resolvedDelivery.to, + accountId: resolvedDelivery.accountId, + threadId: resolvedDelivery.threadId, + source: resolvedDelivery.mode === "implicit" ? "last" : "explicit", + }), + }; + } + return { + ok: false, + ...normalizeCronTraceTarget({ + channel: resolvedDelivery.channel, + to: resolvedDelivery.to ?? null, + accountId: resolvedDelivery.accountId, + threadId: resolvedDelivery.threadId, + source: resolvedDelivery.mode === "implicit" ? "last" : "explicit", + }), + error: resolvedDelivery.error.message, + }; +} + function buildCronDeliveryTrace(params: { deliveryPlan: CronDeliveryPlan; resolvedDelivery: ResolvedCronDeliveryTarget; @@ -216,28 +244,11 @@ function buildCronDeliveryTrace(params: { source: params.deliveryPlan.channel === "last" || !params.deliveryPlan.channel ? "last" : "explicit", }); - const resolved = params.resolvedDelivery.ok - ? { - ok: true, - ...normalizeCronTraceTarget({ - channel: params.resolvedDelivery.channel, - to: params.resolvedDelivery.to, - accountId: params.resolvedDelivery.accountId, - threadId: params.resolvedDelivery.threadId, - source: params.resolvedDelivery.mode === "implicit" ? "last" : "explicit", - }), - } - : { - ok: false, - ...normalizeCronTraceTarget({ - channel: params.resolvedDelivery.channel, - to: params.resolvedDelivery.to ?? null, - accountId: params.resolvedDelivery.accountId, - threadId: params.resolvedDelivery.threadId, - source: params.resolvedDelivery.mode === "implicit" ? "last" : "explicit", - }), - error: params.resolvedDelivery.error.message, - }; + const includeResolved = + params.deliveryPlan.mode !== "none" || hasExplicitCronDeliveryTarget(params.deliveryPlan); + const resolved = includeResolved + ? buildResolvedCronTraceTarget(params.resolvedDelivery) + : undefined; const messageToolSentTo = params.messagingToolSentTargets .map((target) => normalizeMessagingToolTarget( @@ -249,7 +260,7 @@ function buildCronDeliveryTrace(params: { .filter((target): target is CronDeliveryTraceMessageTarget => Boolean(target)); return { ...(intended ? { intended } : {}), - resolved, + ...(resolved ? { resolved } : {}), ...(messageToolSentTo.length > 0 ? { messageToolSentTo } : {}), fallbackUsed: params.fallbackUsed, delivered: params.delivered,