fix(cron): mirror BlueBubbles deliveries to target transcripts#75529
fix(cron): mirror BlueBubbles deliveries to target transcripts#75529zqchris wants to merge 3 commits into
Conversation
|
Thanks for the idea. I checked the current extension path, and this is a better fit for ClawHub.com than OpenClaw core. Close as ClawHub scope: current main removed the bundled BlueBubbles channel, and this PR would add BlueBubbles-specific scaffolding back into core cron dispatch for a channel that now belongs outside core. So I’m closing this as a scope-fit item for the plugin/community path. Please upload or publish it through ClawHub.com so it can live as an installable community skill instead of a bundled OpenClaw core change. Review detailsBest possible solution: Leave BlueBubbles bridge behavior in a ClawHub-hosted plugin, and open a separate generic cron transcript-mirror plugin API proposal only if an external plugin cannot express the needed behavior. Do we have a high-confidence way to reproduce the issue? No. Current main no longer has a bundled BlueBubbles channel, so there is no high-confidence current-main path for a BlueBubbles cron target to reach this dispatcher; the old source-level gap in cron not passing Is this the best way to solve the issue? No. The PR’s implementation is not the best current direction because it adds a removed service id to core cron code; the maintainable path is external BlueBubbles plugin work on ClawHub, or a generic plugin-owned cron mirror seam if maintainers decide that API is needed. Security review: Security review cleared: Cleared: the diff touches cron dispatch logic, focused tests, and changelog text only, with no dependency, workflow, permission, secret, install, release, or supply-chain changes. What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 9e1e59717ffd. |
1766847 to
9a49b3c
Compare
|
@clawsweeper re-review |
9a49b3c to
90ed132
Compare
90ed132 to
04104c0
Compare
|
@clawsweeper re-review Updated this PR in response to the prior review:
Re-review progress:
|
|
@clawsweeper re-review Updated PR body to satisfy the structured Real behavior proof gate (the Also pushed a CI fix in commit |
897fb3c to
9131570
Compare
|
Heads-up for triage: the same upstream commit Two clean options:
Happy with either — flagging so the maintainer call is explicit. |
9131570 to
b470764
Compare
|
@clawsweeper re-review Rebased onto current upstream/main to pick up the recent fix ( |
|
@clawsweeper re-review Pushed a fresh rebase / typing fix on top of the previously-sufficient body to align mock typings with the upstream tightening of |
…imeout
The non-BlueBubbles cron mirror test exercises `dispatchCronDelivery` against
a Telegram delivery target. `normalizeDeliveryTarget` ends up calling
`normalizeTargetForProvider("telegram", ...)`, which triggers the bundled
telegram channel plugin module load chain — that takes ~44s on CI runners
and pushes the suite over the 120s test timeout.
Stub `normalizeTargetForProvider` to a fast pass-through trim; route
normalization through the dedicated BlueBubbles route plugin (already
installed by `installBlueBubblesRoutePlugin`) is what the suite is actually
asserting on.
…tboundPayload contract Upstream change tightened NormalizedOutboundPayload to require mediaUrls and narrowed the implicit `vi.fn()` mock-call tuple to the empty argument list. Add explicit AppendFn / ExactFn signatures to the hoisted transcript mocks so `mock.calls[0]?.[0]` resolves to the param shape used by the test assertions, and supply mediaUrls: [] for the synthetic best-effort onError payload.
e65c441 to
90a0358
Compare
Summary
payload.text) and (b) appended a full-batch mirror throughdeliverOutboundPayloadseven when best-effort cron delivery had partial failures.projectOutboundPayloadPlanForMirror, so text + flattenedmediaUrlsride in one projection (media-only deliveries now mirror correctly throughresolveMirroredTranscriptText). Stop passingmirrorintodeliverOutboundPayloads. Cron now appends the transcript mirror itself, gated on its own all-success outcome (delivered === true).deliverOutboundPayloadsmirror behavior for non-cron callers, BlueBubbles outbound session-route resolution, idempotency-key reuse, the all-successdeliveredsemantics, or other channels (mirror is still BlueBubbles-only).Real behavior proof
Behavior or issue addressed: A media-only BlueBubbles cron delivery (e.g. an Oura morning-briefing chart with no caption) must still produce a transcript mirror so the agent can reconstruct context when the recipient replies later. The pre-fix helper joined only
payload.textand short-circuited when text was empty, so media-only deliveries left no record in the target transcript. Mixed text + media payloads also lost the media context in the mirror.Real environment tested: Local OpenClaw checkout on macOS Darwin 25.4.0, Node 22, pnpm 10.33.2 against the rebased PR head
04104c0409on top of upstream/main95a1c91531. The actual production helperscreateOutboundPayloadPlan,projectOutboundPayloadPlanForMirror, andresolveMirroredTranscriptText(whichdispatchCronDelivery's gated mirror append now calls) are invoked directly from anoderunner (pnpm exec tsx proof.ts) over redacted cron payload shapes.Exact steps or command run after this patch:
pnpm install.proof.tsthat imports the production projection helpers and feeds them (a) a media-only BlueBubbles cron payload and (b) a multi-payload text+media batch.pnpm exec tsx proof.tsagainst the patched worktree.Evidence after fix (redacted runtime log excerpt + copied live
nodeconsole output):After the patch (
pnpm exec tsx proof.ts):Pre-fix, the cron helper would short-circuit on the media-only payload (empty
text→undefinedmirror) and the mixed payload would produce a text-only mirror that lost the media filename. Post-fix, the shared projection capturesmediaUrls, andresolveMirroredTranscriptTextextracts a human-readable filename (oura-daily.png,chart.png) when text is empty.The all-success gate is enforced in
dispatchCronDeliveryitself: the call site now removesmirrorfromdeliverOutboundPayloadsand instead invokesappendAssistantMessageToSessionTranscriptdirectly underif (delivered && bluebubblesMirror) { ... }afterdelivered = deliveryResults.length > 0 && !hadPartialFailure. The new regressiondoes not append the transcript mirror when best-effort delivery has a partial failureindelivery-dispatch.mirror-bluebubbles.test.tsexercises that gate against the productiondispatchCronDelivery(withvi.mockfor the deliver step that triggersonError).Observed result after fix: Both shape cases produce a non-empty mirror that names the delivered media (or includes the joined text). Cron's all-success gate ensures the transcript append fires only when every payload landed; partial failures under best-effort delivery no longer leak full-batch text into the target transcript.
What was not tested: A live BlueBubbles cron run against a real BlueBubbles server delivering a real Oura PNG (would require a paired BlueBubbles deployment + the cron job). The
noderepro above exercises the production projection + mirror-text resolution, which is the actual decision pathdispatchCronDeliveryinvokes for every successful BlueBubbles cron batch.Change Type
Scope
Linked Issue/PR
Root Cause
(1)
buildBluebubblesCronMirrorjoined onlypayload.textand short-circuited when text was empty, dropping the mirror for media-only payloads. (2)deliverOutboundPayloadsappendsparams.mirrorwhenever any result succeeds; cron treats a batch as delivered only when all payloads succeed, so passingmirrorthrough that helper meant best-effort partial failures could append a full-batch mirror covering uncommitted payloads.Regression Test Plan
src/cron/isolated-agent/delivery-dispatch.mirror-bluebubbles.test.tsto assert againstappendAssistantMessageToSessionTranscript(the runtime helper cron now calls itself).media-only payloads still produce a transcript mirror— locks in the shared projection coveringmediaUrls.does not append the transcript mirror when best-effort delivery has a partial failure— locks in the all-success gate.User-visible / Behavior Changes
Security Impact
Verification
pnpm test src/cron/isolated-agent/delivery-dispatch.mirror-bluebubbles.test.ts— 7/7 passing on PR head;delivery-dispatch.double-announce.test.ts— 50/50 passing.pnpm check:changed --base upstream/main. The only failures are 2 pre-existingtsgo:core:testerrors insrc/agents/openai-transport-stream.test.tsandsrc/agents/pi-embedded-runner/openai-stream-wrappers.test.tsthat reproduce on plain upstream/main and are unrelated.Compatibility / Migration