Skip to content

fix(outbound): suppress media sends without delivery identity#92826

Closed
ZengWen-DT wants to merge 1 commit into
openclaw:mainfrom
ZengWen-DT:fix/qqbot-cron-tts-delivery-accounting
Closed

fix(outbound): suppress media sends without delivery identity#92826
ZengWen-DT wants to merge 1 commit into
openclaw:mainfrom
ZengWen-DT:fix/qqbot-cron-tts-delivery-accounting

Conversation

@ZengWen-DT

@ZengWen-DT ZengWen-DT commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Linked context

Closes #92816

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: QQBot-shaped media results with messageId: "" are no longer counted as delivered.

  • Real environment tested: Local OpenClaw checkout on this PR branch, Node 24.14.0, using the real outbound delivery code path with a QQBot-shaped adapter response.

  • Exact steps or command run after this patch:

    pnpm tsx --eval 'import { deliverOutboundPayloads } from "./src/infra/outbound/deliver.ts";
    import { setActivePluginRegistry } from "./src/plugins/runtime.ts";
    import { createOutboundTestPlugin, createTestRegistry } from "./src/test-utils/channel-plugins.ts";
    
    void (async () => {
      setActivePluginRegistry(
        createTestRegistry([
          {
            pluginId: "qqbot",
            source: "manual-proof",
            plugin: createOutboundTestPlugin({
              id: "qqbot",
              outbound: {
                deliveryMode: "direct",
                sendText: async () => ({ channel: "qqbot" as const, messageId: "text-1" }),
                sendMedia: async () => ({ channel: "qqbot" as const, messageId: "" }),
              },
            }),
          },
        ]),
      );
      const outcomes: unknown[] = [];
      const results = await deliverOutboundPayloads({
        cfg: {},
        channel: "qqbot",
        to: "qq-target",
        payloads: [{ text: "voice caption", mediaUrl: "file:///tmp/cron-tts.mp3" }],
        skipQueue: true,
        onPayloadDeliveryOutcome: (outcome) => outcomes.push(outcome),
      });
      console.log(JSON.stringify({ results, outcomes }, null, 2));
    })();'
  • Evidence after fix:

    {
      "results": [],
      "outcomes": [
        {
          "index": 0,
          "status": "suppressed",
          "reason": "adapter_returned_no_identity"
        }
      ]
    }
  • Observed result after fix: The outbound delivery result stays empty when the media adapter returns no delivery identity, so cron/TTS delivery accounting no longer sees that send as delivered.

  • What was not tested: I did not run a live QQBot cron/TTS delivery. I do not have QQBot credentials available in this checkout.

  • Proof limitations or environment constraints: The live service was not exercised; the proof covers the shared delivery boundary changed by this PR.

  • Before evidence (optional but encouraged): The new regression failed before the fix because the media branch counted the empty adapter response as delivered.

Tests and validation

  • pnpm test src/infra/outbound/deliver.test.ts -- --reporter=verbose
  • pnpm test extensions/qqbot/src/channel.message-adapter.test.ts -- --reporter=verbose
  • pnpm test src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts -- --reporter=verbose -t "applies TTS directives before direct cron announce delivery and mirrors spoken text|keeps unresolved message-tool delivery out of delivered status"
  • pnpm format:check -- src/infra/outbound/deliver.ts src/infra/outbound/deliver.test.ts
  • git diff --check
  • pnpm tsgo:core:test
  • Local autoreview completed with no accepted/actionable findings.

Regression coverage added: src/infra/outbound/deliver.test.ts now covers a media adapter result with no delivery identity.

Risk checklist

Did user-visible behavior change? Yes

Did config, environment, or migration behavior change? No

Did security, auth, secrets, network, or tool execution behavior change? No

Highest-risk area: shared outbound media delivery accounting.

How is that risk mitigated? The change is limited to delivery result accounting. Media sends with a real delivery identity are still counted; responses without an identity are treated as not delivered.

Current review state

Next action: maintainer review and CI.

Waiting on: reviewer feedback and CI.

Bot/reviewer comments addressed: none yet.

AI-assisted (Claude). The proof above was run manually.

@openclaw-barnacle openclaw-barnacle Bot added size: S proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded: the useful accounting change is already covered by the broader open clean PR at #79811, which applies the same identity-required contract across text, formatted-text, and media delivery paths rather than only this media branch.

Canonical path: Review and land #79811, or an equivalent broader shared empty-receipt fix, while tracking any remaining live QQBot generated-audio staging problem separately.

So I’m closing this here and keeping the remaining discussion on #79811.

Review details

Best possible solution:

Review and land #79811, or an equivalent broader shared empty-receipt fix, while tracking any remaining live QQBot generated-audio staging problem separately.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main's media branch appends a returned media result even when messageId is empty, so durable send can see a non-empty result set; the PR body shows the intended suppressed output through a local source-path proof.

Is this the best way to solve the issue?

No, not as the best landing path. The local patch is plausible, but the broader open PR is a better canonical solution because it applies the same identity-required contract to text, formatted-text, and media paths.

Security review:

Security review cleared: The diff only changes shared outbound delivery accounting and a colocated regression test; it does not touch dependencies, workflows, credentials, auth, secrets, or supply-chain execution.

AGENTS.md: found and applied where relevant.

What I checked:

  • Repository policy read: Root and scoped outbound/channel AGENTS.md were read; shared channel delivery and fallback behavior are compatibility-sensitive review surfaces. (AGENTS.md:1, 399f5bc99351)
  • Current main bug surface: Current main's media branch appends each returned media result and records delivery based on the returned slice, so a result object with an empty messageId can still make the payload look delivered. (src/infra/outbound/deliver.ts:1894, 399f5bc99351)
  • Existing identity contract: The existing helper treats a delivery result as identifiable only when messageId, chat/channel/room/conversation id, JID, or poll id is present; the nearby sendPayload path already uses this contract. (src/infra/outbound/deliver.ts:859, 399f5bc99351)
  • QQBot no-identity shape: QQBot media delivery can return messageId: result.messageId ?? "", so an engine result without a message id reaches shared outbound delivery as an object with no usable identity. (extensions/qqbot/src/channel.ts:126, 399f5bc99351)
  • Superseding PR implementation: The broader open PR adds shared helpers for identified delivery results and applies them to text, formatted-text, and media result collection, including the same media loop this PR changes. (src/infra/outbound/deliver.ts:1906, e046c2868b6a)
  • Superseding PR viability: The broader PR is still open, mergeable, clean, and already labeled proof-sufficient and ready for maintainer look, making it a viable canonical landing path for the shared empty-receipt contract. (e046c2868b6a)

Likely related people:

  • NarahariRaghava: Current blame for the shared outbound identity helper and media accounting branch points to commit b2da129, from PR 92773 by NarahariRaghava. (role: recent area contributor; confidence: medium; commits: b2da129e51da; files: src/infra/outbound/deliver.ts)
  • sliverp: The merged QQBot channel PR introduced the QQBot channel surface involved in the linked report and maps to commit 7902ccb. (role: original QQBot feature contributor; confidence: high; commits: 7902ccbd4344; files: extensions/qqbot/src/channel.ts)
  • indulgeback: Authored the broader open PR that already applies the identity-required delivery result contract across text, formatted-text, and media paths. (role: canonical open fix author; confidence: high; commits: e046c2868b6a; files: src/infra/outbound/deliver.ts, src/infra/outbound/deliver.test.ts, src/cron/isolated-agent.direct-delivery-core-channels.test.ts)
  • osolmaz: Opened the related durable final fallback semantics issue that tracks the broader cross-channel delivery contract this empty-identity behavior belongs to. (role: adjacent behavior-contract owner; confidence: medium; files: src/infra/outbound/deliver.ts, src/channels/message/send.ts)

Codex review notes: model internal, reasoning high; reviewed against 399f5bc99351.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 14, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed proof: supplied External PR includes structured after-fix real behavior proof. proof: sufficient ClawSweeper judged the real behavior proof convincing. labels Jun 14, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 当tts设置为always时,cron会投递失败,但是结果会显示已投递

2 participants