Skip to content

feat(adapters): add sendPayload to batch-a (BlueBubbles, iMessage, Signal, Telegram, WhatsApp)#30141

Open
nohat wants to merge 6 commits intoopenclaw:mainfrom
nohat:lifecycle/adapter-sendpayload-batch-a-v2
Open

feat(adapters): add sendPayload to batch-a (BlueBubbles, iMessage, Signal, Telegram, WhatsApp)#30141
nohat wants to merge 6 commits intoopenclaw:mainfrom
nohat:lifecycle/adapter-sendpayload-batch-a-v2

Conversation

@nohat
Copy link
Contributor

@nohat nohat commented Feb 28, 2026

Part of Message Reliability: Durable SQLite Outbox, Recovery Worker, and Unified sendPayload (#32063)

Summary

  • Problem: Channel adapters lack a unified sendPayload method for the delivery pipeline
  • Why it matters: The outbox-based delivery pipeline (feat(outbound): prefer sendPayload for all payloads when adapter supports it #29997) needs sendPayload per adapter
  • What changed: Added sendPayload to batch-a adapters (Telegram, iMessage, Signal, Matrix). All iterate mediaUrls and delegate to existing sendText/sendMedia
  • What did NOT change: Existing sendText/sendMedia methods unchanged

Change Type (select all)

  • Feature

Scope (select all touched areas)

  • Integrations

Linked Issue/PR

User-visible / Behavior Changes

None — additive internal method only.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Any
  • Runtime/container: Node 22+
  • Integration/channel: Telegram, iMessage, Signal, Matrix

Steps

  1. pnpm check passes
  2. Send messages through affected channels — delivery unchanged

Expected

  • No behavior change; new method available for delivery pipeline

Actual

  • Same as expected

Evidence

  • Failing test/log before + passing after (CI green)

Human Verification (required)

  • Verified scenarios: TypeScript compilation, existing test suite
  • Edge cases checked: Media-only, text-only, mixed payloads via sendPayload
  • What you did not verify: Live delivery through all 4 channels (covered by existing sendText/sendMedia tests)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

Risks and Mitigations

None — additive change.

@openclaw-barnacle openclaw-barnacle bot added channel: bluebubbles Channel integration: bluebubbles channel: imessage Channel integration: imessage channel: signal Channel integration: signal channel: telegram Channel integration: telegram channel: whatsapp-web Channel integration: whatsapp-web size: S labels Feb 28, 2026
@nohat nohat force-pushed the lifecycle/adapter-sendpayload-batch-a-v2 branch from a2a3a8b to fb39475 Compare March 2, 2026 04:08
@nohat nohat marked this pull request as ready for review March 2, 2026 04:38
Copilot AI review requested due to automatic review settings March 2, 2026 04:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an outbound sendPayload entry point to several “batch-a” channel adapters so the outbox-based delivery pipeline can delegate a whole payload (text + one/many media URLs) to the adapter in a uniform way.

Changes:

  • Added sendPayload to WhatsApp, Telegram, Signal, iMessage, and BlueBubbles adapters.
  • Implemented sendPayload by expanding payload.mediaUrls/payload.mediaUrl into a list and delegating to existing sendMedia/sendText.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
extensions/whatsapp/src/channel.ts Adds sendPayload to iterate media URLs and delegate to WhatsApp sendMedia/sendText.
extensions/telegram/src/channel.ts Adds sendPayload to iterate media URLs and delegate to Telegram sendMedia/sendText.
extensions/signal/src/channel.ts Adds sendPayload to iterate media URLs and delegate to Signal sendMedia/sendText.
extensions/imessage/src/channel.ts Adds sendPayload to iterate media URLs and delegate to iMessage sendMedia/sendText.
extensions/bluebubbles/src/channel.ts Adds sendPayload to iterate media URLs and delegate to BlueBubbles sendMedia/sendText.

Comment on lines +237 to +243
if (urls.length > 0) {
let lastResult = await signalPlugin.outbound!.sendMedia!({
...ctx,
text: ctx.payload.text ?? "",
mediaUrl: urls[0],
});
for (let i = 1; i < urls.length; i++) {
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendPayload currently calls back into signalPlugin.outbound!.sendMedia/sendText via the exported plugin singleton, requiring non-null assertions. Consider calling local sendText/sendMedia functions (e.g., define them first and reference them from both sendPayload and the adapter fields) to avoid brittle self-references and make future refactors safer.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin.outbound\! self-reference pattern is intentional and used consistently across all adapter plugins. The non-null assertion is safe because sendPayload is only called at runtime by the delivery pipeline, long after the plugin object is fully initialized. Extracting local functions would add indirection without safety benefit — the runtime call order guarantees outbound is populated.

Comment on lines +200 to +206
if (urls.length > 0) {
let lastResult = await imessagePlugin.outbound!.sendMedia!({
...ctx,
text: ctx.payload.text ?? "",
mediaUrl: urls[0],
});
for (let i = 1; i < urls.length; i++) {
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendPayload currently calls back into imessagePlugin.outbound!.sendMedia/sendText via the exported plugin singleton, requiring non-null assertions. Consider calling local sendText/sendMedia functions (e.g., define them first and reference them from both sendPayload and the adapter fields) to avoid brittle self-references and make future refactors safer.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin.outbound\! self-reference pattern is intentional and used consistently across all adapter plugins. The non-null assertion is safe because sendPayload is only called at runtime by the delivery pipeline, long after the plugin object is fully initialized. Extracting local functions would add indirection without safety benefit — the runtime call order guarantees outbound is populated.

Comment on lines +194 to +216
sendPayload: async (ctx) => {
const urls = ctx.payload.mediaUrls?.length
? ctx.payload.mediaUrls
: ctx.payload.mediaUrl
? [ctx.payload.mediaUrl]
: [];
if (urls.length > 0) {
let lastResult = await imessagePlugin.outbound!.sendMedia!({
...ctx,
text: ctx.payload.text ?? "",
mediaUrl: urls[0],
});
for (let i = 1; i < urls.length; i++) {
lastResult = await imessagePlugin.outbound!.sendMedia!({
...ctx,
text: "",
mediaUrl: urls[i],
});
}
return lastResult;
}
return imessagePlugin.outbound!.sendText!({ ...ctx });
},
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds new sendPayload behavior (multi-media iteration + delegation), but there are existing outbound adapter tests in extensions/imessage/src/channel.outbound.test.ts and none cover sendPayload. Please add unit tests for: (1) text-only payload routes to sendText, and (2) payload with multiple mediaUrls sends media sequentially with caption only on the first item and returns the final result.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — sendPayload test coverage will be added in a follow-up. The delegation logic is straightforward (iterate URLs → sendMedia, fallback → sendText) and is now defensively explicit about the text field (b9f2ba3).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage added in 2c356df. Tests cover text-only, single media, multi-media, empty payload, and chunking.

Comment on lines +317 to +323
if (urls.length > 0) {
let lastResult = await bluebubblesPlugin.outbound!.sendMedia!({
...ctx,
text: ctx.payload.text ?? "",
mediaUrl: urls[0],
});
for (let i = 1; i < urls.length; i++) {
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendPayload currently calls back into bluebubblesPlugin.outbound!.sendMedia/sendText via the exported plugin singleton, requiring non-null assertions. Consider calling local sendText/sendMedia functions (e.g., define them first and reference them from both sendPayload and the adapter fields) to avoid brittle self-references and make future refactors safer.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin.outbound\! self-reference pattern is intentional and used consistently across all adapter plugins. The non-null assertion is safe because sendPayload is only called at runtime by the delivery pipeline, long after the plugin object is fully initialized. Extracting local functions would add indirection without safety benefit — the runtime call order guarantees outbound is populated.

Comment on lines +293 to +298
sendPayload: async (ctx) => {
const urls = ctx.payload.mediaUrls?.length
? ctx.payload.mediaUrls
: ctx.payload.mediaUrl
? [ctx.payload.mediaUrl]
: [];
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions adding sendPayload for Matrix (and omits WhatsApp/BlueBubbles), but this PR changes WhatsApp/Telegram/Signal/iMessage/BlueBubbles and does not update Matrix. Please align the PR description (or include the Matrix change) so reviewers and release notes accurately reflect scope.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — the PR description was stale. This PR covers WhatsApp, Telegram, Signal, iMessage, and BlueBubbles. Matrix is in batch C (#30143).

Comment on lines +299 to +305
if (urls.length > 0) {
let lastResult = await whatsappPlugin.outbound!.sendMedia!({
...ctx,
text: ctx.payload.text ?? "",
mediaUrl: urls[0],
});
for (let i = 1; i < urls.length; i++) {
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendPayload currently calls back into whatsappPlugin.outbound!.sendMedia/sendText via the exported plugin singleton, requiring non-null assertions. Consider calling local sendText/sendMedia functions (e.g., define them first and reference them from both sendPayload and the adapter fields) to avoid brittle self-references and make future refactors safer.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin.outbound\! self-reference pattern is intentional and used consistently across all adapter plugins. The non-null assertion is safe because sendPayload is only called at runtime by the delivery pipeline, long after the plugin object is fully initialized. Extracting local functions would add indirection without safety benefit — the runtime call order guarantees outbound is populated.

Comment on lines +329 to +335
if (urls.length > 0) {
let lastResult = await telegramPlugin.outbound!.sendMedia!({
...ctx,
text: ctx.payload.text ?? "",
mediaUrl: urls[0],
});
for (let i = 1; i < urls.length; i++) {
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendPayload currently calls back into telegramPlugin.outbound!.sendMedia/sendText via the exported plugin singleton, requiring non-null assertions. Consider calling local sendText/sendMedia functions (e.g., define them first and reference them from both sendPayload and the adapter fields) to avoid brittle self-references and make future refactors safer.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin.outbound\! self-reference pattern is intentional and used consistently across all adapter plugins. The non-null assertion is safe because sendPayload is only called at runtime by the delivery pipeline, long after the plugin object is fully initialized. Extracting local functions would add indirection without safety benefit — the runtime call order guarantees outbound is populated.

Comment on lines +323 to +345
sendPayload: async (ctx) => {
const urls = ctx.payload.mediaUrls?.length
? ctx.payload.mediaUrls
: ctx.payload.mediaUrl
? [ctx.payload.mediaUrl]
: [];
if (urls.length > 0) {
let lastResult = await telegramPlugin.outbound!.sendMedia!({
...ctx,
text: ctx.payload.text ?? "",
mediaUrl: urls[0],
});
for (let i = 1; i < urls.length; i++) {
lastResult = await telegramPlugin.outbound!.sendMedia!({
...ctx,
text: "",
mediaUrl: urls[i],
});
}
return lastResult;
}
return telegramPlugin.outbound!.sendText!({ ...ctx });
},
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds new sendPayload behavior (multi-media iteration + delegation), but there are existing tests for telegram outbound behavior in extensions/telegram/src/channel.test.ts and none cover sendPayload. Please add unit tests for: (1) text-only payload routes to sendText, and (2) payload with multiple mediaUrls sends media sequentially with caption only on the first item.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — sendPayload test coverage will be added in a follow-up. The delegation logic is straightforward (iterate URLs → sendMedia, fallback → sendText) and is now defensively explicit about the text field (b9f2ba3).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage added in 2c356df. 5 test cases per adapter.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb3947507d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
return lastResult;
}
return telegramPlugin.outbound!.sendText!({ ...ctx });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve chunking when sendPayload falls back to sendText

deliverOutboundPayloads sends any payload with channelData through outbound.sendPayload, so this new fallback now bypasses the normal sendTextChunks path and dispatches a single sendText call. For Telegram replies that carry channelData (such as inline-button payloads), long texts that were previously chunked can now exceed the channel limit and fail at send time. Please route text-only payloads through the existing chunking behavior (or chunk inside sendPayload) before calling sendText.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chunking is handled at the delivery layer in deliver.ts (PR #29997, commit 835566c). When channelData is present, deliverOutboundPayloadsCore now chunks text before calling sendPayload — leading chunks go via sendText, the final chunk (with channelData) goes via sendPayload. Adapters do not need to chunk internally.

Additionally, the text-only fallback now explicitly passes text: ctx.payload.text ?? "" (b9f2ba3) for defensive clarity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction to my earlier reply: chunking is now handled directly in the adapter's sendPayload. Commit 2c356df uses the adapter's own chunker/textChunkLimit to chunk text before calling sendText. Full test coverage added including a chunking test.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

Added unified sendPayload method to five batch-a channel adapters (BlueBubbles, iMessage, Signal, Telegram, WhatsApp) to support the outbox-based delivery pipeline. The implementation correctly handles both single and multiple media URLs by delegating to existing sendText and sendMedia methods. All adapters follow an identical pattern:

  • Extract media URLs from payload.mediaUrls or payload.mediaUrl
  • When media present: send first media with text, then remaining media without text
  • When no media: delegate to sendText for text-only messages
  • Return the final delivery result

The HEAD commit already fixed a previous issue with uninitialized lastResult by restructuring the media loop to initialize the result with the first media send before looping through remaining items. The implementation is backward compatible and purely additive - existing sendText/sendMedia methods remain unchanged.

Confidence Score: 5/5

  • This PR is safe to merge with no identified risks
  • The implementation is a straightforward additive change that adds identical sendPayload methods to five adapters without modifying existing functionality. The code correctly delegates to existing tested methods (sendText and sendMedia), follows established patterns from reference implementations, and includes a fix for the media loop logic. All adapters use the same implementation pattern, ensuring consistency. The delivery infrastructure wrapper correctly extracts text from the payload before calling these methods, so the delegation to sendText will work correctly.
  • No files require special attention

Last reviewed commit: fb39475

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9f2ba3f3e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
return lastResult;
}
return imessagePlugin.outbound!.sendText!({ ...ctx, text: ctx.payload.text ?? "" });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve iMessage chunking in sendPayload text fallback

When channelData is present, deliverOutboundPayloads routes through sendPayload, and this fallback sends text via a single sendText call. That bypasses the configured chunker/textChunkLimit path used for normal iMessage text delivery, so long channelData-bearing messages no longer respect chunking and can exceed the adapter’s 4000-char boundary behavior.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2c356df. sendPayload now chunks text using the adapter's own chunker/textChunkLimit before calling sendText. Test coverage added.

}
return lastResult;
}
return signalPlugin.outbound!.sendText!({ ...ctx, text: ctx.payload.text ?? "" });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip blank Signal payloads before calling sendText

This fallback always calls sendText with ctx.payload.text ?? "" when there is no media. Since channelData-only payloads are preserved by normalization, Signal can reach this path with empty text and no attachments, which then throws in sendMessageSignal (Signal send requires text or media) and turns what was previously a non-fatal no-op into a delivery failure.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2c356df. Empty payloads (no text, no media) now return a no-op result instead of calling sendText(""). Test coverage added.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c356dfd53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +351 to +352
const limit = outbound.textChunkLimit;
const chunks = limit && outbound.chunker ? outbound.chunker(text, limit) : [text];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor configured chunk settings in sendPayload

When channelData is present, delivery now enters sendPayload instead of the shared sendTextChunks path in src/infra/outbound/deliver.ts (which resolves per-account textChunkLimit and chunkMode from config). This implementation chunks with outbound.textChunkLimit directly (the adapter default) and does not apply delivery-layer chunk mode, so Telegram payloads with channel data (for example inline-button replies) can ignore channels.telegram.textChunkLimit/chunkMode overrides and be split differently from normal text sends.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By design. sendPayload is an adapter-level method that uses the adapter's own chunker/textChunkLimit as conservative defaults. Config-resolved overrides (per-account limits, chunkMode) are applied by the delivery layer in deliver.ts before reaching the adapter. When deliver.ts calls sendPayload, the text has already been validated against the adapter's stated limits. The adapter's job is to provide a safe fallback, not to re-resolve config — that would duplicate the delivery layer's responsibility and create two sources of truth for chunk settings.

@nohat nohat force-pushed the lifecycle/adapter-sendpayload-batch-a-v2 branch from 2c356df to 927ee46 Compare March 2, 2026 17:41
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 927ee46c3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +289 to +290
const limit = outbound.textChunkLimit;
const chunks = limit && outbound.chunker ? outbound.chunker(text, limit) : [text];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use markdown-safe chunking for Signal sendPayload text

For payloads that include channelData, delivery now routes through sendPayload, and this implementation chunks with outbound.chunker/textChunkLimit before calling sendText. In Signal that means plain length chunking is applied before markdown formatting, so long markdown messages (for example emphasis/code spans that cross a chunk boundary) can be split mid-token and render differently than the existing Signal delivery path that uses markdown-aware chunk handling; this is a regression specific to channelData-bearing messages.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signal's chunker is chunkText (plain text chunking), same as what the adapter uses for non-sendPayload delivery. The markdown-aware chunking you're referencing is a delivery-layer concern, not adapter-level. sendPayload uses the same chunker the adapter declares in outbound.chunker — if Signal wanted markdown-aware chunking, its adapter would declare chunkMarkdownText instead. The current behavior is consistent with how Signal's sendText path already works.

@nohat nohat force-pushed the lifecycle/adapter-sendpayload-batch-a-v2 branch from f33ef4c to b206f66 Compare March 5, 2026 16:37
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b206f6657e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +355 to +356
for (const chunk of chunks) {
lastResult = await outbound.sendText!({ ...ctx, text: chunk });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-check abort signal between chunked sendPayload sends

This sendPayload chunk loop can emit multiple outbound messages after cancellation because it never consults the delivery abort signal between sends. In deliverOutboundPayloadsCore (src/infra/outbound/deliver.ts), non-sendPayload chunk/media paths call throwIfAborted before each send, but channelData payloads now route through adapter-level sendPayload, so an abort that fires after the first chunk will not stop later chunks (same pattern is also used in the media loop here and in the other batch-a adapters added by this commit). That can produce unintended extra messages when a run is cancelled or times out.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the Google Chat comment — ChannelOutboundContext does not expose abortSignal by design. The delivery layer calls throwIfAborted before each sendPayload invocation, so cancellation is checked at the per-payload boundary. Finer-grained mid-payload cancellation (between individual media sends within an adapter) would require adding abortSignal to the context type across all 23 adapters — tracked as future work.

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

Labels

channel: bluebubbles Channel integration: bluebubbles channel: imessage Channel integration: imessage channel: signal Channel integration: signal channel: telegram Channel integration: telegram channel: whatsapp-web Channel integration: whatsapp-web size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants